diff options
Diffstat (limited to '')
40 files changed, 4355 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.toml b/browser/base/content/test/about/browser.toml new file mode 100644 index 0000000000..900c6b7140 --- /dev/null +++ b/browser/base/content/test/about/browser.toml @@ -0,0 +1,88 @@ +[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)", + "win10_2009 && 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"] +fail-if = ["a11y_checks"] # Bug 1854233 text-link may not be focusable +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..9af82b807f --- /dev/null +++ b/browser/base/content/test/about/browser_aboutCertError.js @@ -0,0 +1,551 @@ +/* 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.startLoadingURIString(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.startLoadingURIString(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.toLowerCase(), + }; + }); + 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.toLowerCase(), + cdlTextContent: cdl.textContent, + cdlTagName: cdl.tagName.toLowerCase(), + }; + }); + + 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.toLowerCase(), + "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.toLowerCase(), + "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..ebc4344712 --- /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.isHidden(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.isHidden(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.isHidden(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..0d7ebbe4f2 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutCertError_mitm.js @@ -0,0 +1,159 @@ +/* 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], + [PREF_ENTERPRISE_ROOTS, false], + ], + }); + + 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..bed9517e34 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutCertError_offlineSupport.js @@ -0,0 +1,52 @@ +/* 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"); + Assert.equal( + 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..191b86be65 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutHome_search_POST.js @@ -0,0 +1,66 @@ +/* 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 engine; + await promiseContentSearchChange(browser, async () => { + engine = await SearchTestUtils.promiseNewSearchEngine({ + url: "https://example.com/browser/browser/base/content/test/about/POSTSearchEngine.xml", + setAsDefault: true, + }); + 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..fddceda6f1 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutHome_search_suggestion.js @@ -0,0 +1,74 @@ +/* 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, + }); + 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..8222a0f6e8 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutNetError.js @@ -0,0 +1,242 @@ +/* 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.isVisible(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.isVisible(tlsVersionNotice), + "TLS version notice is visible" + ); + + const learnMoreLink = doc.getElementById("learnMoreLink"); + ok(ContentTaskUtils.isVisible(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.isVisible(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.startLoadingURIString(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.startLoadingURIString(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.startLoadingURIString(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..c8028a4cf4 --- /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.startLoadingURIString(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..d82d4d6877 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutNetError_native_fallback.js @@ -0,0 +1,176 @@ +/* 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; + Assert.equal( + 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 { + Assert.notEqual( + 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..f5fd240643 --- /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.startLoadingURIString(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..aaa6eac8bd --- /dev/null +++ b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbar.js @@ -0,0 +1,317 @@ +/* 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.startLoadingURIString( + 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.startLoadingURIString(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.startLoadingURIString(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.startLoadingURIString( + 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..8175c53c5d --- /dev/null +++ b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarEmpty.js @@ -0,0 +1,183 @@ +/* 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", + }, +]; + +async function emptyToolbarMessageVisible(visible, win = window) { + info("Empty toolbar message should be " + (visible ? "visible" : "hidden")); + let emptyMessage = win.document.getElementById("personal-toolbar-empty"); + await BrowserTestUtils.waitForMutationCondition( + emptyMessage, + { attributes: true, attributeFilter: ["hidden"] }, + () => emptyMessage.hidden != visible + ); +} + +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 exampleTab = 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 + let placesItems = document.getElementById("PlacesToolbarItems"); + let promiseBookmarksOnToolbar = BrowserTestUtils.waitForMutationCondition( + placesItems, + { childList: true }, + () => placesItems.childNodes.length + ); + await waitForBookmarksToolbarVisibility({ + visible: true, + message: "Toolbar should be visible on newtab", + }); + await promiseBookmarksOnToolbar; + await emptyToolbarMessageVisible(false); + + // 2: Toolbar should get hidden when switching tab to example.com + let promiseToolbar = waitForBookmarksToolbarVisibility({ + visible: false, + message: "Toolbar should be hidden on example.com", + }); + await BrowserTestUtils.switchTab(gBrowser, exampleTab); + await promiseToolbar; + + // 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", + }); + await emptyToolbarMessageVisible(true); + + // 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, exampleTab); + await BrowserTestUtils.switchTab(gBrowser, newtab); + promiseBookmarksOnToolbar = BrowserTestUtils.waitForMutationCondition( + placesItems, + { childList: true }, + () => placesItems.childNodes.length + ); + await waitForBookmarksToolbarVisibility({ + visible: true, + message: "Toolbar should be visible with Bookmarks Toolbar Items restored", + }); + await promiseBookmarksOnToolbar; + await emptyToolbarMessageVisible(false); + + // 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, exampleTab); + 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", + }); + await emptyToolbarMessageVisible(true); + + // 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, exampleTab); + await BrowserTestUtils.switchTab(gBrowser, newtab); + await waitForBookmarksToolbarVisibility({ + visible: true, + message: "Toolbar is visible when there is a visible button in the toolbar", + }); + await emptyToolbarMessageVisible(false); + + await BrowserTestUtils.removeTab(newtab); + await BrowserTestUtils.removeTab(exampleTab); + 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..21507509bc --- /dev/null +++ b/browser/base/content/test/about/browser_aboutStopReload.js @@ -0,0 +1,175 @@ +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.startLoadingURIString(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.startLoadingURIString(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.startLoadingURIString( + 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.startLoadingURIString( + 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..6fef282730 --- /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.startLoadingURIString(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..bc70aeea31 --- /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.isHidden(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..6c0f1242b6 --- /dev/null +++ b/browser/base/content/test/about/head.js @@ -0,0 +1,221 @@ +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.startLoadingURIString(tab.linkedBrowser, url); + } + + return loaded; +} + +/** + * Wait for the user's default 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 == "CurrentEngine") { + 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, + `Waiting for ${expectedEngineNameChild} to be set` + ); + 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); + } +} |