summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/about
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/test/about')
-rw-r--r--browser/base/content/test/about/POSTSearchEngine.xml6
-rw-r--r--browser/base/content/test/about/browser.ini59
-rw-r--r--browser/base/content/test/about/browser_aboutCertError.js548
-rw-r--r--browser/base/content/test/about/browser_aboutCertError_clockSkew.js153
-rw-r--r--browser/base/content/test/about/browser_aboutCertError_exception.js221
-rw-r--r--browser/base/content/test/about/browser_aboutCertError_mitm.js158
-rw-r--r--browser/base/content/test/about/browser_aboutCertError_noSubjectAltName.js67
-rw-r--r--browser/base/content/test/about/browser_aboutCertError_offlineSupport.js51
-rw-r--r--browser/base/content/test/about/browser_aboutCertError_telemetry.js164
-rw-r--r--browser/base/content/test/about/browser_aboutDialog_distribution.js66
-rw-r--r--browser/base/content/test/about/browser_aboutHome_search_POST.js104
-rw-r--r--browser/base/content/test/about/browser_aboutHome_search_composing.js110
-rw-r--r--browser/base/content/test/about/browser_aboutHome_search_searchbar.js44
-rw-r--r--browser/base/content/test/about/browser_aboutHome_search_suggestion.js78
-rw-r--r--browser/base/content/test/about/browser_aboutHome_search_telemetry.js101
-rw-r--r--browser/base/content/test/about/browser_aboutNetError.js245
-rw-r--r--browser/base/content/test/about/browser_aboutNetError_csp_iframe.js153
-rw-r--r--browser/base/content/test/about/browser_aboutNetError_native_fallback.js174
-rw-r--r--browser/base/content/test/about/browser_aboutNetError_trr.js189
-rw-r--r--browser/base/content/test/about/browser_aboutNetError_xfo_iframe.js139
-rw-r--r--browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbar.js311
-rw-r--r--browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarEmpty.js158
-rw-r--r--browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarNewWindow.js82
-rw-r--r--browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarPrefs.js74
-rw-r--r--browser/base/content/test/about/browser_aboutStopReload.js169
-rw-r--r--browser/base/content/test/about/browser_aboutSupport.js146
-rw-r--r--browser/base/content/test/about/browser_aboutSupport_newtab_security_state.js19
-rw-r--r--browser/base/content/test/about/browser_aboutSupport_places.js45
-rw-r--r--browser/base/content/test/about/browser_bug435325.js58
-rw-r--r--browser/base/content/test/about/browser_bug633691.js32
-rw-r--r--browser/base/content/test/about/csp_iframe.sjs33
-rw-r--r--browser/base/content/test/about/dummy_page.html9
-rw-r--r--browser/base/content/test/about/head.js220
-rw-r--r--browser/base/content/test/about/iframe_page_csp.html16
-rw-r--r--browser/base/content/test/about/iframe_page_xfo.html16
-rw-r--r--browser/base/content/test/about/print_postdata.sjs25
-rw-r--r--browser/base/content/test/about/searchSuggestionEngine.sjs9
-rw-r--r--browser/base/content/test/about/searchSuggestionEngine.xml11
-rw-r--r--browser/base/content/test/about/slow_loading_page.sjs29
-rw-r--r--browser/base/content/test/about/xfo_iframe.sjs34
40 files changed, 4326 insertions, 0 deletions
diff --git a/browser/base/content/test/about/POSTSearchEngine.xml b/browser/base/content/test/about/POSTSearchEngine.xml
new file mode 100644
index 0000000000..f2f884cf51
--- /dev/null
+++ b/browser/base/content/test/about/POSTSearchEngine.xml
@@ -0,0 +1,6 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+ <ShortName>POST Search</ShortName>
+ <Url type="text/html" method="POST" template="http://mochi.test:8888/browser/browser/base/content/test/about/print_postdata.sjs">
+ <Param name="searchterms" value="{searchTerms}"/>
+ </Url>
+</OpenSearchDescription>
diff --git a/browser/base/content/test/about/browser.ini b/browser/base/content/test/about/browser.ini
new file mode 100644
index 0000000000..ce82ff8006
--- /dev/null
+++ b/browser/base/content/test/about/browser.ini
@@ -0,0 +1,59 @@
+[DEFAULT]
+support-files =
+ head.js
+ print_postdata.sjs
+ searchSuggestionEngine.sjs
+ searchSuggestionEngine.xml
+ slow_loading_page.sjs
+ POSTSearchEngine.xml
+ dummy_page.html
+
+[browser_aboutCertError.js]
+[browser_aboutCertError_clockSkew.js]
+[browser_aboutCertError_exception.js]
+[browser_aboutCertError_mitm.js]
+[browser_aboutCertError_noSubjectAltName.js]
+[browser_aboutCertError_offlineSupport.js]
+[browser_aboutCertError_telemetry.js]
+[browser_aboutDialog_distribution.js]
+[browser_aboutHome_search_POST.js]
+[browser_aboutHome_search_composing.js]
+[browser_aboutHome_search_searchbar.js]
+[browser_aboutHome_search_suggestion.js]
+skip-if =
+ os == "mac"
+ os == "linux" && (!debug || bits == 64)
+ os == 'win' && os_version == '10.0' && bits == 64 && !debug # Bug 1399648, bug 1402502
+[browser_aboutHome_search_telemetry.js]
+[browser_aboutNetError.js]
+[browser_aboutNetError_csp_iframe.js]
+https_first_disabled = true
+support-files =
+ iframe_page_csp.html
+ csp_iframe.sjs
+[browser_aboutNetError_native_fallback.js]
+skip-if =
+ socketprocess_networking
+[browser_aboutNetError_trr.js]
+skip-if =
+ socketprocess_networking
+[browser_aboutNetError_xfo_iframe.js]
+https_first_disabled = true
+support-files =
+ iframe_page_xfo.html
+ xfo_iframe.sjs
+[browser_aboutNewTab_bookmarksToolbar.js]
+[browser_aboutNewTab_bookmarksToolbarEmpty.js]
+skip-if = tsan # Bug 1676326, highly frequent on TSan
+[browser_aboutNewTab_bookmarksToolbarNewWindow.js]
+[browser_aboutNewTab_bookmarksToolbarPrefs.js]
+[browser_aboutStopReload.js]
+[browser_aboutSupport.js]
+skip-if =
+ os == 'linux' && bits == 64 && asan && !debug # Bug 1713368
+[browser_aboutSupport_newtab_security_state.js]
+[browser_aboutSupport_places.js]
+skip-if = os == 'android'
+[browser_bug435325.js]
+skip-if = verify && !debug && os == 'mac'
+[browser_bug633691.js]
diff --git a/browser/base/content/test/about/browser_aboutCertError.js b/browser/base/content/test/about/browser_aboutCertError.js
new file mode 100644
index 0000000000..7f1f8149fa
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutCertError.js
@@ -0,0 +1,548 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// This is testing the aboutCertError page (Bug 1207107).
+
+const GOOD_PAGE = "https://example.com/";
+const GOOD_PAGE_2 = "https://example.org/";
+const BAD_CERT = "https://expired.example.com/";
+const UNKNOWN_ISSUER = "https://self-signed.example.com ";
+const BAD_STS_CERT =
+ "https://badchain.include-subdomains.pinning.example.com:443";
+const { TabStateFlusher } = ChromeUtils.importESModule(
+ "resource:///modules/sessionstore/TabStateFlusher.sys.mjs"
+);
+
+add_task(async function checkReturnToAboutHome() {
+ info(
+ "Loading a bad cert page directly and making sure 'return to previous page' goes to about:home"
+ );
+ for (let useFrame of [false, true]) {
+ let tab = await openErrorPage(BAD_CERT, useFrame);
+ let browser = tab.linkedBrowser;
+
+ is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack");
+ is(
+ browser.webNavigation.canGoForward,
+ false,
+ "!webNavigation.canGoForward"
+ );
+
+ // Populate the shistory entries manually, since it happens asynchronously
+ // and the following tests will be too soon otherwise.
+ await TabStateFlusher.flush(browser);
+ let { entries } = JSON.parse(SessionStore.getTabState(tab));
+ is(entries.length, 1, "there is one shistory entry");
+
+ info("Clicking the go back button on about:certerror");
+ let bc = browser.browsingContext;
+ if (useFrame) {
+ bc = bc.children[0];
+ }
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ "about:home"
+ );
+ await SpecialPowers.spawn(bc, [useFrame], async function (subFrame) {
+ let returnButton = content.document.getElementById("returnButton");
+ if (!subFrame) {
+ if (!Services.focus.focusedElement == returnButton) {
+ await ContentTaskUtils.waitForEvent(returnButton, "focus");
+ }
+ Assert.ok(true, "returnButton has focus");
+ }
+ // Note that going back to about:newtab might cause a process flip, if
+ // the browser is configured to run about:newtab in its own special
+ // content process.
+ returnButton.click();
+ });
+
+ await locationChangePromise;
+
+ is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack");
+ is(
+ browser.webNavigation.canGoForward,
+ false,
+ "!webNavigation.canGoForward"
+ );
+ is(gBrowser.currentURI.spec, "about:home", "Went back");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+});
+
+add_task(async function checkReturnToPreviousPage() {
+ info(
+ "Loading a bad cert page and making sure 'return to previous page' goes back"
+ );
+ for (let useFrame of [false, true]) {
+ let tab;
+ let browser;
+ if (useFrame) {
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE);
+ browser = tab.linkedBrowser;
+
+ BrowserTestUtils.loadURIString(browser, GOOD_PAGE_2);
+ await BrowserTestUtils.browserLoaded(browser, false, GOOD_PAGE_2);
+ await injectErrorPageFrame(tab, BAD_CERT);
+ } else {
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE);
+ browser = gBrowser.selectedBrowser;
+
+ info("Loading and waiting for the cert error");
+ let certErrorLoaded = BrowserTestUtils.waitForErrorPage(browser);
+ BrowserTestUtils.loadURIString(browser, BAD_CERT);
+ await certErrorLoaded;
+ }
+
+ is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack");
+ is(
+ browser.webNavigation.canGoForward,
+ false,
+ "!webNavigation.canGoForward"
+ );
+
+ // Populate the shistory entries manually, since it happens asynchronously
+ // and the following tests will be too soon otherwise.
+ await TabStateFlusher.flush(browser);
+ let { entries } = JSON.parse(SessionStore.getTabState(tab));
+ is(entries.length, 2, "there are two shistory entries");
+
+ info("Clicking the go back button on about:certerror");
+ let bc = browser.browsingContext;
+ if (useFrame) {
+ bc = bc.children[0];
+ }
+
+ let pageShownPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "pageshow",
+ true
+ );
+ await SpecialPowers.spawn(bc, [useFrame], async function (subFrame) {
+ let returnButton = content.document.getElementById("returnButton");
+ returnButton.click();
+ });
+ await pageShownPromise;
+
+ is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack");
+ is(browser.webNavigation.canGoForward, true, "webNavigation.canGoForward");
+ is(gBrowser.currentURI.spec, GOOD_PAGE, "Went back");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+});
+
+// This checks that the appinfo.appBuildID starts with a date string,
+// which is required for the misconfigured system time check.
+add_task(async function checkAppBuildIDIsDate() {
+ let appBuildID = Services.appinfo.appBuildID;
+ let year = parseInt(appBuildID.substr(0, 4), 10);
+ let month = parseInt(appBuildID.substr(4, 2), 10);
+ let day = parseInt(appBuildID.substr(6, 2), 10);
+
+ ok(year >= 2016 && year <= 2100, "appBuildID contains a valid year");
+ ok(month >= 1 && month <= 12, "appBuildID contains a valid month");
+ ok(day >= 1 && day <= 31, "appBuildID contains a valid day");
+});
+
+add_task(async function checkAdvancedDetails() {
+ info(
+ "Loading a bad cert page and verifying the main error and advanced details section"
+ );
+ for (let useFrame of [false, true]) {
+ let tab = await openErrorPage(BAD_CERT, useFrame);
+ let browser = tab.linkedBrowser;
+
+ let bc = browser.browsingContext;
+ if (useFrame) {
+ bc = bc.children[0];
+ }
+
+ let message = await SpecialPowers.spawn(bc, [], async function () {
+ let doc = content.document;
+
+ const shortDesc = doc.getElementById("errorShortDesc");
+ const sdArgs = JSON.parse(shortDesc.dataset.l10nArgs);
+ is(
+ sdArgs.hostname,
+ "expired.example.com",
+ "Should list hostname in error message."
+ );
+
+ Assert.ok(
+ doc.getElementById("certificateErrorDebugInformation").hidden,
+ "Debug info is initially hidden"
+ );
+
+ let exceptionButton = doc.getElementById("exceptionDialogButton");
+ Assert.ok(
+ !exceptionButton.disabled,
+ "Exception button is not disabled by default."
+ );
+
+ let advancedButton = doc.getElementById("advancedButton");
+ advancedButton.click();
+
+ // Wait until fluent sets the errorCode inner text.
+ let errorCode;
+ await ContentTaskUtils.waitForCondition(() => {
+ errorCode = doc.getElementById("errorCode");
+ return errorCode && errorCode.textContent != "";
+ }, "error code has been set inside the advanced button panel");
+
+ return { textContent: errorCode.textContent, tagName: errorCode.tagName };
+ });
+ is(
+ message.textContent,
+ "SEC_ERROR_EXPIRED_CERTIFICATE",
+ "Correct error message found"
+ );
+ is(message.tagName, "a", "Error message is a link");
+
+ message = await SpecialPowers.spawn(bc, [], async function () {
+ let doc = content.document;
+ let errorCode = doc.getElementById("errorCode");
+ errorCode.click();
+ let div = doc.getElementById("certificateErrorDebugInformation");
+ let text = doc.getElementById("certificateErrorText");
+ Assert.ok(
+ content.getComputedStyle(div).display !== "none",
+ "Debug information is visible"
+ );
+ let failedCertChain =
+ content.docShell.failedChannel.securityInfo.failedCertChain.map(cert =>
+ cert.getBase64DERString()
+ );
+ return {
+ divDisplay: content.getComputedStyle(div).display,
+ text: text.textContent,
+ failedCertChain,
+ };
+ });
+ isnot(message.divDisplay, "none", "Debug information is visible");
+ ok(message.text.includes(BAD_CERT), "Correct URL found");
+ ok(
+ message.text.includes("Certificate has expired"),
+ "Correct error message found"
+ );
+ ok(
+ message.text.includes("HTTP Strict Transport Security: false"),
+ "Correct HSTS value found"
+ );
+ ok(
+ message.text.includes("HTTP Public Key Pinning: false"),
+ "Correct HPKP value found"
+ );
+ let certChain = getCertChainAsString(message.failedCertChain);
+ ok(message.text.includes(certChain), "Found certificate chain");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+});
+
+add_task(async function checkAdvancedDetailsForHSTS() {
+ info(
+ "Loading a bad STS cert page and verifying the advanced details section"
+ );
+ for (let useFrame of [false, true]) {
+ let tab = await openErrorPage(BAD_STS_CERT, useFrame);
+ let browser = tab.linkedBrowser;
+
+ let bc = browser.browsingContext;
+ if (useFrame) {
+ bc = bc.children[0];
+ }
+
+ let message = await SpecialPowers.spawn(bc, [], async function () {
+ let doc = content.document;
+ let advancedButton = doc.getElementById("advancedButton");
+ advancedButton.click();
+
+ // Wait until fluent sets the errorCode inner text.
+ let ec;
+ await ContentTaskUtils.waitForCondition(() => {
+ ec = doc.getElementById("errorCode");
+ return ec.textContent != "";
+ }, "error code has been set inside the advanced button panel");
+
+ let cdl = doc.getElementById("cert_domain_link");
+ return {
+ ecTextContent: ec.textContent,
+ ecTagName: ec.tagName,
+ cdlTextContent: cdl.textContent,
+ cdlTagName: cdl.tagName,
+ };
+ });
+
+ const badStsUri = Services.io.newURI(BAD_STS_CERT);
+ is(
+ message.ecTextContent,
+ "SSL_ERROR_BAD_CERT_DOMAIN",
+ "Correct error message found"
+ );
+ is(message.ecTagName, "a", "Error message is a link");
+ const url = badStsUri.prePath.slice(badStsUri.prePath.indexOf(".") + 1);
+ is(message.cdlTextContent, url, "Correct cert_domain_link contents found");
+ is(message.cdlTagName, "a", "cert_domain_link is a link");
+
+ message = await SpecialPowers.spawn(bc, [], async function () {
+ let doc = content.document;
+ let errorCode = doc.getElementById("errorCode");
+ errorCode.click();
+ let div = doc.getElementById("certificateErrorDebugInformation");
+ let text = doc.getElementById("certificateErrorText");
+ let failedCertChain =
+ content.docShell.failedChannel.securityInfo.failedCertChain.map(cert =>
+ cert.getBase64DERString()
+ );
+ return {
+ divDisplay: content.getComputedStyle(div).display,
+ text: text.textContent,
+ failedCertChain,
+ };
+ });
+ isnot(message.divDisplay, "none", "Debug information is visible");
+ ok(message.text.includes(badStsUri.spec), "Correct URL found");
+ ok(
+ message.text.includes(
+ "requested domain name does not match the server\u2019s certificate"
+ ),
+ "Correct error message found"
+ );
+ ok(
+ message.text.includes("HTTP Strict Transport Security: false"),
+ "Correct HSTS value found"
+ );
+ ok(
+ message.text.includes("HTTP Public Key Pinning: true"),
+ "Correct HPKP value found"
+ );
+ let certChain = getCertChainAsString(message.failedCertChain);
+ ok(message.text.includes(certChain), "Found certificate chain");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+});
+
+add_task(async function checkUnknownIssuerLearnMoreLink() {
+ info(
+ "Loading a cert error for self-signed pages and checking the correct link is shown"
+ );
+ for (let useFrame of [false, true]) {
+ let tab = await openErrorPage(UNKNOWN_ISSUER, useFrame);
+ let browser = tab.linkedBrowser;
+
+ let bc = browser.browsingContext;
+ if (useFrame) {
+ bc = bc.children[0];
+ }
+
+ let href = await SpecialPowers.spawn(bc, [], async function () {
+ let learnMoreLink = content.document.getElementById("learnMoreLink");
+ return learnMoreLink.href;
+ });
+ ok(href.endsWith("security-error"), "security-error in the Learn More URL");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+});
+
+add_task(async function checkViewCertificate() {
+ info("Loading a cert error and checking that the certificate can be shown.");
+ for (let useFrame of [true, false]) {
+ if (useFrame) {
+ // Bug #1573502
+ continue;
+ }
+ let tab = await openErrorPage(UNKNOWN_ISSUER, useFrame);
+ let browser = tab.linkedBrowser;
+
+ let bc = browser.browsingContext;
+ if (useFrame) {
+ bc = bc.children[0];
+ }
+
+ let loaded = BrowserTestUtils.waitForNewTab(gBrowser, null, true);
+ await SpecialPowers.spawn(bc, [], async function () {
+ let viewCertificate = content.document.getElementById("viewCertificate");
+ viewCertificate.click();
+ });
+ await loaded;
+
+ let spec = gBrowser.selectedTab.linkedBrowser.documentURI.spec;
+ Assert.ok(
+ spec.startsWith("about:certificate"),
+ "about:certificate is the new opened tab"
+ );
+
+ await SpecialPowers.spawn(
+ gBrowser.selectedTab.linkedBrowser,
+ [],
+ async function () {
+ let doc = content.document;
+ let certificateSection = await ContentTaskUtils.waitForCondition(() => {
+ return doc.querySelector("certificate-section");
+ }, "Certificate section found");
+
+ let infoGroup =
+ certificateSection.shadowRoot.querySelector("info-group");
+ Assert.ok(infoGroup, "infoGroup found");
+
+ let items = infoGroup.shadowRoot.querySelectorAll("info-item");
+ let commonnameID = items[items.length - 1].shadowRoot
+ .querySelector("label")
+ .getAttribute("data-l10n-id");
+ Assert.equal(
+ commonnameID,
+ "certificate-viewer-common-name",
+ "The correct item was selected"
+ );
+
+ let commonnameValue =
+ items[items.length - 1].shadowRoot.querySelector(".info").textContent;
+ Assert.equal(
+ commonnameValue,
+ "self-signed.example.com",
+ "Shows the correct certificate in the page"
+ );
+ }
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab); // closes about:certificate
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+});
+
+add_task(async function checkBadStsCertHeadline() {
+ info(
+ "Loading a bad sts cert error page and checking that the correct headline is shown"
+ );
+ for (let useFrame of [false, true]) {
+ let tab = await openErrorPage(BAD_CERT, useFrame);
+ let browser = tab.linkedBrowser;
+
+ let bc = browser.browsingContext;
+ if (useFrame) {
+ bc = bc.children[0];
+ }
+
+ await SpecialPowers.spawn(bc, [useFrame], async _useFrame => {
+ const titleText = content.document.querySelector(".title-text");
+ is(
+ titleText.dataset.l10nId,
+ _useFrame ? "nssBadCert-sts-title" : "nssBadCert-title",
+ "Error page title is set"
+ );
+ });
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+});
+
+add_task(async function checkSandboxedIframe() {
+ info(
+ "Loading a bad sts cert error in a sandboxed iframe and check that the correct headline is shown"
+ );
+ let useFrame = true;
+ let sandboxed = true;
+ let tab = await openErrorPage(BAD_CERT, useFrame, sandboxed);
+ let browser = tab.linkedBrowser;
+
+ let bc = browser.browsingContext.children[0];
+ await SpecialPowers.spawn(bc, [], async function () {
+ let doc = content.document;
+
+ const titleText = doc.querySelector(".title-text");
+ is(
+ titleText.dataset.l10nId,
+ "nssBadCert-sts-title",
+ "Title shows Did Not Connect: Potential Security Issue"
+ );
+
+ const errorLabel = doc.querySelector(
+ '[data-l10n-id="cert-error-code-prefix-link"]'
+ );
+ const elArgs = JSON.parse(errorLabel.dataset.l10nArgs);
+ is(
+ elArgs.error,
+ "SEC_ERROR_EXPIRED_CERTIFICATE",
+ "Correct error message found"
+ );
+ is(
+ doc.getElementById("errorCode").tagName,
+ "a",
+ "Error message contains a link"
+ );
+ });
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function checkViewSource() {
+ info(
+ "Loading a bad sts cert error in a sandboxed iframe and check that the correct headline is shown"
+ );
+ let uri = "view-source:" + BAD_CERT;
+ let tab = await openErrorPage(uri);
+ let browser = tab.linkedBrowser;
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ let doc = content.document;
+
+ const errorLabel = doc.querySelector(
+ '[data-l10n-id="cert-error-code-prefix-link"]'
+ );
+ const elArgs = JSON.parse(errorLabel.dataset.l10nArgs);
+ is(
+ elArgs.error,
+ "SEC_ERROR_EXPIRED_CERTIFICATE",
+ "Correct error message found"
+ );
+ is(
+ doc.getElementById("errorCode").tagName,
+ "a",
+ "Error message contains a link"
+ );
+
+ const titleText = doc.querySelector(".title-text");
+ is(titleText.dataset.l10nId, "nssBadCert-title", "Error page title is set");
+
+ const shortDesc = doc.getElementById("errorShortDesc");
+ const sdArgs = JSON.parse(shortDesc.dataset.l10nArgs);
+ is(
+ sdArgs.hostname,
+ "expired.example.com",
+ "Should list hostname in error message."
+ );
+ });
+
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, uri);
+ info("Clicking the exceptionDialogButton in advanced panel");
+ await SpecialPowers.spawn(browser, [], async function () {
+ let doc = content.document;
+ let exceptionButton = doc.getElementById("exceptionDialogButton");
+ exceptionButton.click();
+ });
+
+ info("Loading the url after adding exception");
+ await loaded;
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ let doc = content.document;
+ ok(
+ !doc.documentURI.startsWith("about:certerror"),
+ "Exception has been added"
+ );
+ });
+
+ let certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+ ].getService(Ci.nsICertOverrideService);
+ certOverrideService.clearValidityOverride("expired.example.com", -1, {});
+
+ loaded = BrowserTestUtils.waitForErrorPage(browser);
+ BrowserReloadSkipCache();
+ await loaded;
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/about/browser_aboutCertError_clockSkew.js b/browser/base/content/test/about/browser_aboutCertError_clockSkew.js
new file mode 100644
index 0000000000..e3b77bd636
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutCertError_clockSkew.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const PREF_SERVICES_SETTINGS_CLOCK_SKEW_SECONDS =
+ "services.settings.clock_skew_seconds";
+const PREF_SERVICES_SETTINGS_LAST_FETCHED =
+ "services.settings.last_update_seconds";
+
+add_task(async function checkWrongSystemTimeWarning() {
+ async function setUpPage() {
+ let browser;
+ let certErrorLoaded;
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ () => {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "https://expired.example.com/"
+ );
+ browser = gBrowser.selectedBrowser;
+ certErrorLoaded = BrowserTestUtils.waitForErrorPage(browser);
+ },
+ false
+ );
+
+ info("Loading and waiting for the cert error");
+ await certErrorLoaded;
+
+ return SpecialPowers.spawn(browser, [], async function () {
+ let doc = content.document;
+ let div = doc.getElementById("errorShortDesc");
+ let learnMoreLink = doc.getElementById("learnMoreLink");
+
+ await ContentTaskUtils.waitForCondition(
+ () => div.textContent.includes("update your computer clock"),
+ "Correct error message found"
+ );
+
+ return {
+ divDisplay: content.getComputedStyle(div).display,
+ text: div.textContent,
+ learnMoreLink: learnMoreLink.href,
+ };
+ });
+ }
+
+ // Pretend that we recently updated our kinto clock skew pref
+ Services.prefs.setIntPref(
+ PREF_SERVICES_SETTINGS_LAST_FETCHED,
+ Math.floor(Date.now() / 1000)
+ );
+
+ // For this test, we want to trick Firefox into believing that
+ // the local system time (as returned by Date.now()) is wrong.
+ // Because we don't want to actually change the local system time,
+ // we will do the following:
+
+ // Take the validity date of our test page (expired.example.com).
+ let expiredDate = new Date("2010/01/05 12:00");
+ let localDate = Date.now();
+
+ // Compute the difference between the server date and the correct
+ // local system date.
+ let skew = Math.floor((localDate - expiredDate) / 1000);
+
+ // Make it seem like our reference server agrees that the certificate
+ // date is correct by recording the difference as clock skew.
+ Services.prefs.setIntPref(PREF_SERVICES_SETTINGS_CLOCK_SKEW_SECONDS, skew);
+
+ let localDateFmt = new Intl.DateTimeFormat("en-US", {
+ dateStyle: "medium",
+ }).format(localDate);
+
+ info("Loading a bad cert page with a skewed clock");
+ let message = await setUpPage();
+
+ isnot(
+ message.divDisplay,
+ "none",
+ "Wrong time message information is visible"
+ );
+ ok(
+ message.text.includes("update your computer clock"),
+ "Correct error message found"
+ );
+ ok(
+ message.text.includes("expired.example.com"),
+ "URL found in error message"
+ );
+ ok(message.text.includes(localDateFmt), "Correct local date displayed");
+ ok(
+ message.learnMoreLink.includes("time-errors"),
+ "time-errors in the Learn More URL"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ Services.prefs.clearUserPref(PREF_SERVICES_SETTINGS_LAST_FETCHED);
+ Services.prefs.clearUserPref(PREF_SERVICES_SETTINGS_CLOCK_SKEW_SECONDS);
+});
+
+add_task(async function checkCertError() {
+ async function setUpPage() {
+ let browser;
+ let certErrorLoaded;
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "https://expired.example.com/"
+ );
+ browser = gBrowser.selectedBrowser;
+ certErrorLoaded = BrowserTestUtils.waitForErrorPage(browser);
+
+ info("Loading and waiting for the cert error");
+ await certErrorLoaded;
+
+ return SpecialPowers.spawn(browser, [], async function () {
+ let doc = content.document;
+ let el = doc.getElementById("errorWhatToDoText");
+ await ContentTaskUtils.waitForCondition(() => el.textContent);
+ return el.textContent;
+ });
+ }
+
+ // The particular error message will be displayed only when clock_skew_seconds is
+ // less or equal to a day and the difference between date.now() and last_fetched is less than
+ // or equal to 5 days. Setting the prefs accordingly.
+
+ Services.prefs.setIntPref(
+ PREF_SERVICES_SETTINGS_LAST_FETCHED,
+ Math.floor(Date.now() / 1000)
+ );
+
+ let skew = 60 * 60 * 24;
+ Services.prefs.setIntPref(PREF_SERVICES_SETTINGS_CLOCK_SKEW_SECONDS, skew);
+
+ info("Loading a bad cert page");
+ let message = await setUpPage();
+
+ ok(
+ message.includes(
+ "The issue is most likely with the website, and there is nothing you can do" +
+ " to resolve it. You can notify the website’s administrator about the problem."
+ ),
+ "Correct error message found"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ Services.prefs.clearUserPref(PREF_SERVICES_SETTINGS_LAST_FETCHED);
+ Services.prefs.clearUserPref(PREF_SERVICES_SETTINGS_CLOCK_SKEW_SECONDS);
+});
diff --git a/browser/base/content/test/about/browser_aboutCertError_exception.js b/browser/base/content/test/about/browser_aboutCertError_exception.js
new file mode 100644
index 0000000000..7ee1bdde45
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutCertError_exception.js
@@ -0,0 +1,221 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const BAD_CERT = "https://expired.example.com/";
+const BAD_STS_CERT =
+ "https://badchain.include-subdomains.pinning.example.com:443";
+const PREF_PERMANENT_OVERRIDE = "security.certerrors.permanentOverride";
+
+add_task(async function checkExceptionDialogButton() {
+ info(
+ "Loading a bad cert page and making sure the exceptionDialogButton directly adds an exception"
+ );
+ let tab = await openErrorPage(BAD_CERT);
+ let browser = tab.linkedBrowser;
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, BAD_CERT);
+ info("Clicking the exceptionDialogButton in advanced panel");
+ await SpecialPowers.spawn(browser, [], async function () {
+ let doc = content.document;
+ let exceptionButton = doc.getElementById("exceptionDialogButton");
+ exceptionButton.click();
+ });
+
+ info("Loading the url after adding exception");
+ await loaded;
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ let doc = content.document;
+ ok(
+ !doc.documentURI.startsWith("about:certerror"),
+ "Exception has been added"
+ );
+ });
+
+ let certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+ ].getService(Ci.nsICertOverrideService);
+ certOverrideService.clearValidityOverride("expired.example.com", -1, {});
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function checkPermanentExceptionPref() {
+ info(
+ "Loading a bad cert page and making sure the permanent state of exceptions can be controlled via pref"
+ );
+
+ for (let permanentOverride of [false, true]) {
+ Services.prefs.setBoolPref(PREF_PERMANENT_OVERRIDE, permanentOverride);
+
+ let tab = await openErrorPage(BAD_CERT);
+ let browser = tab.linkedBrowser;
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, BAD_CERT);
+ info("Clicking the exceptionDialogButton in advanced panel");
+ let serverCertBytes = await SpecialPowers.spawn(
+ browser,
+ [],
+ async function () {
+ let doc = content.document;
+ let exceptionButton = doc.getElementById("exceptionDialogButton");
+ exceptionButton.click();
+ return content.docShell.failedChannel.securityInfo.serverCert.getRawDER();
+ }
+ );
+
+ info("Loading the url after adding exception");
+ await loaded;
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ let doc = content.document;
+ ok(
+ !doc.documentURI.startsWith("about:certerror"),
+ "Exception has been added"
+ );
+ });
+
+ let certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+ ].getService(Ci.nsICertOverrideService);
+
+ let isTemporary = {};
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ let cert = certdb.constructX509(serverCertBytes);
+ let hasException = certOverrideService.hasMatchingOverride(
+ "expired.example.com",
+ -1,
+ {},
+ cert,
+ isTemporary
+ );
+ ok(hasException, "Has stored an exception for the page.");
+ is(
+ isTemporary.value,
+ !permanentOverride,
+ `Has stored a ${
+ permanentOverride ? "permanent" : "temporary"
+ } exception for the page.`
+ );
+
+ certOverrideService.clearValidityOverride("expired.example.com", -1, {});
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+
+ Services.prefs.clearUserPref(PREF_PERMANENT_OVERRIDE);
+});
+
+add_task(async function checkBadStsCert() {
+ info("Loading a badStsCert and making sure exception button doesn't show up");
+
+ for (let useFrame of [false, true]) {
+ let tab = await openErrorPage(BAD_STS_CERT, useFrame);
+ let browser = tab.linkedBrowser;
+
+ await SpecialPowers.spawn(
+ browser,
+ [{ frame: useFrame }],
+ async function ({ frame }) {
+ let doc = frame
+ ? content.document.querySelector("iframe").contentDocument
+ : content.document;
+ let exceptionButton = doc.getElementById("exceptionDialogButton");
+ ok(
+ ContentTaskUtils.is_hidden(exceptionButton),
+ "Exception button is hidden."
+ );
+ }
+ );
+
+ let message = await SpecialPowers.spawn(
+ browser,
+ [{ frame: useFrame }],
+ async function ({ frame }) {
+ let doc = frame
+ ? content.document.querySelector("iframe").contentDocument
+ : content.document;
+ let advancedButton = doc.getElementById("advancedButton");
+ advancedButton.click();
+
+ // aboutNetError.mjs is using async localization to format several
+ // messages and in result the translation may be applied later.
+ // We want to return the textContent of the element only after
+ // the translation completes, so let's wait for it here.
+ let elements = [doc.getElementById("badCertTechnicalInfo")];
+ await ContentTaskUtils.waitForCondition(() => {
+ return elements.every(elem => !!elem.textContent.trim().length);
+ });
+
+ return doc.getElementById("badCertTechnicalInfo").textContent;
+ }
+ );
+ ok(
+ message.includes("SSL_ERROR_BAD_CERT_DOMAIN"),
+ "Didn't find SSL_ERROR_BAD_CERT_DOMAIN."
+ );
+ ok(
+ message.includes("The certificate is only valid for"),
+ "Didn't find error message."
+ );
+ ok(
+ message.includes("a certificate that is not valid for"),
+ "Didn't find error message."
+ );
+ ok(
+ message.includes("badchain.include-subdomains.pinning.example.com"),
+ "Didn't find domain in error message."
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+});
+
+add_task(async function checkhideAddExceptionButtonViaPref() {
+ info(
+ "Loading a bad cert page and verifying the pref security.certerror.hideAddException"
+ );
+ Services.prefs.setBoolPref("security.certerror.hideAddException", true);
+
+ for (let useFrame of [false, true]) {
+ let tab = await openErrorPage(BAD_CERT, useFrame);
+ let browser = tab.linkedBrowser;
+
+ await SpecialPowers.spawn(
+ browser,
+ [{ frame: useFrame }],
+ async function ({ frame }) {
+ let doc = frame
+ ? content.document.querySelector("iframe").contentDocument
+ : content.document;
+
+ let exceptionButton = doc.getElementById("exceptionDialogButton");
+ ok(
+ ContentTaskUtils.is_hidden(exceptionButton),
+ "Exception button is hidden."
+ );
+ }
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+
+ Services.prefs.clearUserPref("security.certerror.hideAddException");
+});
+
+add_task(async function checkhideAddExceptionButtonInFrames() {
+ info("Loading a bad cert page in a frame and verifying it's hidden.");
+ let tab = await openErrorPage(BAD_CERT, true);
+ let browser = tab.linkedBrowser;
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ let doc = content.document.querySelector("iframe").contentDocument;
+ let exceptionButton = doc.getElementById("exceptionDialogButton");
+ ok(
+ ContentTaskUtils.is_hidden(exceptionButton),
+ "Exception button is hidden."
+ );
+ });
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/about/browser_aboutCertError_mitm.js b/browser/base/content/test/about/browser_aboutCertError_mitm.js
new file mode 100644
index 0000000000..5c9b5e8144
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutCertError_mitm.js
@@ -0,0 +1,158 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const PREF_MITM_PRIMING = "security.certerrors.mitm.priming.enabled";
+const PREF_MITM_PRIMING_ENDPOINT = "security.certerrors.mitm.priming.endpoint";
+const PREF_MITM_CANARY_ISSUER = "security.pki.mitm_canary_issuer";
+const PREF_MITM_AUTO_ENABLE_ENTERPRISE_ROOTS =
+ "security.certerrors.mitm.auto_enable_enterprise_roots";
+const PREF_ENTERPRISE_ROOTS = "security.enterprise_roots.enabled";
+
+const UNKNOWN_ISSUER = "https://untrusted.example.com";
+
+// Check that basic MitM priming works and the MitM error page is displayed successfully.
+add_task(async function checkMitmPriming() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_MITM_PRIMING, true],
+ [PREF_MITM_PRIMING_ENDPOINT, UNKNOWN_ISSUER],
+ ],
+ });
+
+ let browser;
+ let certErrorLoaded;
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ () => {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, UNKNOWN_ISSUER);
+ browser = gBrowser.selectedBrowser;
+ // The page will reload by itself after the initial canary request, so we wait
+ // until the AboutNetErrorLoad event has happened twice.
+ certErrorLoaded = new Promise(resolve => {
+ let loaded = 0;
+ let removeEventListener = BrowserTestUtils.addContentEventListener(
+ browser,
+ "AboutNetErrorLoad",
+ () => {
+ if (++loaded == 2) {
+ removeEventListener();
+ resolve();
+ }
+ },
+ { capture: false, wantUntrusted: true }
+ );
+ });
+ },
+ false
+ );
+
+ await certErrorLoaded;
+
+ await SpecialPowers.spawn(browser, [], () => {
+ is(
+ content.document.body.getAttribute("code"),
+ "MOZILLA_PKIX_ERROR_MITM_DETECTED",
+ "MitM error page has loaded."
+ );
+ });
+
+ ok(true, "Successfully loaded the MitM error page.");
+
+ is(
+ Services.prefs.getStringPref(PREF_MITM_CANARY_ISSUER),
+ "CN=Unknown CA",
+ "Stored the correct issuer"
+ );
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ const shortDesc = content.document.querySelector("#errorShortDesc");
+ const whatToDo = content.document.querySelector("#errorWhatToDoText");
+
+ await ContentTaskUtils.waitForCondition(
+ () => shortDesc.textContent != "" && whatToDo.textContent != "",
+ "DOM localization has been updated"
+ );
+
+ ok(
+ shortDesc.textContent.includes("Unknown CA"),
+ "Shows the name of the issuer."
+ );
+
+ ok(
+ whatToDo.textContent.includes("Unknown CA"),
+ "Shows the name of the issuer."
+ );
+ });
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ Services.prefs.clearUserPref(PREF_MITM_CANARY_ISSUER);
+});
+
+// Check that we set the enterprise roots pref correctly on MitM
+add_task(async function checkMitmAutoEnableEnterpriseRoots() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_MITM_PRIMING, true],
+ [PREF_MITM_PRIMING_ENDPOINT, UNKNOWN_ISSUER],
+ [PREF_MITM_AUTO_ENABLE_ENTERPRISE_ROOTS, true],
+ [PREF_ENTERPRISE_ROOTS, false],
+ ],
+ });
+
+ let browser;
+ let certErrorLoaded;
+
+ let prefChanged = TestUtils.waitForPrefChange(
+ PREF_ENTERPRISE_ROOTS,
+ value => value === true
+ );
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ () => {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, UNKNOWN_ISSUER);
+ browser = gBrowser.selectedBrowser;
+ // The page will reload by itself after the initial canary request, so we wait
+ // until the AboutNetErrorLoad event has happened twice.
+ certErrorLoaded = new Promise(resolve => {
+ let loaded = 0;
+ let removeEventListener = BrowserTestUtils.addContentEventListener(
+ browser,
+ "AboutNetErrorLoad",
+ () => {
+ if (++loaded == 2) {
+ removeEventListener();
+ resolve();
+ }
+ },
+ { capture: false, wantUntrusted: true }
+ );
+ });
+ },
+ false
+ );
+
+ await certErrorLoaded;
+ await prefChanged;
+
+ await SpecialPowers.spawn(browser, [], () => {
+ is(
+ content.document.body.getAttribute("code"),
+ "MOZILLA_PKIX_ERROR_MITM_DETECTED",
+ "MitM error page has loaded."
+ );
+ });
+
+ ok(true, "Successfully loaded the MitM error page.");
+
+ ok(
+ !Services.prefs.prefHasUserValue(PREF_ENTERPRISE_ROOTS),
+ "Flipped the enterprise roots pref back"
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ Services.prefs.clearUserPref(PREF_MITM_CANARY_ISSUER);
+});
diff --git a/browser/base/content/test/about/browser_aboutCertError_noSubjectAltName.js b/browser/base/content/test/about/browser_aboutCertError_noSubjectAltName.js
new file mode 100644
index 0000000000..1a2add1c96
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutCertError_noSubjectAltName.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const BROWSER_NAME = document
+ .getElementById("bundle_brand")
+ .getString("brandShortName");
+const UNKNOWN_ISSUER = "https://no-subject-alt-name.example.com:443";
+
+const checkAdvancedAndGetTechnicalInfoText = async () => {
+ let doc = content.document;
+
+ let advancedButton = doc.getElementById("advancedButton");
+ ok(advancedButton, "advancedButton found");
+ is(
+ advancedButton.hasAttribute("disabled"),
+ false,
+ "advancedButton should be clickable"
+ );
+ advancedButton.click();
+
+ let badCertAdvancedPanel = doc.getElementById("badCertAdvancedPanel");
+ ok(badCertAdvancedPanel, "badCertAdvancedPanel found");
+
+ let badCertTechnicalInfo = doc.getElementById("badCertTechnicalInfo");
+ ok(badCertTechnicalInfo, "badCertTechnicalInfo found");
+
+ // Wait until fluent sets the errorCode inner text.
+ await ContentTaskUtils.waitForCondition(() => {
+ let errorCode = doc.getElementById("errorCode");
+ return errorCode.textContent == "SSL_ERROR_BAD_CERT_DOMAIN";
+ }, "correct error code has been set inside the advanced button panel");
+
+ let viewCertificate = doc.getElementById("viewCertificate");
+ ok(viewCertificate, "viewCertificate found");
+
+ return badCertTechnicalInfo.innerHTML;
+};
+
+const checkCorrectMessages = message => {
+ let isCorrectMessage = message.includes(
+ "Websites prove their identity via certificates. " +
+ BROWSER_NAME +
+ " does not trust this site because it uses a certificate that is" +
+ " not valid for no-subject-alt-name.example.com"
+ );
+ is(isCorrectMessage, true, "That message should appear");
+ let isWrongMessage = message.includes("The certificate is only valid for ");
+ is(isWrongMessage, false, "That message shouldn't appear");
+};
+
+add_task(async function checkUntrustedCertError() {
+ info(
+ `Loading ${UNKNOWN_ISSUER} which does not have a subject specified in the certificate`
+ );
+ let tab = await openErrorPage(UNKNOWN_ISSUER);
+ let browser = tab.linkedBrowser;
+ info("Clicking the exceptionDialogButton in advanced panel");
+ let badCertTechnicalInfoText = await SpecialPowers.spawn(
+ browser,
+ [],
+ checkAdvancedAndGetTechnicalInfoText
+ );
+ checkCorrectMessages(badCertTechnicalInfoText, browser);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/about/browser_aboutCertError_offlineSupport.js b/browser/base/content/test/about/browser_aboutCertError_offlineSupport.js
new file mode 100644
index 0000000000..5b717a683a
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutCertError_offlineSupport.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const BAD_CERT_PAGE = "https://expired.example.com";
+const DUMMY_SUPPORT_BASE_PATH = "/1/firefox/fxVersion/OSVersion/language/";
+const DUMMY_SUPPORT_URL = BAD_CERT_PAGE + DUMMY_SUPPORT_BASE_PATH;
+const OFFLINE_SUPPORT_PAGE =
+ "chrome://global/content/neterror/supportpages/time-errors.html";
+
+add_task(async function testOfflineSupportPage() {
+ // Cache the original value of app.support.baseURL pref to reset later
+ let originalBaseURL = Services.prefs.getCharPref("app.support.baseURL");
+
+ Services.prefs.setCharPref("app.support.baseURL", DUMMY_SUPPORT_URL);
+ let errorTab = await openErrorPage(BAD_CERT_PAGE);
+
+ let offlineSupportPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ DUMMY_SUPPORT_URL + "time-errors"
+ );
+ await SpecialPowers.spawn(
+ errorTab.linkedBrowser,
+ [DUMMY_SUPPORT_URL],
+ async expectedURL => {
+ let doc = content.document;
+
+ let learnMoreLink = doc.getElementById("learnMoreLink");
+ let supportPageURL = learnMoreLink.getAttribute("href");
+ ok(
+ supportPageURL == expectedURL + "time-errors",
+ "Correct support page URL has been set"
+ );
+ await EventUtils.synthesizeMouseAtCenter(learnMoreLink, {}, content);
+ }
+ );
+ let offlineSupportTab = await offlineSupportPromise;
+ await BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ OFFLINE_SUPPORT_PAGE
+ );
+
+ // Reset this pref instead of clearing it to maintain globally set
+ // custom value for testing purposes.
+ Services.prefs.setCharPref("app.support.baseURL", originalBaseURL);
+
+ await BrowserTestUtils.removeTab(offlineSupportTab);
+ await BrowserTestUtils.removeTab(errorTab);
+});
diff --git a/browser/base/content/test/about/browser_aboutCertError_telemetry.js b/browser/base/content/test/about/browser_aboutCertError_telemetry.js
new file mode 100644
index 0000000000..61ec8afcbf
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutCertError_telemetry.js
@@ -0,0 +1,164 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+const BAD_CERT = "https://expired.example.com/";
+const BAD_STS_CERT =
+ "https://badchain.include-subdomains.pinning.example.com:443";
+
+add_task(async function checkTelemetryClickEvents() {
+ info("Loading a bad cert page and verifying telemetry click events arrive.");
+
+ let oldCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+
+ registerCleanupFunction(() => {
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ });
+
+ // For obvious reasons event telemetry in the content processes updates with
+ // the main processs asynchronously, so we need to wait for the main process
+ // to catch up through the entire test.
+
+ // There's an arbitrary interval of 2 seconds in which the content
+ // processes sync their event data with the parent process, we wait
+ // this out to ensure that we clear everything that is left over from
+ // previous tests and don't receive random events in the middle of our tests.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(c => setTimeout(c, 2000));
+
+ // Clear everything.
+ Services.telemetry.clearEvents();
+ await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ return !events || !events.length;
+ });
+
+ // Now enable recording our telemetry. Even if this is disabled, content
+ // processes will send event telemetry to the parent, thus we needed to ensure
+ // we waited and cleared first. Sigh.
+ Services.telemetry.setEventRecordingEnabled("security.ui.certerror", true);
+
+ for (let useFrame of [false, true]) {
+ let recordedObjects = [
+ "advanced_button",
+ "learn_more_link",
+ "error_code_link",
+ "clipboard_button_top",
+ "clipboard_button_bot",
+ "return_button_top",
+ ];
+
+ recordedObjects.push("return_button_adv");
+ if (!useFrame) {
+ recordedObjects.push("exception_button");
+ }
+
+ for (let object of recordedObjects) {
+ let tab = await openErrorPage(BAD_CERT, useFrame);
+ let browser = tab.linkedBrowser;
+
+ let loadEvents = await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ if (events && events.length) {
+ events = events.filter(
+ e => e[1] == "security.ui.certerror" && e[2] == "load"
+ );
+ if (
+ events.length == 1 &&
+ events[0][5].is_frame == useFrame.toString()
+ ) {
+ return events;
+ }
+ }
+ return null;
+ }, "recorded telemetry for the load");
+
+ is(
+ loadEvents.length,
+ 1,
+ `recorded telemetry for the load testing ${object}, useFrame: ${useFrame}`
+ );
+
+ let bc = browser.browsingContext;
+ if (useFrame) {
+ bc = bc.children[0];
+ }
+
+ await SpecialPowers.spawn(bc, [object], async function (objectId) {
+ let doc = content.document;
+
+ await ContentTaskUtils.waitForCondition(
+ () => doc.body.classList.contains("certerror"),
+ "Wait for certerror to be loaded"
+ );
+
+ let domElement = doc.querySelector(`[data-telemetry-id='${objectId}']`);
+ domElement.click();
+ });
+
+ let clickEvents = await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ if (events && events.length) {
+ events = events.filter(
+ e =>
+ e[1] == "security.ui.certerror" &&
+ e[2] == "click" &&
+ e[3] == object
+ );
+ if (
+ events.length == 1 &&
+ events[0][5].is_frame == useFrame.toString()
+ ) {
+ return events;
+ }
+ }
+ return null;
+ }, "Has captured telemetry events.");
+
+ is(
+ clickEvents.length,
+ 1,
+ `recorded telemetry for the click on ${object}, useFrame: ${useFrame}`
+ );
+
+ // We opened an extra tab for the SUMO page, need to close it.
+ if (object == "learn_more_link") {
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+
+ if (object == "exception_button") {
+ let certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+ ].getService(Ci.nsICertOverrideService);
+ certOverrideService.clearValidityOverride(
+ "expired.example.com",
+ -1,
+ {}
+ );
+ }
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+ }
+
+ let enableCertErrorUITelemetry = Services.prefs.getBoolPref(
+ "security.certerrors.recordEventTelemetry"
+ );
+ Services.telemetry.setEventRecordingEnabled(
+ "security.ui.certerror",
+ enableCertErrorUITelemetry
+ );
+});
diff --git a/browser/base/content/test/about/browser_aboutDialog_distribution.js b/browser/base/content/test/about/browser_aboutDialog_distribution.js
new file mode 100644
index 0000000000..8f52533bbc
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutDialog_distribution.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/mozapps/update/tests/browser/head.js",
+ this
+);
+
+add_task(async function verify_distribution_info_hides() {
+ let defaultBranch = Services.prefs.getDefaultBranch(null);
+
+ defaultBranch.setCharPref("distribution.id", "mozilla-test-distribution-id");
+ defaultBranch.setCharPref("distribution.version", "1.0");
+
+ let aboutDialog = await waitForAboutDialog();
+
+ let distroIdField = aboutDialog.document.getElementById("distributionId");
+ let distroField = aboutDialog.document.getElementById("distribution");
+
+ if (
+ AppConstants.platform === "win" &&
+ Services.sysinfo.getProperty("hasWinPackageId")
+ ) {
+ is(distroIdField.value, "mozilla-test-distribution-id - 1.0");
+ is(distroIdField.style.display, "block");
+ is(distroField.style.display, "block");
+ } else {
+ is(distroIdField.value, "");
+ isnot(distroIdField.style.display, "block");
+ isnot(distroField.style.display, "block");
+ }
+
+ aboutDialog.close();
+});
+
+add_task(async function verify_distribution_info_displays() {
+ let defaultBranch = Services.prefs.getDefaultBranch(null);
+
+ defaultBranch.setCharPref("distribution.id", "test-distribution-id");
+ defaultBranch.setCharPref("distribution.version", "1.0");
+ defaultBranch.setCharPref("distribution.about", "About Text");
+
+ let aboutDialog = await waitForAboutDialog();
+
+ let distroIdField = aboutDialog.document.getElementById("distributionId");
+
+ is(distroIdField.value, "test-distribution-id - 1.0");
+ is(distroIdField.style.display, "block");
+
+ let distroField = aboutDialog.document.getElementById("distribution");
+ is(distroField.value, "About Text");
+ is(distroField.style.display, "block");
+
+ aboutDialog.close();
+});
+
+add_task(async function cleanup() {
+ let defaultBranch = Services.prefs.getDefaultBranch(null);
+
+ // This is the best we can do since we can't remove default prefs
+ defaultBranch.setCharPref("distribution.id", "");
+ defaultBranch.setCharPref("distribution.version", "");
+ defaultBranch.setCharPref("distribution.about", "");
+});
diff --git a/browser/base/content/test/about/browser_aboutHome_search_POST.js b/browser/base/content/test/about/browser_aboutHome_search_POST.js
new file mode 100644
index 0000000000..c892198207
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutHome_search_POST.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+ignoreAllUncaughtExceptions();
+
+add_task(async function () {
+ info("Check POST search engine support");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar",
+ false,
+ ],
+ ],
+ });
+
+ let currEngine = await Services.search.getDefault();
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:home" },
+ async browser => {
+ let observerPromise = new Promise(resolve => {
+ let searchObserver = async function search_observer(
+ subject,
+ topic,
+ data
+ ) {
+ let engine = subject.QueryInterface(Ci.nsISearchEngine);
+ info("Observer: " + data + " for " + engine.name);
+
+ if (data != "engine-added") {
+ return;
+ }
+
+ if (engine.name != "POST Search") {
+ return;
+ }
+
+ Services.obs.removeObserver(
+ searchObserver,
+ "browser-search-engine-modified"
+ );
+
+ resolve(engine);
+ };
+
+ Services.obs.addObserver(
+ searchObserver,
+ "browser-search-engine-modified"
+ );
+ });
+
+ let engine;
+ await promiseContentSearchChange(browser, async () => {
+ Services.search.addOpenSearchEngine(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://test:80/browser/browser/base/content/test/about/POSTSearchEngine.xml",
+ null
+ );
+
+ engine = await observerPromise;
+ Services.search.setDefault(
+ engine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ return engine.name;
+ });
+
+ // Ready to execute the tests!
+ let needle = "Search for something awesome.";
+
+ let promise = BrowserTestUtils.browserLoaded(browser);
+ await SpecialPowers.spawn(browser, [{ needle }], async function (args) {
+ let doc = content.document;
+ let el = doc.querySelector(["#searchText", "#newtab-search-text"]);
+ el.value = args.needle;
+ doc.getElementById("searchSubmit").click();
+ });
+
+ await promise;
+
+ // When the search results load, check them for correctness.
+ await SpecialPowers.spawn(browser, [{ needle }], async function (args) {
+ let loadedText = content.document.body.textContent;
+ ok(loadedText, "search page loaded");
+ is(
+ loadedText,
+ "searchterms=" + escape(args.needle.replace(/\s/g, "+")),
+ "Search text should arrive correctly"
+ );
+ });
+
+ await Services.search.setDefault(
+ currEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ try {
+ await Services.search.removeEngine(engine);
+ } catch (ex) {}
+ }
+ );
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/base/content/test/about/browser_aboutHome_search_composing.js b/browser/base/content/test/about/browser_aboutHome_search_composing.js
new file mode 100644
index 0000000000..309f1f674a
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutHome_search_composing.js
@@ -0,0 +1,110 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+ignoreAllUncaughtExceptions();
+
+add_task(async function () {
+ info("Clicking suggestion list while composing");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar",
+ false,
+ ],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:home" },
+ async function (browser) {
+ // Add a test engine that provides suggestions and switch to it.
+ let engine;
+ await promiseContentSearchChange(browser, async () => {
+ engine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "searchSuggestionEngine.xml",
+ setAsDefault: true,
+ });
+ return engine.name;
+ });
+
+ // Clear any search history results
+ await FormHistory.update({ op: "remove" });
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ // Start composition and type "x"
+ let input = content.document.querySelector([
+ "#searchText",
+ "#newtab-search-text",
+ ]);
+ input.focus();
+ });
+
+ info("Setting up the mutation observer before synthesizing composition");
+ let mutationPromise = SpecialPowers.spawn(browser, [], async function () {
+ let searchController = content.wrappedJSObject.gContentSearchController;
+
+ // Wait for the search suggestions to become visible.
+ let table = searchController._suggestionsList;
+ let input = content.document.querySelector([
+ "#searchText",
+ "#newtab-search-text",
+ ]);
+
+ await ContentTaskUtils.waitForMutationCondition(
+ input,
+ { attributeFilter: ["aria-expanded"] },
+ () => input.getAttribute("aria-expanded") == "true"
+ );
+ ok(!table.hidden, "Search suggestion table unhidden");
+
+ let row = table.children[1];
+ row.setAttribute("id", "TEMPID");
+
+ // ContentSearchUIController looks at the current selectedIndex when
+ // performing a search. Synthesizing the mouse event on the suggestion
+ // doesn't actually mouseover the suggestion and trigger it to be flagged
+ // as selected, so we manually select it first.
+ searchController.selectedIndex = 1;
+ });
+
+ // FYI: "compositionstart" will be dispatched automatically.
+ await BrowserTestUtils.synthesizeCompositionChange(
+ {
+ composition: {
+ string: "x",
+ clauses: [
+ { length: 1, attr: Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE },
+ ],
+ },
+ caret: { start: 1, length: 0 },
+ },
+ browser
+ );
+
+ info("Waiting for search suggestion table unhidden");
+ await mutationPromise;
+
+ // Click the second suggestion.
+ let expectedURL = (await Services.search.getDefault()).getSubmission(
+ "xbar",
+ null,
+ "homepage"
+ ).uri.spec;
+ let loadPromise = BrowserTestUtils.waitForDocLoadAndStopIt(
+ expectedURL,
+ gBrowser.selectedBrowser
+ );
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#TEMPID",
+ {
+ button: 0,
+ },
+ browser
+ );
+ await loadPromise;
+ }
+ );
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/base/content/test/about/browser_aboutHome_search_searchbar.js b/browser/base/content/test/about/browser_aboutHome_search_searchbar.js
new file mode 100644
index 0000000000..7b08d2ae34
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutHome_search_searchbar.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const { CustomizableUITestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CustomizableUITestUtils.sys.mjs"
+);
+let gCUITestUtils = new CustomizableUITestUtils(window);
+
+ignoreAllUncaughtExceptions();
+
+add_task(async function test_setup() {
+ await gCUITestUtils.addSearchBar();
+ registerCleanupFunction(() => {
+ gCUITestUtils.removeSearchBar();
+ });
+});
+
+add_task(async function () {
+ info("Cmd+k should focus the search box in the toolbar when it's present");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:home" },
+ async function (browser) {
+ await BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser);
+
+ let doc = window.document;
+ let searchInput = BrowserSearch.searchBar.textbox;
+ isnot(
+ searchInput,
+ doc.activeElement,
+ "Search bar should not be the active element."
+ );
+
+ EventUtils.synthesizeKey("k", { accelKey: true });
+ await TestUtils.waitForCondition(() => doc.activeElement === searchInput);
+ is(
+ searchInput,
+ doc.activeElement,
+ "Search bar should be the active element."
+ );
+ }
+ );
+});
diff --git a/browser/base/content/test/about/browser_aboutHome_search_suggestion.js b/browser/base/content/test/about/browser_aboutHome_search_suggestion.js
new file mode 100644
index 0000000000..4e1da9fe3e
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutHome_search_suggestion.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+ignoreAllUncaughtExceptions();
+
+add_task(async function () {
+ // See browser_contentSearchUI.js for comprehensive content search UI tests.
+ info("Search suggestion smoke test");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar",
+ false,
+ ],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:home" },
+ async function (browser) {
+ // Add a test engine that provides suggestions and switch to it.
+ let engine;
+ await promiseContentSearchChange(browser, async () => {
+ engine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "searchSuggestionEngine.xml",
+ setAsDefault: true,
+ });
+ await Services.search.setDefault(
+ engine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ return engine.name;
+ });
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ // Type an X in the search input.
+ let input = content.document.querySelector([
+ "#searchText",
+ "#newtab-search-text",
+ ]);
+ input.focus();
+ });
+
+ await BrowserTestUtils.synthesizeKey("x", {}, browser);
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ // Wait for the search suggestions to become visible.
+ let table = content.document.getElementById("searchSuggestionTable");
+ let input = content.document.querySelector([
+ "#searchText",
+ "#newtab-search-text",
+ ]);
+
+ await ContentTaskUtils.waitForMutationCondition(
+ input,
+ { attributeFilter: ["aria-expanded"] },
+ () => input.getAttribute("aria-expanded") == "true"
+ );
+ ok(!table.hidden, "Search suggestion table unhidden");
+ });
+
+ // Empty the search input, causing the suggestions to be hidden.
+ await BrowserTestUtils.synthesizeKey("a", { accelKey: true }, browser);
+ await BrowserTestUtils.synthesizeKey("VK_DELETE", {}, browser);
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ let table = content.document.getElementById("searchSuggestionTable");
+ await ContentTaskUtils.waitForCondition(
+ () => table.hidden,
+ "Search suggestion table hidden"
+ );
+ });
+ }
+ );
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/base/content/test/about/browser_aboutHome_search_telemetry.js b/browser/base/content/test/about/browser_aboutHome_search_telemetry.js
new file mode 100644
index 0000000000..e23d07aa38
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutHome_search_telemetry.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+ignoreAllUncaughtExceptions();
+
+add_task(async function () {
+ info(
+ "Check that performing a search fires a search event and records to Telemetry."
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar",
+ false,
+ ],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:home" },
+ async function (browser) {
+ let engine;
+ await promiseContentSearchChange(browser, async () => {
+ engine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "searchSuggestionEngine.xml",
+ setAsDefault: true,
+ });
+ return engine.name;
+ });
+
+ await SpecialPowers.spawn(
+ browser,
+ [{ expectedName: engine.name }],
+ async function (args) {
+ let engineName =
+ content.wrappedJSObject.gContentSearchController.defaultEngine.name;
+ is(
+ engineName,
+ args.expectedName,
+ "Engine name in DOM should match engine we just added"
+ );
+ }
+ );
+
+ let numSearchesBefore = 0;
+ // Get the current number of recorded searches.
+ let histogramKey = `other-${engine.name}.abouthome`;
+ try {
+ let hs = Services.telemetry
+ .getKeyedHistogramById("SEARCH_COUNTS")
+ .snapshot();
+ if (histogramKey in hs) {
+ numSearchesBefore = hs[histogramKey].sum;
+ }
+ } catch (ex) {
+ // No searches performed yet, not a problem, |numSearchesBefore| is 0.
+ }
+
+ let searchStr = "a search";
+
+ let expectedURL = (await Services.search.getDefault()).getSubmission(
+ searchStr,
+ null,
+ "homepage"
+ ).uri.spec;
+ let promise = BrowserTestUtils.waitForDocLoadAndStopIt(
+ expectedURL,
+ browser
+ );
+
+ // Perform a search to increase the SEARCH_COUNT histogram.
+ await SpecialPowers.spawn(
+ browser,
+ [{ searchStr }],
+ async function (args) {
+ let doc = content.document;
+ info("Perform a search.");
+ let el = doc.querySelector(["#searchText", "#newtab-search-text"]);
+ el.value = args.searchStr;
+ doc.getElementById("searchSubmit").click();
+ }
+ );
+
+ await promise;
+
+ // Make sure the SEARCH_COUNTS histogram has the right key and count.
+ let hs = Services.telemetry
+ .getKeyedHistogramById("SEARCH_COUNTS")
+ .snapshot();
+ Assert.ok(histogramKey in hs, "histogram with key should be recorded");
+ Assert.equal(
+ hs[histogramKey].sum,
+ numSearchesBefore + 1,
+ "histogram sum should be incremented"
+ );
+ }
+ );
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/base/content/test/about/browser_aboutNetError.js b/browser/base/content/test/about/browser_aboutNetError.js
new file mode 100644
index 0000000000..0f98413f33
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutNetError.js
@@ -0,0 +1,245 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const SSL3_PAGE = "https://ssl3.example.com/";
+const TLS10_PAGE = "https://tls1.example.com/";
+const TLS12_PAGE = "https://tls12.example.com/";
+const TRIPLEDES_PAGE = "https://3des.example.com/";
+
+const lazy = {};
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "gDNSOverride",
+ "@mozilla.org/network/native-dns-override;1",
+ "nsINativeDNSResolverOverride"
+);
+
+// This includes all the cipher suite prefs we have.
+function resetPrefs() {
+ Services.prefs.clearUserPref("security.tls.version.min");
+ Services.prefs.clearUserPref("security.tls.version.max");
+ Services.prefs.clearUserPref("security.tls.version.enable-deprecated");
+ Services.prefs.clearUserPref("browser.fixup.alternate.enabled");
+}
+
+add_task(async function resetToDefaultConfig() {
+ info(
+ "Change TLS config to cause page load to fail, check that reset button is shown and that it works"
+ );
+
+ // Set ourselves up for a TLS error.
+ Services.prefs.setIntPref("security.tls.version.min", 1); // TLS 1.0
+ Services.prefs.setIntPref("security.tls.version.max", 1);
+
+ let browser;
+ let pageLoaded;
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ () => {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, TLS12_PAGE);
+ browser = gBrowser.selectedBrowser;
+ pageLoaded = BrowserTestUtils.waitForErrorPage(browser);
+ },
+ false
+ );
+
+ info("Loading and waiting for the net error");
+ await pageLoaded;
+
+ // Setup an observer for the target page.
+ const finalLoadComplete = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ TLS12_PAGE
+ );
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ const doc = content.document;
+ ok(
+ doc.documentURI.startsWith("about:neterror"),
+ "Should be showing error page"
+ );
+
+ const prefResetButton = doc.getElementById("prefResetButton");
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.is_visible(prefResetButton),
+ "prefResetButton is visible"
+ );
+
+ if (!Services.focus.focusedElement == prefResetButton) {
+ await ContentTaskUtils.waitForEvent(prefResetButton, "focus");
+ }
+
+ Assert.ok(true, "prefResetButton has focus");
+
+ prefResetButton.click();
+ });
+
+ info("Waiting for the page to load after the click");
+ await finalLoadComplete;
+
+ resetPrefs();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function checkLearnMoreLink() {
+ info("Load an unsupported TLS page and check for a learn more link");
+
+ // Set ourselves up for TLS error
+ Services.prefs.setIntPref("security.tls.version.min", 3);
+ Services.prefs.setIntPref("security.tls.version.max", 4);
+
+ let browser;
+ let pageLoaded;
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ () => {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, TLS10_PAGE);
+ browser = gBrowser.selectedBrowser;
+ pageLoaded = BrowserTestUtils.waitForErrorPage(browser);
+ },
+ false
+ );
+
+ info("Loading and waiting for the net error");
+ await pageLoaded;
+
+ const baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+
+ await SpecialPowers.spawn(browser, [baseURL], function (_baseURL) {
+ const doc = content.document;
+ ok(
+ doc.documentURI.startsWith("about:neterror"),
+ "Should be showing error page"
+ );
+
+ const tlsVersionNotice = doc.getElementById("tlsVersionNotice");
+ ok(
+ ContentTaskUtils.is_visible(tlsVersionNotice),
+ "TLS version notice is visible"
+ );
+
+ const learnMoreLink = doc.getElementById("learnMoreLink");
+ ok(
+ ContentTaskUtils.is_visible(learnMoreLink),
+ "Learn More link is visible"
+ );
+ is(learnMoreLink.getAttribute("href"), _baseURL + "connection-not-secure");
+
+ const titleEl = doc.querySelector(".title-text");
+ const actualDataL10nID = titleEl.getAttribute("data-l10n-id");
+ is(
+ actualDataL10nID,
+ "nssFailure2-title",
+ "Correct error page title is set"
+ );
+
+ const errorCodeEl = doc.querySelector("#errorShortDesc2");
+ const actualDataL10Args = errorCodeEl.getAttribute("data-l10n-args");
+ ok(
+ actualDataL10Args.includes("SSL_ERROR_PROTOCOL_VERSION_ALERT"),
+ "Correct error code is set"
+ );
+ });
+
+ resetPrefs();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// When a user tries going to a host without a suffix
+// and the term doesn't match a host and we are able to suggest a
+// valid correction, the page should show the correction.
+// e.g. http://example/example2 -> https://www.example.com/example2
+add_task(async function checkDomainCorrection() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.fixup.alternate.enabled", false]],
+ });
+ lazy.gDNSOverride.addIPOverride("www.example.com", "::1");
+
+ info("Try loading a URI that should result in an error page");
+ BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example/example2/",
+ false
+ );
+
+ info("Loading and waiting for the net error");
+ let browser = gBrowser.selectedBrowser;
+ let pageLoaded = BrowserTestUtils.waitForErrorPage(browser);
+ await pageLoaded;
+
+ const baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+
+ await SpecialPowers.spawn(browser, [baseURL], async function (_baseURL) {
+ const doc = content.document;
+ ok(
+ doc.documentURI.startsWith("about:neterror"),
+ "Should be showing error page"
+ );
+
+ const errorNotice = doc.getElementById("errorShortDesc");
+ ok(ContentTaskUtils.is_visible(errorNotice), "Error text is visible");
+
+ // Wait for the domain suggestion to be resolved and for the text to update
+ let link;
+ await ContentTaskUtils.waitForCondition(() => {
+ link = errorNotice.querySelector("a");
+ return link && link.textContent != "";
+ }, "Helper link has been set");
+
+ is(
+ link.getAttribute("href"),
+ "https://www.example.com/example2/",
+ "Link was corrected"
+ );
+
+ const actualDataL10nID = link.getAttribute("data-l10n-name");
+ is(actualDataL10nID, "website", "Correct name is set");
+ });
+
+ lazy.gDNSOverride.clearHostOverride("www.example.com");
+ resetPrefs();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Test that ciphersuites that use 3DES (namely, TLS_RSA_WITH_3DES_EDE_CBC_SHA)
+// can only be enabled when deprecated TLS is enabled.
+add_task(async function onlyAllow3DESWithDeprecatedTLS() {
+ // By default, connecting to a server that only uses 3DES should fail.
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async browser => {
+ BrowserTestUtils.loadURIString(browser, TRIPLEDES_PAGE);
+ await BrowserTestUtils.waitForErrorPage(browser);
+ }
+ );
+
+ // Enabling deprecated TLS should also enable 3DES.
+ Services.prefs.setBoolPref("security.tls.version.enable-deprecated", true);
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async browser => {
+ BrowserTestUtils.loadURIString(browser, TRIPLEDES_PAGE);
+ await BrowserTestUtils.browserLoaded(browser, false, TRIPLEDES_PAGE);
+ }
+ );
+
+ // 3DES can be disabled separately.
+ Services.prefs.setBoolPref(
+ "security.ssl3.deprecated.rsa_des_ede3_sha",
+ false
+ );
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async browser => {
+ BrowserTestUtils.loadURIString(browser, TRIPLEDES_PAGE);
+ await BrowserTestUtils.waitForErrorPage(browser);
+ }
+ );
+
+ resetPrefs();
+});
diff --git a/browser/base/content/test/about/browser_aboutNetError_csp_iframe.js b/browser/base/content/test/about/browser_aboutNetError_csp_iframe.js
new file mode 100644
index 0000000000..21e2ba7b51
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutNetError_csp_iframe.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const BLOCKED_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.org:8000/browser/browser/base/content/test/about/csp_iframe.sjs";
+
+add_task(async function test_csp() {
+ let { iframePageTab, blockedPageTab } = await setupPage(
+ "iframe_page_csp.html",
+ BLOCKED_PAGE
+ );
+
+ let cspBrowser = gBrowser.selectedTab.linkedBrowser;
+
+ // The blocked page opened in a new window/tab
+ await SpecialPowers.spawn(
+ cspBrowser,
+ [BLOCKED_PAGE],
+ async function (cspBlockedPage) {
+ let cookieHeader = content.document.getElementById("strictCookie");
+ let location = content.document.location.href;
+
+ Assert.ok(
+ cookieHeader.textContent.includes("No same site strict cookie header"),
+ "Same site strict cookie has not been set"
+ );
+ Assert.equal(
+ location,
+ cspBlockedPage,
+ "Location of new page is correct!"
+ );
+ }
+ );
+
+ Services.cookies.removeAll();
+ BrowserTestUtils.removeTab(iframePageTab);
+ BrowserTestUtils.removeTab(blockedPageTab);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+async function setupPage(htmlPageName, blockedPage) {
+ let iFramePage =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+ ) + htmlPageName;
+
+ // Opening the blocked page once in a new tab
+ let blockedPageTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ blockedPage
+ );
+ let blockedPageBrowser = blockedPageTab.linkedBrowser;
+
+ let cookies = Services.cookies.getCookiesFromHost(
+ "example.org",
+ blockedPageBrowser.contentPrincipal.originAttributes
+ );
+ let strictCookie = cookies[0];
+
+ is(
+ strictCookie.value,
+ "green",
+ "Same site strict cookie has the expected value"
+ );
+
+ is(strictCookie.sameSite, 2, "The cookie is a same site strict cookie");
+
+ // Opening the page that contains the iframe
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ let browser = tab.linkedBrowser;
+ let browserLoaded = BrowserTestUtils.browserLoaded(
+ browser,
+ true,
+ blockedPage,
+ true
+ );
+
+ BrowserTestUtils.loadURIString(browser, iFramePage);
+ await browserLoaded;
+ info("The error page has loaded!");
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ let iframe = content.document.getElementById("theIframe");
+
+ await ContentTaskUtils.waitForCondition(() =>
+ SpecialPowers.spawn(iframe, [], () =>
+ content.document.body.classList.contains("neterror")
+ )
+ );
+ });
+
+ let iframe = browser.browsingContext.children[0];
+
+ let newTabLoaded = BrowserTestUtils.waitForNewTab(gBrowser, null, true);
+
+ // In the iframe, we see the correct error page and click on the button
+ // to open the blocked page in a new window/tab
+ await SpecialPowers.spawn(iframe, [], async function () {
+ let doc = content.document;
+
+ // aboutNetError.mjs is using async localization to format several
+ // messages and in result the translation may be applied later.
+ // We want to return the textContent of the element only after
+ // the translation completes, so let's wait for it here.
+ let elements = [
+ doc.getElementById("errorLongDesc"),
+ doc.getElementById("openInNewWindowButton"),
+ ];
+ await ContentTaskUtils.waitForCondition(() => {
+ return elements.every(elem => !!elem.textContent.trim().length);
+ });
+
+ let textLongDescription = doc.getElementById("errorLongDesc").textContent;
+ let learnMoreLinkLocation = doc.getElementById("learnMoreLink").href;
+
+ Assert.ok(
+ textLongDescription.includes(
+ "To see this page, you need to open it in a new window."
+ ),
+ "Correct error message found"
+ );
+
+ let button = doc.getElementById("openInNewWindowButton");
+ Assert.ok(
+ button.textContent.includes("Open Site in New Window"),
+ "We see the correct button to open the site in a new window"
+ );
+
+ Assert.ok(
+ learnMoreLinkLocation.includes("xframe-neterror-page"),
+ "Correct Learn More URL for CSP error page"
+ );
+
+ // We click on the button
+ await EventUtils.synthesizeMouseAtCenter(button, {}, content);
+ });
+ info("Button was clicked!");
+
+ // We wait for the new tab to load
+ await newTabLoaded;
+ info("The new tab has loaded!");
+
+ let iframePageTab = tab;
+ return {
+ iframePageTab,
+ blockedPageTab,
+ };
+}
diff --git a/browser/base/content/test/about/browser_aboutNetError_native_fallback.js b/browser/base/content/test/about/browser_aboutNetError_native_fallback.js
new file mode 100644
index 0000000000..4a87ad5cce
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutNetError_native_fallback.js
@@ -0,0 +1,174 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let oldProxyType = Services.prefs.getIntPref("network.proxy.type");
+
+function reset() {
+ Services.prefs.clearUserPref("network.trr.display_fallback_warning");
+ Services.prefs.clearUserPref("network.trr.mode");
+ Services.prefs.clearUserPref("network.dns.native-is-localhost");
+ Services.prefs.clearUserPref("doh-rollout.disable-heuristics");
+ Services.prefs.setIntPref("network.proxy.type", oldProxyType);
+ Services.prefs.clearUserPref("network.trr.uri");
+
+ Services.dns.setHeuristicDetectionResult(Ci.nsITRRSkipReason.TRR_OK);
+}
+
+// This helper verifies that the given url loads correctly
+async function verifyLoad(url, testName) {
+ let browser;
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ () => {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url);
+ browser = gBrowser.selectedBrowser;
+ },
+ true
+ );
+
+ await SpecialPowers.spawn(browser, [{ url, testName }], function (args) {
+ const doc = content.document;
+ ok(
+ doc.documentURI == args.url,
+ "Should have loaded page: " + args.testName
+ );
+ });
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+}
+
+// This helper verifies that loading the given url will lead to an error -- the fallback warning if the parameter is true
+async function verifyError(url, fallbackWarning, testName) {
+ // Clear everything.
+ Services.telemetry.clearEvents();
+ await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ return !events || !events.length;
+ });
+ Services.telemetry.setEventRecordingEnabled("security.doh.neterror", true);
+
+ let browser;
+ let pageLoaded;
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ () => {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url);
+ browser = gBrowser.selectedBrowser;
+ pageLoaded = BrowserTestUtils.waitForErrorPage(browser);
+ },
+ false
+ );
+
+ info("Loading and waiting for the net error");
+ await pageLoaded;
+
+ await SpecialPowers.spawn(
+ browser,
+ [{ url, fallbackWarning, testName }],
+ function (args) {
+ const doc = content.document;
+
+ ok(doc.documentURI.startsWith("about:neterror"));
+ "Should be showing error page: " + args.testName;
+
+ const titleEl = doc.querySelector(".title-text");
+ const actualDataL10nID = titleEl.getAttribute("data-l10n-id");
+ if (args.fallbackWarning) {
+ is(
+ actualDataL10nID,
+ "dns-not-found-native-fallback-title2",
+ "Correct fallback warning error page title is set: " + args.testName
+ );
+ } else {
+ ok(
+ actualDataL10nID != "dns-not-found-native-fallback-title2",
+ "Should not show fallback warning: " + args.testName
+ );
+ }
+ }
+ );
+
+ if (fallbackWarning) {
+ let loadEvent = await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ return events?.find(
+ e => e[1] == "security.doh.neterror" && e[2] == "load"
+ );
+ }, "recorded telemetry for the load");
+ loadEvent.shift();
+ Assert.deepEqual(loadEvent, [
+ "security.doh.neterror",
+ "load",
+ "dohwarning",
+ "NativeFallbackWarning",
+ {
+ mode: "0",
+ provider_key: "0.0.0.0",
+ skip_reason: "TRR_HEURISTIC_TRIPPED_CANARY",
+ },
+ ]);
+ }
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+}
+
+// This test verifies that the native fallback warning appears in the desired scenarios, and only in those scenarios
+add_task(async function nativeFallbackWarnings() {
+ Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
+
+ // Disable heuristics since they will attempt to connect to external servers
+ Services.prefs.setBoolPref("doh-rollout.disable-heuristics", true);
+
+ // Set a local TRR to prevent external connections
+ Services.prefs.setCharPref("network.trr.uri", "https://0.0.0.0/dns-query");
+
+ registerCleanupFunction(reset);
+
+ // Test without DoH
+ Services.prefs.setIntPref(
+ "network.trr.mode",
+ Ci.nsIDNSService.MODE_NATIVEONLY
+ );
+
+ Services.dns.clearCache(true);
+ await verifyLoad("https://www.example.com/", "valid url, no error");
+
+ // Should not trigger the native fallback warning
+ await verifyError("https://does-not-exist.test", false, "non existent url");
+
+ // We need to disable proxy, otherwise TRR isn't used for name resolution.
+ Services.prefs.setIntPref("network.proxy.type", 0);
+
+ // Switch to TRR first
+ Services.prefs.setIntPref(
+ "network.trr.mode",
+ Ci.nsIDNSService.MODE_NATIVEONLY
+ );
+ Services.prefs.setBoolPref("network.trr.display_fallback_warning", true);
+
+ // Simulate a tripped canary network
+ Services.dns.setHeuristicDetectionResult(
+ Ci.nsITRRSkipReason.TRR_HEURISTIC_TRIPPED_CANARY
+ );
+
+ // We should see the fallback warning displayed in both of these scenarios
+ Services.dns.clearCache(true);
+ await verifyError(
+ "https://www.example.com",
+ true,
+ "canary heuristic tripped"
+ );
+ await verifyError(
+ "https://does-not-exist.test",
+ true,
+ "canary heuristic tripped - non existent url"
+ );
+
+ reset();
+});
diff --git a/browser/base/content/test/about/browser_aboutNetError_trr.js b/browser/base/content/test/about/browser_aboutNetError_trr.js
new file mode 100644
index 0000000000..bfee686e7c
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutNetError_trr.js
@@ -0,0 +1,189 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// See bug 1831731. This test should not actually try to create a connection to
+// the real DoH endpoint. But that may happen when clearing the proxy type, and
+// sometimes even in the next test.
+// To prevent that we override the IP to a local address.
+Cc["@mozilla.org/network/native-dns-override;1"]
+ .getService(Ci.nsINativeDNSResolverOverride)
+ .addIPOverride("mozilla.cloudflare-dns.com", "127.0.0.1");
+
+let oldProxyType = Services.prefs.getIntPref("network.proxy.type");
+function resetPrefs() {
+ Services.prefs.clearUserPref("network.trr.mode");
+ Services.prefs.clearUserPref("network.dns.native-is-localhost");
+ Services.prefs.setIntPref("network.proxy.type", oldProxyType);
+}
+
+async function loadErrorPage() {
+ Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
+ // We need to disable proxy, otherwise TRR isn't used for name resolution.
+ Services.prefs.setIntPref("network.proxy.type", 0);
+ registerCleanupFunction(resetPrefs);
+
+ let browser;
+ let pageLoaded;
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ () => {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "https://does-not-exist.test"
+ );
+ browser = gBrowser.selectedBrowser;
+ pageLoaded = BrowserTestUtils.waitForErrorPage(browser);
+ },
+ false
+ );
+
+ info("Loading and waiting for the net error");
+ await pageLoaded;
+ return browser;
+}
+
+// This test makes sure that the Add exception button only shows up
+// when the skipReason indicates that the domain could not be resolved.
+// If instead there is a problem with the TRR connection, then we don't
+// show the exception button.
+add_task(async function exceptionButtonTRROnly() {
+ let browser = await loadErrorPage();
+
+ await SpecialPowers.spawn(browser, [], function () {
+ const doc = content.document;
+ ok(
+ doc.documentURI.startsWith("about:neterror"),
+ "Should be showing error page"
+ );
+
+ const titleEl = doc.querySelector(".title-text");
+ const actualDataL10nID = titleEl.getAttribute("data-l10n-id");
+ is(
+ actualDataL10nID,
+ "dns-not-found-trr-only-title2",
+ "Correct error page title is set"
+ );
+
+ let trrExceptionButton = doc.getElementById("trrExceptionButton");
+ Assert.equal(
+ trrExceptionButton.hidden,
+ true,
+ "Exception button should be hidden for TRR service failures"
+ );
+ });
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ resetPrefs();
+});
+
+add_task(async function TRROnlyExceptionButtonTelemetry() {
+ // Clear everything.
+ Services.telemetry.clearEvents();
+ await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ return !events || !events.length;
+ });
+ Services.telemetry.setEventRecordingEnabled("security.doh.neterror", true);
+
+ let browser = await loadErrorPage();
+
+ await SpecialPowers.spawn(browser, [], function () {
+ const doc = content.document;
+ ok(
+ doc.documentURI.startsWith("about:neterror"),
+ "Should be showing error page"
+ );
+ });
+
+ let loadEvent = await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ return events?.find(e => e[1] == "security.doh.neterror" && e[2] == "load");
+ }, "recorded telemetry for the load");
+
+ loadEvent.shift();
+ Assert.deepEqual(loadEvent, [
+ "security.doh.neterror",
+ "load",
+ "dohwarning",
+ "TRROnlyFailure",
+ {
+ mode: "3",
+ provider_key: "mozilla.cloudflare-dns.com",
+ skip_reason: "TRR_UNKNOWN_CHANNEL_FAILURE",
+ },
+ ]);
+
+ await SpecialPowers.spawn(browser, [], function () {
+ const doc = content.document;
+ let buttons = ["neterrorTryAgainButton", "trrSettingsButton"];
+ for (let buttonId of buttons) {
+ let button = doc.getElementById(buttonId);
+ button.click();
+ }
+ });
+
+ // Since we click TryAgain, make sure the error page is loaded again.
+ await BrowserTestUtils.waitForErrorPage(browser);
+
+ is(
+ gBrowser.tabs.length,
+ 3,
+ "Should open about:preferences#privacy-doh in another tab"
+ );
+
+ let clickEvents = await TestUtils.waitForCondition(
+ () => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).content;
+ return events?.filter(
+ e => e[1] == "security.doh.neterror" && e[2] == "click"
+ );
+ },
+ "recorded telemetry for clicking buttons",
+ 500,
+ 100
+ );
+
+ let firstEvent = clickEvents[0];
+ firstEvent.shift(); // remove timestamp
+ Assert.deepEqual(firstEvent, [
+ "security.doh.neterror",
+ "click",
+ "try_again_button",
+ "TRROnlyFailure",
+ {
+ mode: "3",
+ provider_key: "mozilla.cloudflare-dns.com",
+ skip_reason: "TRR_UNKNOWN_CHANNEL_FAILURE",
+ },
+ ]);
+
+ let secondEvent = clickEvents[1];
+ secondEvent.shift(); // remove timestamp
+ Assert.deepEqual(secondEvent, [
+ "security.doh.neterror",
+ "click",
+ "settings_button",
+ "TRROnlyFailure",
+ {
+ mode: "3",
+ provider_key: "mozilla.cloudflare-dns.com",
+ skip_reason: "TRR_UNKNOWN_CHANNEL_FAILURE",
+ },
+ ]);
+
+ BrowserTestUtils.removeTab(gBrowser.tabs[2]);
+ BrowserTestUtils.removeTab(gBrowser.tabs[1]);
+ resetPrefs();
+});
diff --git a/browser/base/content/test/about/browser_aboutNetError_xfo_iframe.js b/browser/base/content/test/about/browser_aboutNetError_xfo_iframe.js
new file mode 100644
index 0000000000..ae4d5c22a2
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutNetError_xfo_iframe.js
@@ -0,0 +1,139 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const BLOCKED_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.org:8000/browser/browser/base/content/test/about/xfo_iframe.sjs";
+
+add_task(async function test_xfo_iframe() {
+ let { iframePageTab, blockedPageTab } = await setupPage(
+ "iframe_page_xfo.html",
+ BLOCKED_PAGE
+ );
+
+ let xfoBrowser = gBrowser.selectedTab.linkedBrowser;
+
+ // The blocked page opened in a new window/tab
+ await SpecialPowers.spawn(
+ xfoBrowser,
+ [BLOCKED_PAGE],
+ async function (xfoBlockedPage) {
+ let cookieHeader = content.document.getElementById("strictCookie");
+ let location = content.document.location.href;
+
+ Assert.ok(
+ cookieHeader.textContent.includes("No same site strict cookie header"),
+ "Same site strict cookie has not been set"
+ );
+ Assert.equal(
+ location,
+ xfoBlockedPage,
+ "Location of new page is correct!"
+ );
+ }
+ );
+
+ Services.cookies.removeAll();
+ BrowserTestUtils.removeTab(iframePageTab);
+ BrowserTestUtils.removeTab(blockedPageTab);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+async function setupPage(htmlPageName, blockedPage) {
+ let iFramePage =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+ ) + htmlPageName;
+
+ // Opening the blocked page once in a new tab
+ let blockedPageTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ blockedPage
+ );
+ let blockedPageBrowser = blockedPageTab.linkedBrowser;
+
+ let cookies = Services.cookies.getCookiesFromHost(
+ "example.org",
+ blockedPageBrowser.contentPrincipal.originAttributes
+ );
+ let strictCookie = cookies[0];
+
+ is(
+ strictCookie.value,
+ "creamy",
+ "Same site strict cookie has the expected value"
+ );
+
+ is(strictCookie.sameSite, 2, "The cookie is a same site strict cookie");
+
+ // Opening the page that contains the iframe
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ let browser = tab.linkedBrowser;
+ let browserLoaded = BrowserTestUtils.browserLoaded(
+ browser,
+ true,
+ blockedPage,
+ true
+ );
+
+ BrowserTestUtils.loadURIString(browser, iFramePage);
+ await browserLoaded;
+ info("The error page has loaded!");
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ let iframe = content.document.getElementById("theIframe");
+
+ await ContentTaskUtils.waitForCondition(() =>
+ SpecialPowers.spawn(iframe, [], () =>
+ content.document.body.classList.contains("neterror")
+ )
+ );
+ });
+
+ let frameContext = browser.browsingContext.children[0];
+ let newTabLoaded = BrowserTestUtils.waitForNewTab(gBrowser, null, true);
+
+ // In the iframe, we see the correct error page and click on the button
+ // to open the blocked page in a new window/tab
+ await SpecialPowers.spawn(frameContext, [], async function () {
+ let doc = content.document;
+ let textLongDescription = doc.getElementById("errorLongDesc").textContent;
+ let learnMoreLinkLocation = doc.getElementById("learnMoreLink").href;
+
+ Assert.ok(
+ textLongDescription.includes(
+ "To see this page, you need to open it in a new window."
+ ),
+ "Correct error message found"
+ );
+
+ let button = doc.getElementById("openInNewWindowButton");
+ Assert.ok(
+ button.textContent.includes("Open Site in New Window"),
+ "We see the correct button to open the site in a new window"
+ );
+
+ Assert.ok(
+ learnMoreLinkLocation.includes("xframe-neterror-page"),
+ "Correct Learn More URL for XFO error page"
+ );
+
+ // We click on the button
+ await EventUtils.synthesizeMouseAtCenter(button, {}, content);
+ });
+ info("Button was clicked!");
+
+ // We wait for the new tab to load
+ await newTabLoaded;
+ info("The new tab has loaded!");
+
+ let iframePageTab = tab;
+ return {
+ iframePageTab,
+ blockedPageTab,
+ };
+}
diff --git a/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbar.js b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbar.js
new file mode 100644
index 0000000000..c566276d9f
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbar.js
@@ -0,0 +1,311 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function bookmarks_toolbar_shown_on_newtab() {
+ let newtab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "about:newtab",
+ waitForLoad: false,
+ });
+
+ // 1: Test that the toolbar is shown in a newly opened foreground about:newtab
+ await waitForBookmarksToolbarVisibility({
+ visible: true,
+ message: "Toolbar should be visible on newtab",
+ });
+ ok(isBookmarksToolbarVisible(), "Toolbar should be visible on newtab");
+
+ // 2: Test that the toolbar is hidden when the browser is navigated away from newtab
+ BrowserTestUtils.loadURIString(newtab.linkedBrowser, "https://example.com");
+ await BrowserTestUtils.browserLoaded(newtab.linkedBrowser);
+ await waitForBookmarksToolbarVisibility({
+ visible: false,
+ message:
+ "Toolbar should not be visible on newtab after example.com is loaded within",
+ });
+ ok(
+ !isBookmarksToolbarVisible(),
+ "Toolbar should not be visible on newtab after example.com is loaded within"
+ );
+
+ // 3: Re-load about:newtab in the browser for the following tests and confirm toolbar reappears
+ BrowserTestUtils.loadURIString(newtab.linkedBrowser, "about:newtab");
+ await BrowserTestUtils.browserLoaded(newtab.linkedBrowser);
+ await waitForBookmarksToolbarVisibility({
+ visible: true,
+ message: "Toolbar should be visible on newtab",
+ });
+ ok(isBookmarksToolbarVisible(), "Toolbar should be visible on newtab");
+
+ // 4: Toolbar should get hidden when opening a new tab to example.com
+ let example = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "https://example.com",
+ });
+ await waitForBookmarksToolbarVisibility({
+ visible: false,
+ message: "Toolbar should be hidden on example.com",
+ });
+
+ // 5: Toolbar should become visible when switching tabs to newtab
+ await BrowserTestUtils.switchTab(gBrowser, newtab);
+ await waitForBookmarksToolbarVisibility({
+ visible: true,
+ message: "Toolbar is visible with switch to newtab",
+ });
+ ok(isBookmarksToolbarVisible(), "Toolbar is visible with switch to newtab");
+
+ // 6: Toolbar should become hidden when switching tabs to example.com
+ await BrowserTestUtils.switchTab(gBrowser, example);
+ await waitForBookmarksToolbarVisibility({
+ visible: false,
+ message: "Toolbar is hidden with switch to example",
+ });
+
+ // 7: Similar to #3 above, loading about:newtab in example should show toolbar
+ BrowserTestUtils.loadURIString(example.linkedBrowser, "about:newtab");
+ await BrowserTestUtils.browserLoaded(example.linkedBrowser);
+ await waitForBookmarksToolbarVisibility({
+ visible: true,
+ message: "Toolbar is visible with newtab load",
+ });
+ ok(isBookmarksToolbarVisible(), "Toolbar is visible with newtab load");
+
+ // 8: Switching back and forth between two browsers showing about:newtab will still show the toolbar
+ await BrowserTestUtils.switchTab(gBrowser, newtab);
+ ok(isBookmarksToolbarVisible(), "Toolbar is visible with switch to newtab");
+ await BrowserTestUtils.switchTab(gBrowser, example);
+ ok(
+ isBookmarksToolbarVisible(),
+ "Toolbar is visible with switch to example(newtab)"
+ );
+
+ // 9: With custom newtab URL, toolbar isn't shown on about:newtab but is shown on custom URL
+ let oldNewTab = AboutNewTab.newTabURL;
+ AboutNewTab.newTabURL = "https://example.com/2";
+ await BrowserTestUtils.switchTab(gBrowser, newtab);
+ await waitForBookmarksToolbarVisibility({ visible: false });
+ ok(!isBookmarksToolbarVisible(), "Toolbar should hide with custom newtab");
+ BrowserTestUtils.loadURIString(example.linkedBrowser, AboutNewTab.newTabURL);
+ await BrowserTestUtils.browserLoaded(example.linkedBrowser);
+ await BrowserTestUtils.switchTab(gBrowser, example);
+ await waitForBookmarksToolbarVisibility({ visible: true });
+ ok(
+ isBookmarksToolbarVisible(),
+ "Toolbar is visible with switch to custom newtab"
+ );
+
+ await BrowserTestUtils.removeTab(newtab);
+ await BrowserTestUtils.removeTab(example);
+ AboutNewTab.newTabURL = oldNewTab;
+});
+
+add_task(async function bookmarks_toolbar_open_persisted() {
+ let newtab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "about:newtab",
+ waitForLoad: false,
+ });
+ let example = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "https://example.com",
+ });
+ let isToolbarPersistedOpen = () =>
+ Services.prefs.getCharPref("browser.toolbars.bookmarks.visibility") ==
+ "always";
+
+ ok(!isBookmarksToolbarVisible(), "Toolbar is hidden");
+ await BrowserTestUtils.switchTab(gBrowser, newtab);
+ await waitForBookmarksToolbarVisibility({ visible: true });
+ ok(isBookmarksToolbarVisible(), "Toolbar is visible");
+ await BrowserTestUtils.switchTab(gBrowser, example);
+ await waitForBookmarksToolbarVisibility({ visible: false });
+ ok(!isBookmarksToolbarVisible(), "Toolbar is hidden");
+ ok(!isToolbarPersistedOpen(), "Toolbar is not persisted open");
+
+ let contextMenu = document.querySelector("#toolbar-context-menu");
+ let popupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ let menuButton = document.getElementById("PanelUI-menu-button");
+ EventUtils.synthesizeMouseAtCenter(
+ menuButton,
+ { type: "contextmenu" },
+ window
+ );
+ await popupShown;
+ let bookmarksToolbarMenu = document.querySelector("#toggle_PersonalToolbar");
+ let subMenu = bookmarksToolbarMenu.querySelector("menupopup");
+ popupShown = BrowserTestUtils.waitForEvent(subMenu, "popupshown");
+ bookmarksToolbarMenu.openMenu(true);
+ await popupShown;
+ let alwaysMenuItem = document.querySelector(
+ 'menuitem[data-visibility-enum="always"]'
+ );
+ let neverMenuItem = document.querySelector(
+ 'menuitem[data-visibility-enum="never"]'
+ );
+ let newTabMenuItem = document.querySelector(
+ 'menuitem[data-visibility-enum="newtab"]'
+ );
+ is(alwaysMenuItem.getAttribute("checked"), "false", "Menuitem isn't checked");
+ is(neverMenuItem.getAttribute("checked"), "false", "Menuitem isn't checked");
+ is(newTabMenuItem.getAttribute("checked"), "true", "Menuitem is checked");
+
+ subMenu.activateItem(alwaysMenuItem);
+
+ await waitForBookmarksToolbarVisibility({ visible: true });
+ popupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(
+ menuButton,
+ { type: "contextmenu" },
+ window
+ );
+ await popupShown;
+ bookmarksToolbarMenu = document.querySelector("#toggle_PersonalToolbar");
+ subMenu = bookmarksToolbarMenu.querySelector("menupopup");
+ popupShown = BrowserTestUtils.waitForEvent(subMenu, "popupshown");
+ bookmarksToolbarMenu.openMenu(true);
+ await popupShown;
+ alwaysMenuItem = document.querySelector(
+ 'menuitem[data-visibility-enum="always"]'
+ );
+ neverMenuItem = document.querySelector(
+ 'menuitem[data-visibility-enum="never"]'
+ );
+ newTabMenuItem = document.querySelector(
+ 'menuitem[data-visibility-enum="newtab"]'
+ );
+ is(alwaysMenuItem.getAttribute("checked"), "true", "Menuitem is checked");
+ is(neverMenuItem.getAttribute("checked"), "false", "Menuitem isn't checked");
+ is(newTabMenuItem.getAttribute("checked"), "false", "Menuitem isn't checked");
+ contextMenu.hidePopup();
+ ok(isBookmarksToolbarVisible(), "Toolbar is visible");
+ ok(isToolbarPersistedOpen(), "Toolbar is persisted open");
+ await BrowserTestUtils.switchTab(gBrowser, newtab);
+ ok(isBookmarksToolbarVisible(), "Toolbar is visible");
+ await BrowserTestUtils.switchTab(gBrowser, example);
+ ok(isBookmarksToolbarVisible(), "Toolbar is visible");
+
+ popupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(
+ menuButton,
+ { type: "contextmenu" },
+ window
+ );
+ await popupShown;
+ bookmarksToolbarMenu = document.querySelector("#toggle_PersonalToolbar");
+ subMenu = bookmarksToolbarMenu.querySelector("menupopup");
+ popupShown = BrowserTestUtils.waitForEvent(subMenu, "popupshown");
+ bookmarksToolbarMenu.openMenu(true);
+ await popupShown;
+ alwaysMenuItem = document.querySelector(
+ 'menuitem[data-visibility-enum="always"]'
+ );
+ neverMenuItem = document.querySelector(
+ 'menuitem[data-visibility-enum="never"]'
+ );
+ newTabMenuItem = document.querySelector(
+ 'menuitem[data-visibility-enum="newtab"]'
+ );
+ is(alwaysMenuItem.getAttribute("checked"), "true", "Menuitem is checked");
+ is(neverMenuItem.getAttribute("checked"), "false", "Menuitem isn't checked");
+ is(newTabMenuItem.getAttribute("checked"), "false", "Menuitem isn't checked");
+ subMenu.activateItem(newTabMenuItem);
+ await waitForBookmarksToolbarVisibility({
+ visible: false,
+ message: "Toolbar is hidden",
+ });
+ await BrowserTestUtils.switchTab(gBrowser, newtab);
+ await waitForBookmarksToolbarVisibility({
+ visible: true,
+ message: "Toolbar is visible",
+ });
+ await BrowserTestUtils.switchTab(gBrowser, example);
+ await waitForBookmarksToolbarVisibility({
+ visible: false,
+ message: "Toolbar is hidden",
+ });
+
+ await BrowserTestUtils.removeTab(newtab);
+ await BrowserTestUtils.removeTab(example);
+});
+
+add_task(async function test_with_newtabpage_disabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.newtabpage.enabled", true]],
+ });
+
+ let tabCount = gBrowser.tabs.length;
+ document.getElementById("cmd_newNavigatorTab").doCommand();
+ // Can't use BrowserTestUtils.waitForNewTab since onLocationChange will not
+ // fire due to preloaded new tabs.
+ await TestUtils.waitForCondition(() => gBrowser.tabs.length == tabCount + 1);
+ let newtab = gBrowser.selectedTab;
+ is(newtab.linkedBrowser.currentURI.spec, "about:newtab", "newtab is loaded");
+ await waitForBookmarksToolbarVisibility({
+ visible: true,
+ message: "Toolbar is visible with NTP enabled",
+ });
+ let firstid = await SpecialPowers.spawn(newtab.linkedBrowser, [], () => {
+ return content.document.body.firstElementChild?.id;
+ });
+ is(firstid, "root", "new tab page contains content");
+ await BrowserTestUtils.removeTab(newtab);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.newtabpage.enabled", false]],
+ });
+
+ document.getElementById("cmd_newNavigatorTab").doCommand();
+ await TestUtils.waitForCondition(() => gBrowser.tabs.length == tabCount + 1);
+ newtab = gBrowser.selectedTab;
+
+ await waitForBookmarksToolbarVisibility({
+ visible: true,
+ message: "Toolbar is visible with NTP disabled",
+ });
+
+ is(
+ newtab.linkedBrowser.currentURI.spec,
+ "about:newtab",
+ "blank new tab is loaded"
+ );
+ firstid = await SpecialPowers.spawn(newtab.linkedBrowser, [], () => {
+ return content.document.body.firstElementChild;
+ });
+ ok(!firstid, "blank new tab page contains no content");
+
+ await BrowserTestUtils.removeTab(newtab);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.newtabpage.enabled", true]],
+ });
+});
+
+add_task(async function test_history_pushstate() {
+ await BrowserTestUtils.withNewTab("https://example.com/", async browser => {
+ await waitForBookmarksToolbarVisibility({ visible: false });
+ ok(!isBookmarksToolbarVisible(), "Toolbar should be hidden");
+
+ // Temporarily show the toolbar:
+ setToolbarVisibility(
+ document.querySelector("#PersonalToolbar"),
+ true,
+ false,
+ false
+ );
+ ok(isBookmarksToolbarVisible(), "Toolbar should now be visible");
+
+ // Now "navigate"
+ await SpecialPowers.spawn(browser, [], () => {
+ content.location.href += "#foo";
+ });
+
+ await TestUtils.waitForCondition(
+ () => gURLBar.value.endsWith("#foo"),
+ "URL bar should update"
+ );
+ ok(isBookmarksToolbarVisible(), "Toolbar should still be visible");
+ });
+});
diff --git a/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarEmpty.js b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarEmpty.js
new file mode 100644
index 0000000000..8e9ef8d163
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarEmpty.js
@@ -0,0 +1,158 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const bookmarksInfo = [
+ {
+ title: "firefox",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ url: "http://example.com",
+ },
+ {
+ title: "rules",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ url: "http://example.com/2",
+ },
+ {
+ title: "yo",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ url: "http://example.com/2",
+ },
+];
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ // Ensure we can wait for about:newtab to load.
+ set: [["browser.newtab.preload", false]],
+ });
+ // Move all existing bookmarks in the Bookmarks Toolbar and
+ // Other Bookmarks to the Bookmarks Menu so they don't affect
+ // the visibility of the Bookmarks Toolbar. Restore them at
+ // the end of the test.
+ let Bookmarks = PlacesUtils.bookmarks;
+ let toolbarBookmarks = [];
+ let unfiledBookmarks = [];
+ let guidBookmarkTuples = [
+ [Bookmarks.toolbarGuid, toolbarBookmarks],
+ [Bookmarks.unfiledGuid, unfiledBookmarks],
+ ];
+ for (let [parentGuid, arr] of guidBookmarkTuples) {
+ await Bookmarks.fetch({ parentGuid }, bookmark => arr.push(bookmark));
+ }
+ await Promise.all(
+ [...toolbarBookmarks, ...unfiledBookmarks].map(async bookmark => {
+ bookmark.parentGuid = Bookmarks.menuGuid;
+ return Bookmarks.update(bookmark);
+ })
+ );
+ registerCleanupFunction(async () => {
+ for (let [parentGuid, arr] of guidBookmarkTuples) {
+ await Promise.all(
+ arr.map(async bookmark => {
+ bookmark.parentGuid = parentGuid;
+ return Bookmarks.update(bookmark);
+ })
+ );
+ }
+ });
+});
+
+add_task(async function bookmarks_toolbar_not_shown_when_empty() {
+ let bookmarks = await PlacesUtils.bookmarks.insertTree({
+ guid: PlacesUtils.bookmarks.toolbarGuid,
+ children: bookmarksInfo,
+ });
+ let example = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "https://example.com",
+ });
+ let newtab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "about:newtab",
+ });
+ let emptyMessage = document.getElementById("personal-toolbar-empty");
+
+ // 1: Test that the toolbar is shown in a newly opened foreground about:newtab
+ await waitForBookmarksToolbarVisibility({
+ visible: true,
+ message: "Toolbar should be visible on newtab",
+ });
+ ok(emptyMessage.hidden, "Empty message is hidden with toolbar populated");
+
+ // 2: Toolbar should get hidden when switching tab to example.com
+ await BrowserTestUtils.switchTab(gBrowser, example);
+ await waitForBookmarksToolbarVisibility({
+ visible: false,
+ message: "Toolbar should be hidden on example.com",
+ });
+
+ // 3: Remove all children of the Bookmarks Toolbar and confirm that
+ // the toolbar should not become visible when switching to newtab
+ CustomizableUI.addWidgetToArea(
+ "personal-bookmarks",
+ CustomizableUI.AREA_TABSTRIP
+ );
+ CustomizableUI.removeWidgetFromArea("import-button");
+ await BrowserTestUtils.switchTab(gBrowser, newtab);
+ await waitForBookmarksToolbarVisibility({
+ visible: true,
+ message: "Toolbar is visible when there are no items in the toolbar area",
+ });
+ ok(!emptyMessage.hidden, "Empty message is shown with toolbar empty");
+ // Click the link and check we open the library:
+ let winPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
+ EventUtils.synthesizeMouseAtCenter(
+ emptyMessage.querySelector(".text-link"),
+ {}
+ );
+ let libraryWin = await winPromise;
+ is(
+ libraryWin.document.location.href,
+ "chrome://browser/content/places/places.xhtml",
+ "Should have opened library."
+ );
+ await BrowserTestUtils.closeWindow(libraryWin);
+
+ // 4: Put personal-bookmarks back in the toolbar and confirm the toolbar is visible now
+ CustomizableUI.addWidgetToArea(
+ "personal-bookmarks",
+ CustomizableUI.AREA_BOOKMARKS
+ );
+ await BrowserTestUtils.switchTab(gBrowser, example);
+ await BrowserTestUtils.switchTab(gBrowser, newtab);
+ await waitForBookmarksToolbarVisibility({
+ visible: true,
+ message: "Toolbar should be visible with Bookmarks Toolbar Items restored",
+ });
+ ok(emptyMessage.hidden, "Empty message is hidden with toolbar populated");
+
+ // 5: Remove all the bookmarks in the toolbar and confirm that the toolbar
+ // is hidden on the New Tab now
+ await PlacesUtils.bookmarks.remove(bookmarks);
+ await BrowserTestUtils.switchTab(gBrowser, example);
+ await BrowserTestUtils.switchTab(gBrowser, newtab);
+ await waitForBookmarksToolbarVisibility({
+ visible: true,
+ message:
+ "Toolbar is visible when there are no items or nested bookmarks in the toolbar area",
+ });
+ ok(!emptyMessage.hidden, "Empty message is shown with toolbar empty");
+
+ // 6: Add a toolbarbutton and make sure that the toolbar appears when the button is visible
+ CustomizableUI.addWidgetToArea(
+ "characterencoding-button",
+ CustomizableUI.AREA_BOOKMARKS
+ );
+ await BrowserTestUtils.switchTab(gBrowser, example);
+ await BrowserTestUtils.switchTab(gBrowser, newtab);
+ await waitForBookmarksToolbarVisibility({
+ visible: true,
+ message: "Toolbar is visible when there is a visible button in the toolbar",
+ });
+ ok(emptyMessage.hidden, "Empty message is hidden with button in toolbar");
+
+ await BrowserTestUtils.removeTab(newtab);
+ await BrowserTestUtils.removeTab(example);
+ CustomizableUI.reset();
+});
diff --git a/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarNewWindow.js b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarNewWindow.js
new file mode 100644
index 0000000000..19c990bbbc
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarNewWindow.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+const testCases = [
+ {
+ name: "bookmarks_toolbar_shown_on_newtab_newTabEnabled",
+ newTabEnabled: true,
+ },
+ {
+ name: "bookmarks_toolbar_shown_on_newtab",
+ newTabEnabled: false,
+ },
+];
+
+async function test_bookmarks_toolbar_visibility({ newTabEnabled }) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.newtabpage.enabled", newTabEnabled]],
+ });
+
+ // Ensure the toolbar doesnt become visible at any point before the tab finishes loading
+
+ let url =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "slow_loading_page.sjs";
+
+ let startTime = Date.now();
+ let newWindowOpened = BrowserTestUtils.domWindowOpened();
+ let beforeShown = TestUtils.topicObserved("browser-window-before-show");
+
+ openTrustedLinkIn(url, "window");
+
+ let newWin = await newWindowOpened;
+ let slowSiteLoaded = BrowserTestUtils.firstBrowserLoaded(newWin, false);
+
+ function checkToolbarIsCollapsed(win, message) {
+ let toolbar = win.document.getElementById("PersonalToolbar");
+ ok(toolbar && toolbar.collapsed, message);
+ }
+
+ await beforeShown;
+ checkToolbarIsCollapsed(
+ newWin,
+ "Toolbar is initially hidden on the new window"
+ );
+
+ function onToolbarMutation() {
+ checkToolbarIsCollapsed(newWin, "Toolbar should remain collapsed");
+ }
+ let toolbarMutationObserver = new newWin.MutationObserver(onToolbarMutation);
+ toolbarMutationObserver.observe(
+ newWin.document.getElementById("PersonalToolbar"),
+ {
+ attributeFilter: ["collapsed"],
+ }
+ );
+
+ info("Waiting for the slow site to load");
+ await slowSiteLoaded;
+ info(`Window opened and slow site loaded in: ${Date.now() - startTime}ms`);
+
+ checkToolbarIsCollapsed(newWin, "Finally, the toolbar is still hidden");
+
+ toolbarMutationObserver.disconnect();
+ await BrowserTestUtils.closeWindow(newWin);
+}
+
+// Make separate tasks for each test case, so we get more useful stack traces on failure
+for (let testData of testCases) {
+ let tmp = {
+ async [testData.name]() {
+ info("testing with: " + JSON.stringify(testData));
+ await test_bookmarks_toolbar_visibility(testData);
+ },
+ };
+ add_task(tmp[testData.name]);
+}
diff --git a/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarPrefs.js b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarPrefs.js
new file mode 100644
index 0000000000..e9f7768beb
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarPrefs.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(3);
+
+add_task(async function test_with_different_pref_states() {
+ // [prefName, prefValue, toolbarVisibleExampleCom, toolbarVisibleNewTab]
+ let bookmarksToolbarVisibilityStates = [
+ ["browser.toolbars.bookmarks.visibility", "newtab"],
+ ["browser.toolbars.bookmarks.visibility", "always"],
+ ["browser.toolbars.bookmarks.visibility", "never"],
+ ];
+ for (let visibilityState of bookmarksToolbarVisibilityStates) {
+ await SpecialPowers.pushPrefEnv({
+ set: [visibilityState],
+ });
+
+ for (let privateWin of [true, false]) {
+ info(
+ `Testing with ${visibilityState} in a ${
+ privateWin ? "private" : "non-private"
+ } window`
+ );
+ let win = await BrowserTestUtils.openNewBrowserWindow({
+ private: privateWin,
+ });
+ is(
+ win.gBrowser.currentURI.spec,
+ privateWin ? "about:privatebrowsing" : "about:blank",
+ "Expecting about:privatebrowsing or about:blank as URI of new window"
+ );
+
+ if (!privateWin) {
+ await waitForBookmarksToolbarVisibility({
+ win,
+ visible: visibilityState[1] == "always",
+ message:
+ "Toolbar should be visible only if visibilityState is 'always'. State: " +
+ visibilityState[1],
+ });
+ await BrowserTestUtils.openNewForegroundTab({
+ gBrowser: win.gBrowser,
+ opening: "about:newtab",
+ waitForLoad: false,
+ });
+ }
+
+ await waitForBookmarksToolbarVisibility({
+ win,
+ visible:
+ visibilityState[1] == "newtab" || visibilityState[1] == "always",
+ message:
+ "Toolbar should be visible as long as visibilityState isn't set to 'never'. State: " +
+ visibilityState[1],
+ });
+
+ await BrowserTestUtils.openNewForegroundTab({
+ gBrowser: win.gBrowser,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ opening: "http://example.com",
+ });
+ await waitForBookmarksToolbarVisibility({
+ win,
+ visible: visibilityState[1] == "always",
+ message:
+ "Toolbar should be visible only if visibilityState is 'always'. State: " +
+ visibilityState[1],
+ });
+ await BrowserTestUtils.closeWindow(win);
+ }
+ }
+});
diff --git a/browser/base/content/test/about/browser_aboutStopReload.js b/browser/base/content/test/about/browser_aboutStopReload.js
new file mode 100644
index 0000000000..66c11a3de3
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutStopReload.js
@@ -0,0 +1,169 @@
+async function waitForNoAnimation(elt) {
+ return TestUtils.waitForCondition(() => !elt.hasAttribute("animate"));
+}
+
+async function getAnimatePromise(elt) {
+ return BrowserTestUtils.waitForAttribute("animate", elt).then(() =>
+ Assert.ok(true, `${elt.id} should animate`)
+ );
+}
+
+function stopReloadMutationCallback() {
+ Assert.ok(
+ false,
+ "stop-reload's animate attribute should not have been mutated"
+ );
+}
+
+// Force-enable the animation
+gReduceMotionOverride = false;
+
+add_task(async function checkDontShowStopOnNewTab() {
+ let stopReloadContainer = document.getElementById("stop-reload-button");
+ let stopReloadContainerObserver = new MutationObserver(
+ stopReloadMutationCallback
+ );
+
+ await waitForNoAnimation(stopReloadContainer);
+ stopReloadContainerObserver.observe(stopReloadContainer, {
+ attributeFilter: ["animate"],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "about:robots",
+ waitForStateStop: true,
+ });
+ BrowserTestUtils.removeTab(tab);
+
+ Assert.ok(
+ true,
+ "Test finished: stop-reload does not animate when navigating to local URI on new tab"
+ );
+ stopReloadContainerObserver.disconnect();
+});
+
+add_task(async function checkDontShowStopFromLocalURI() {
+ let stopReloadContainer = document.getElementById("stop-reload-button");
+ let stopReloadContainerObserver = new MutationObserver(
+ stopReloadMutationCallback
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "about:robots",
+ waitForStateStop: true,
+ });
+ await waitForNoAnimation(stopReloadContainer);
+ stopReloadContainerObserver.observe(stopReloadContainer, {
+ attributeFilter: ["animate"],
+ });
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, "about:mozilla");
+ BrowserTestUtils.removeTab(tab);
+
+ Assert.ok(
+ true,
+ "Test finished: stop-reload does not animate when navigating between local URIs"
+ );
+ stopReloadContainerObserver.disconnect();
+});
+
+add_task(async function checkDontShowStopFromNonLocalURI() {
+ let stopReloadContainer = document.getElementById("stop-reload-button");
+ let stopReloadContainerObserver = new MutationObserver(
+ stopReloadMutationCallback
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "https://example.com",
+ waitForStateStop: true,
+ });
+ await waitForNoAnimation(stopReloadContainer);
+ stopReloadContainerObserver.observe(stopReloadContainer, {
+ attributeFilter: ["animate"],
+ });
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, "about:mozilla");
+ BrowserTestUtils.removeTab(tab);
+
+ Assert.ok(
+ true,
+ "Test finished: stop-reload does not animate when navigating to local URI from non-local URI"
+ );
+ stopReloadContainerObserver.disconnect();
+});
+
+add_task(async function checkDoShowStopOnNewTab() {
+ let stopReloadContainer = document.getElementById("stop-reload-button");
+ let reloadButton = document.getElementById("reload-button");
+ let stopPromise = BrowserTestUtils.waitForAttribute(
+ "displaystop",
+ reloadButton
+ );
+
+ await waitForNoAnimation(stopReloadContainer);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "https://example.com",
+ waitForStateStop: true,
+ });
+ await stopPromise;
+ await waitForNoAnimation(stopReloadContainer);
+ BrowserTestUtils.removeTab(tab);
+
+ info(
+ "Test finished: stop-reload shows stop when navigating to non-local URI during tab opening"
+ );
+});
+
+add_task(async function checkAnimateStopOnTabAfterTabFinishesOpening() {
+ let stopReloadContainer = document.getElementById("stop-reload-button");
+
+ await waitForNoAnimation(stopReloadContainer);
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ waitForStateStop: true,
+ });
+ await TestUtils.waitForCondition(() => {
+ info(
+ "Waiting for tabAnimationsInProgress to equal 0, currently " +
+ gBrowser.tabAnimationsInProgress
+ );
+ return !gBrowser.tabAnimationsInProgress;
+ });
+ let animatePromise = getAnimatePromise(stopReloadContainer);
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, "https://example.com");
+ await animatePromise;
+ BrowserTestUtils.removeTab(tab);
+
+ info(
+ "Test finished: stop-reload animates when navigating to non-local URI on new tab after tab has opened"
+ );
+});
+
+add_task(async function checkDoShowStopFromLocalURI() {
+ let stopReloadContainer = document.getElementById("stop-reload-button");
+
+ await waitForNoAnimation(stopReloadContainer);
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "about:robots",
+ waitForStateStop: true,
+ });
+ await TestUtils.waitForCondition(() => {
+ info(
+ "Waiting for tabAnimationsInProgress to equal 0, currently " +
+ gBrowser.tabAnimationsInProgress
+ );
+ return !gBrowser.tabAnimationsInProgress;
+ });
+ let animatePromise = getAnimatePromise(stopReloadContainer);
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, "https://example.com");
+ await animatePromise;
+ await waitForNoAnimation(stopReloadContainer);
+ BrowserTestUtils.removeTab(tab);
+
+ info(
+ "Test finished: stop-reload animates when navigating to non-local URI from local URI"
+ );
+});
diff --git a/browser/base/content/test/about/browser_aboutSupport.js b/browser/base/content/test/about/browser_aboutSupport.js
new file mode 100644
index 0000000000..e846a2b493
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutSupport.js
@@ -0,0 +1,146 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { ExperimentAPI } = ChromeUtils.importESModule(
+ "resource://nimbus/ExperimentAPI.sys.mjs"
+);
+const { ExperimentFakes } = ChromeUtils.importESModule(
+ "resource://testing-common/NimbusTestUtils.sys.mjs"
+);
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:support" },
+ async function (browser) {
+ let keyLocationServiceGoogleStatus = await SpecialPowers.spawn(
+ browser,
+ [],
+ async function () {
+ let textBox = content.document.getElementById(
+ "key-location-service-google-box"
+ );
+ await ContentTaskUtils.waitForCondition(
+ () => content.document.l10n.getAttributes(textBox).id,
+ "Google location service API key status loaded"
+ );
+ return content.document.l10n.getAttributes(textBox).id;
+ }
+ );
+ ok(
+ keyLocationServiceGoogleStatus,
+ "Google location service API key status shown"
+ );
+
+ let keySafebrowsingGoogleStatus = await SpecialPowers.spawn(
+ browser,
+ [],
+ async function () {
+ let textBox = content.document.getElementById(
+ "key-safebrowsing-google-box"
+ );
+ await ContentTaskUtils.waitForCondition(
+ () => content.document.l10n.getAttributes(textBox).id,
+ "Google Safebrowsing API key status loaded"
+ );
+ return content.document.l10n.getAttributes(textBox).id;
+ }
+ );
+ ok(
+ keySafebrowsingGoogleStatus,
+ "Google Safebrowsing API key status shown"
+ );
+
+ let keyMozillaStatus = await SpecialPowers.spawn(
+ browser,
+ [],
+ async function () {
+ let textBox = content.document.getElementById("key-mozilla-box");
+ await ContentTaskUtils.waitForCondition(
+ () => content.document.l10n.getAttributes(textBox).id,
+ "Mozilla API key status loaded"
+ );
+ return content.document.l10n.getAttributes(textBox).id;
+ }
+ );
+ ok(keyMozillaStatus, "Mozilla API key status shown");
+ }
+ );
+});
+
+add_task(async function test_nimbus_experiments() {
+ await ExperimentAPI.ready();
+ let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
+ featureId: "aboutwelcome",
+ value: { enabled: true },
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:support" },
+ async function (browser) {
+ let experimentName = await SpecialPowers.spawn(
+ browser,
+ [],
+ async function () {
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ content.document.querySelector(
+ "#remote-experiments-tbody tr:first-child td"
+ )?.innerText
+ );
+ return content.document.querySelector(
+ "#remote-experiments-tbody tr:first-child td"
+ ).innerText;
+ }
+ );
+ ok(
+ experimentName.match("Nimbus"),
+ "Rendered the expected experiment slug"
+ );
+ }
+ );
+
+ await doExperimentCleanup();
+});
+
+add_task(async function test_remote_configuration() {
+ await ExperimentAPI.ready();
+ let doCleanup = await ExperimentFakes.enrollWithRollout({
+ featureId: NimbusFeatures.aboutwelcome.featureId,
+ value: { enabled: true },
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:support" },
+ async function (browser) {
+ let [userFacingName, branch] = await SpecialPowers.spawn(
+ browser,
+ [],
+ async function () {
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ content.document.querySelector(
+ "#remote-features-tbody tr:first-child td"
+ )?.innerText
+ );
+ let rolloutName = content.document.querySelector(
+ "#remote-features-tbody tr:first-child td"
+ ).innerText;
+ let branchName = content.document.querySelector(
+ "#remote-features-tbody tr:first-child td:nth-child(2)"
+ ).innerText;
+
+ return [rolloutName, branchName];
+ }
+ );
+ ok(
+ userFacingName.match("NimbusTestUtils"),
+ "Rendered the expected rollout"
+ );
+ ok(branch.match("aboutwelcome"), "Rendered the expected rollout branch");
+ }
+ );
+
+ await doCleanup();
+});
diff --git a/browser/base/content/test/about/browser_aboutSupport_newtab_security_state.js b/browser/base/content/test/about/browser_aboutSupport_newtab_security_state.js
new file mode 100644
index 0000000000..caa45a1af5
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutSupport_newtab_security_state.js
@@ -0,0 +1,19 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function checkIdentityOfAboutSupport() {
+ let tab = gBrowser.addTab("about:support", {
+ referrerURI: null,
+ inBackground: false,
+ allowThirdPartyFixup: false,
+ relatedToCurrent: false,
+ skipAnimation: true,
+ allowMixedContent: false,
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+
+ await promiseTabLoaded(tab);
+ let identityBox = document.getElementById("identity-box");
+ is(identityBox.className, "chromeUI", "Should know that we're chrome.");
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/about/browser_aboutSupport_places.js b/browser/base/content/test/about/browser_aboutSupport_places.js
new file mode 100644
index 0000000000..e971de7f0e
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutSupport_places.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_places_db_stats_table() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:support" },
+ async function (browser) {
+ const [initialToggleText, toggleTextAfterShow, toggleTextAfterHide] =
+ await SpecialPowers.spawn(browser, [], async function () {
+ const toggleButton = content.document.getElementById(
+ "place-database-stats-toggle"
+ );
+ const getToggleText = () =>
+ content.document.l10n.getAttributes(toggleButton).id;
+ const toggleTexts = [];
+ const table = content.document.getElementById(
+ "place-database-stats-tbody"
+ );
+ await ContentTaskUtils.waitForCondition(
+ () => table.style.display === "none",
+ "Stats table is hidden initially"
+ );
+ toggleTexts.push(getToggleText());
+ toggleButton.click();
+ await ContentTaskUtils.waitForCondition(
+ () => table.style.display === "",
+ "Stats table is shown after first toggle"
+ );
+ toggleTexts.push(getToggleText());
+ toggleButton.click();
+ await ContentTaskUtils.waitForCondition(
+ () => table.style.display === "none",
+ "Stats table is hidden after second toggle"
+ );
+ toggleTexts.push(getToggleText());
+ return toggleTexts;
+ });
+ Assert.equal(initialToggleText, "place-database-stats-show");
+ Assert.equal(toggleTextAfterShow, "place-database-stats-hide");
+ Assert.equal(toggleTextAfterHide, "place-database-stats-show");
+ }
+ );
+});
diff --git a/browser/base/content/test/about/browser_bug435325.js b/browser/base/content/test/about/browser_bug435325.js
new file mode 100644
index 0000000000..70a3b272a9
--- /dev/null
+++ b/browser/base/content/test/about/browser_bug435325.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Ensure that clicking the button in the Offline mode neterror page makes the browser go online. See bug 435325. */
+
+add_task(async function checkSwitchPageToOnlineMode() {
+ // Go offline and disable the proxy and cache, then try to load the test URL.
+ Services.io.offline = true;
+
+ // Tests always connect to localhost, and per bug 87717, localhost is now
+ // reachable in offline mode. To avoid this, disable any proxy.
+ let proxyPrefValue = SpecialPowers.getIntPref("network.proxy.type");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.proxy.type", 0],
+ ["browser.cache.disk.enable", false],
+ ["browser.cache.memory.enable", false],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab("about:blank", async function (browser) {
+ let netErrorLoaded = BrowserTestUtils.waitForErrorPage(browser);
+
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ BrowserTestUtils.loadURIString(browser, "http://example.com/");
+ await netErrorLoaded;
+
+ // Re-enable the proxy so example.com is resolved to localhost, rather than
+ // the actual example.com.
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.proxy.type", proxyPrefValue]],
+ });
+ let changeObserved = TestUtils.topicObserved(
+ "network:offline-status-changed"
+ );
+
+ // Click on the 'Try again' button.
+ await SpecialPowers.spawn(browser, [], async function () {
+ ok(
+ content.document.documentURI.startsWith("about:neterror?e=netOffline"),
+ "Should be showing error page"
+ );
+ content.document
+ .querySelector("#netErrorButtonContainer > .try-again")
+ .click();
+ });
+
+ await changeObserved;
+ ok(
+ !Services.io.offline,
+ "After clicking the 'Try Again' button, we're back online."
+ );
+ });
+});
+
+registerCleanupFunction(function () {
+ Services.io.offline = false;
+});
diff --git a/browser/base/content/test/about/browser_bug633691.js b/browser/base/content/test/about/browser_bug633691.js
new file mode 100644
index 0000000000..33d58475f6
--- /dev/null
+++ b/browser/base/content/test/about/browser_bug633691.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+add_task(async function test() {
+ const URL = "data:text/html,<iframe width='700' height='700'></iframe>";
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: URL },
+ async function (browser) {
+ let context = await SpecialPowers.spawn(browser, [], function () {
+ let iframe = content.document.querySelector("iframe");
+ iframe.src = "https://expired.example.com/";
+ return BrowsingContext.getFromWindow(iframe.contentWindow);
+ });
+ await TestUtils.waitForCondition(() => {
+ let frame = context.currentWindowGlobal;
+ return frame && frame.documentURI.spec.startsWith("about:certerror");
+ });
+ await SpecialPowers.spawn(context, [], async function () {
+ await ContentTaskUtils.waitForCondition(
+ () => content.document.readyState == "interactive"
+ );
+ let aP = content.document.getElementById("badCertAdvancedPanel");
+ Assert.ok(aP, "Advanced content should exist");
+ Assert.ok(
+ ContentTaskUtils.is_hidden(aP),
+ "Advanced content should not be visible by default"
+ );
+ });
+ }
+ );
+});
diff --git a/browser/base/content/test/about/csp_iframe.sjs b/browser/base/content/test/about/csp_iframe.sjs
new file mode 100644
index 0000000000..f53ed8498f
--- /dev/null
+++ b/browser/base/content/test/about/csp_iframe.sjs
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(request, response) {
+ // let's enjoy the amazing CSP setting
+ response.setHeader(
+ "Content-Security-Policy",
+ "frame-ancestors 'self'",
+ false
+ );
+
+ // let's avoid caching issues
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // everything is fine - no needs to worry :)
+ response.setStatusLine(request.httpVersion, 200);
+ response.setHeader("Content-Type", "text/html", false);
+ let txt = "<html><body><h1>CSP Page opened in new window!</h1></body></html>";
+ response.write(txt);
+
+ let cookie = request.hasHeader("Cookie")
+ ? request.getHeader("Cookie")
+ : "<html><body>" +
+ "<h2 id='strictCookie'>No same site strict cookie header</h2>" +
+ "</body></html>";
+ response.write(cookie);
+
+ if (!request.hasHeader("Cookie")) {
+ let strictCookie = `matchaCookie=green; Domain=.example.org; SameSite=Strict`;
+ response.setHeader("Set-Cookie", strictCookie);
+ }
+}
diff --git a/browser/base/content/test/about/dummy_page.html b/browser/base/content/test/about/dummy_page.html
new file mode 100644
index 0000000000..1a87e28408
--- /dev/null
+++ b/browser/base/content/test/about/dummy_page.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Dummy test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/browser/base/content/test/about/head.js b/browser/base/content/test/about/head.js
new file mode 100644
index 0000000000..c723fbee33
--- /dev/null
+++ b/browser/base/content/test/about/head.js
@@ -0,0 +1,220 @@
+ChromeUtils.defineESModuleGetters(this, {
+ FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
+ SearchTestUtils: "resource://testing-common/SearchTestUtils.sys.mjs",
+});
+
+SearchTestUtils.init(this);
+
+function getCertChainAsString(certBase64Array) {
+ let certChain = "";
+ for (let cert of certBase64Array) {
+ certChain += getPEMString(cert);
+ }
+ return certChain;
+}
+
+function getPEMString(derb64) {
+ // Wrap the Base64 string into lines of 64 characters,
+ // with CRLF line breaks (as specified in RFC 1421).
+ var wrapped = derb64.replace(/(\S{64}(?!$))/g, "$1\r\n");
+ return (
+ "-----BEGIN CERTIFICATE-----\r\n" +
+ wrapped +
+ "\r\n-----END CERTIFICATE-----\r\n"
+ );
+}
+
+async function injectErrorPageFrame(tab, src, sandboxed) {
+ let loadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ true,
+ null,
+ true
+ );
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [src, sandboxed],
+ async function (frameSrc, frameSandboxed) {
+ let iframe = content.document.createElement("iframe");
+ iframe.src = frameSrc;
+ if (frameSandboxed) {
+ iframe.setAttribute("sandbox", "allow-scripts");
+ }
+ content.document.body.appendChild(iframe);
+ }
+ );
+
+ await loadedPromise;
+}
+
+async function openErrorPage(src, useFrame, sandboxed) {
+ let dummyPage =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "dummy_page.html";
+
+ let tab;
+ if (useFrame) {
+ info("Loading cert error page in an iframe");
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, dummyPage);
+ await injectErrorPageFrame(tab, src, sandboxed);
+ } else {
+ let certErrorLoaded;
+ tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ () => {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, src);
+ let browser = gBrowser.selectedBrowser;
+ certErrorLoaded = BrowserTestUtils.waitForErrorPage(browser);
+ },
+ false
+ );
+ info("Loading and waiting for the cert error");
+ await certErrorLoaded;
+ }
+
+ return tab;
+}
+
+function waitForCondition(condition, nextTest, errorMsg, retryTimes) {
+ retryTimes = typeof retryTimes !== "undefined" ? retryTimes : 30;
+ var tries = 0;
+ var interval = setInterval(function () {
+ if (tries >= retryTimes) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ var conditionPassed;
+ try {
+ conditionPassed = condition();
+ } catch (e) {
+ ok(false, e + "\n" + e.stack);
+ conditionPassed = false;
+ }
+ if (conditionPassed) {
+ moveOn();
+ }
+ tries++;
+ }, 100);
+ var moveOn = function () {
+ clearInterval(interval);
+ nextTest();
+ };
+}
+
+function whenTabLoaded(aTab, aCallback) {
+ promiseTabLoadEvent(aTab).then(aCallback);
+}
+
+function promiseTabLoaded(aTab) {
+ return new Promise(resolve => {
+ whenTabLoaded(aTab, resolve);
+ });
+}
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ * The tab to load into.
+ * @param [optional] url
+ * The url to load, or the current url.
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url) {
+ info("Wait tab event: load");
+
+ function handle(loadedUrl) {
+ if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) {
+ info(`Skipping spurious load event for ${loadedUrl}`);
+ return false;
+ }
+
+ info("Tab event received: load");
+ return true;
+ }
+
+ let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
+
+ if (url) {
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, url);
+ }
+
+ return loaded;
+}
+
+/**
+ * Wait for the search engine to change. searchEngineChangeFn is a function
+ * that will be called to change the search engine.
+ */
+async function promiseContentSearchChange(browser, searchEngineChangeFn) {
+ // Add an event listener manually then perform the action, rather than using
+ // BrowserTestUtils.addContentEventListener as that doesn't add the listener
+ // early enough.
+ await SpecialPowers.spawn(browser, [], async () => {
+ // Store the results in a temporary place.
+ content._searchDetails = {
+ defaultEnginesList: [],
+ listener: event => {
+ if (event.detail.type == "CurrentState") {
+ content._searchDetails.defaultEnginesList.push(
+ content.wrappedJSObject.gContentSearchController.defaultEngine.name
+ );
+ }
+ },
+ };
+
+ // Listen using the system group to ensure that it fires after
+ // the default behaviour.
+ content.addEventListener(
+ "ContentSearchService",
+ content._searchDetails.listener,
+ { mozSystemGroup: true }
+ );
+ });
+
+ let expectedEngineName = await searchEngineChangeFn();
+
+ await SpecialPowers.spawn(
+ browser,
+ [expectedEngineName],
+ async expectedEngineNameChild => {
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ content._searchDetails.defaultEnginesList &&
+ content._searchDetails.defaultEnginesList[
+ content._searchDetails.defaultEnginesList.length - 1
+ ] == expectedEngineNameChild
+ );
+ content.removeEventListener(
+ "ContentSearchService",
+ content._searchDetails.listener,
+ { mozSystemGroup: true }
+ );
+ delete content._searchDetails;
+ }
+ );
+}
+
+async function waitForBookmarksToolbarVisibility({
+ win = window,
+ visible,
+ message,
+}) {
+ let result = await TestUtils.waitForCondition(() => {
+ let toolbar = win.document.getElementById("PersonalToolbar");
+ return toolbar && (visible ? !toolbar.collapsed : toolbar.collapsed);
+ }, message || "waiting for toolbar to become " + (visible ? "visible" : "hidden"));
+ ok(result, message);
+ return result;
+}
+
+function isBookmarksToolbarVisible(win = window) {
+ let toolbar = win.document.getElementById("PersonalToolbar");
+ return !toolbar.collapsed;
+}
diff --git a/browser/base/content/test/about/iframe_page_csp.html b/browser/base/content/test/about/iframe_page_csp.html
new file mode 100644
index 0000000000..93a23de15d
--- /dev/null
+++ b/browser/base/content/test/about/iframe_page_csp.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Dummy iFrame page</title>
+</head>
+<body>
+<h1>iFrame CSP test</h1>
+<iframe id="theIframe"
+ sandbox="allow-scripts"
+ width=800
+ height=800
+ src="http://example.org:8000/browser/browser/base/content/test/about/csp_iframe.sjs">
+</iframe>
+</body>
+</html>
diff --git a/browser/base/content/test/about/iframe_page_xfo.html b/browser/base/content/test/about/iframe_page_xfo.html
new file mode 100644
index 0000000000..34e7f5cc52
--- /dev/null
+++ b/browser/base/content/test/about/iframe_page_xfo.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Dummy iFrame page</title>
+</head>
+<body>
+<h1>iFrame XFO test</h1>
+<iframe id="theIframe"
+ sandbox="allow-scripts"
+ width=800
+ height=800
+ src="http://example.org:8000/browser/browser/base/content/test/about/xfo_iframe.sjs">
+</iframe>
+</body>
+</html>
diff --git a/browser/base/content/test/about/print_postdata.sjs b/browser/base/content/test/about/print_postdata.sjs
new file mode 100644
index 0000000000..0e3ef38419
--- /dev/null
+++ b/browser/base/content/test/about/print_postdata.sjs
@@ -0,0 +1,25 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ if (request.method == "GET") {
+ response.write(request.queryString);
+ } else {
+ var body = new BinaryInputStream(request.bodyInputStream);
+
+ var avail;
+ var bytes = [];
+
+ while ((avail = body.available()) > 0) {
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+ }
+
+ var data = String.fromCharCode.apply(null, bytes);
+ response.bodyOutputStream.write(data, data.length);
+ }
+}
diff --git a/browser/base/content/test/about/searchSuggestionEngine.sjs b/browser/base/content/test/about/searchSuggestionEngine.sjs
new file mode 100644
index 0000000000..1978b4f665
--- /dev/null
+++ b/browser/base/content/test/about/searchSuggestionEngine.sjs
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(req, resp) {
+ let suffixes = ["foo", "bar"];
+ let data = [req.queryString, suffixes.map(s => req.queryString + s)];
+ resp.setHeader("Content-Type", "application/json", false);
+ resp.write(JSON.stringify(data));
+}
diff --git a/browser/base/content/test/about/searchSuggestionEngine.xml b/browser/base/content/test/about/searchSuggestionEngine.xml
new file mode 100644
index 0000000000..409d0b4084
--- /dev/null
+++ b/browser/base/content/test/about/searchSuggestionEngine.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/about/searchSuggestionEngine.sjs?{searchTerms}"/>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform">
+ <Param name="terms" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/base/content/test/about/slow_loading_page.sjs b/browser/base/content/test/about/slow_loading_page.sjs
new file mode 100644
index 0000000000..747390cdf7
--- /dev/null
+++ b/browser/base/content/test/about/slow_loading_page.sjs
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const DELAY_MS = 400;
+
+const HTML = `<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>hi mom!
+ </body>
+</html>`;
+
+function handleRequest(req, resp) {
+ resp.processAsync();
+
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.init(
+ () => {
+ resp.setHeader("Cache-Control", "no-cache", false);
+ resp.setHeader("Content-Type", "text/html;charset=utf-8", false);
+ resp.write(HTML);
+ resp.finish();
+ },
+ DELAY_MS,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/browser/base/content/test/about/xfo_iframe.sjs b/browser/base/content/test/about/xfo_iframe.sjs
new file mode 100644
index 0000000000..e8a6352ce0
--- /dev/null
+++ b/browser/base/content/test/about/xfo_iframe.sjs
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(request, response) {
+ // let's enjoy the amazing XFO setting
+ response.setHeader("X-Frame-Options", "SAMEORIGIN");
+
+ // let's avoid caching issues
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // everything is fine - no needs to worry :)
+ response.setStatusLine(request.httpVersion, 200);
+
+ response.setHeader("Content-Type", "text/html", false);
+ let txt =
+ "<html><head><title>XFO page</title></head>" +
+ "<body><h1>" +
+ "XFO blocked page opened in new window!" +
+ "</h1></body></html>";
+ response.write(txt);
+
+ let cookie = request.hasHeader("Cookie")
+ ? request.getHeader("Cookie")
+ : "<html><body>" +
+ "<h2 id='strictCookie'>No same site strict cookie header</h2></body>" +
+ "</html>";
+ response.write(cookie);
+
+ if (!request.hasHeader("Cookie")) {
+ let strictCookie = `matchaCookie=creamy; Domain=.example.org; SameSite=Strict`;
+ response.setHeader("Set-Cookie", strictCookie);
+ }
+}