diff options
Diffstat (limited to 'browser/base/content/test/about')
39 files changed, 4457 insertions, 0 deletions
diff --git a/browser/base/content/test/about/.eslintrc.js b/browser/base/content/test/about/.eslintrc.js new file mode 100644 index 0000000000..1779fd7f1c --- /dev/null +++ b/browser/base/content/test/about/.eslintrc.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = { + extends: ["plugin:mozilla/browser-test"], +}; diff --git a/browser/base/content/test/about/POSTSearchEngine.xml b/browser/base/content/test/about/POSTSearchEngine.xml new file mode 100644 index 0000000000..f2f884cf51 --- /dev/null +++ b/browser/base/content/test/about/POSTSearchEngine.xml @@ -0,0 +1,6 @@ +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>POST Search</ShortName> + <Url type="text/html" method="POST" template="http://mochi.test:8888/browser/browser/base/content/test/about/print_postdata.sjs"> + <Param name="searchterms" value="{searchTerms}"/> + </Url> +</OpenSearchDescription> diff --git a/browser/base/content/test/about/browser.ini b/browser/base/content/test/about/browser.ini new file mode 100644 index 0000000000..a5e9048843 --- /dev/null +++ b/browser/base/content/test/about/browser.ini @@ -0,0 +1,49 @@ +[DEFAULT] +support-files = + head.js + print_postdata.sjs + searchSuggestionEngine.sjs + searchSuggestionEngine.xml + slow_loading_page.sjs + POSTSearchEngine.xml + dummy_page.html +prefs = + browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar=false + +[browser_aboutCertError.js] +[browser_aboutCertError_clockSkew.js] +[browser_aboutCertError_exception.js] +[browser_aboutCertError_mitm.js] +[browser_aboutCertError_multiple_errors.js] +[browser_aboutCertError_noSubjectAltName.js] +[browser_aboutCertError_telemetry.js] +[browser_aboutDialog_distribution.js] +[browser_aboutHome_search_POST.js] +[browser_aboutHome_search_composing.js] +[browser_aboutHome_search_searchbar.js] +[browser_aboutHome_search_suggestion.js] +skip-if = os == "mac" || (os == "linux" && (!debug || bits == 64)) || (os == 'win' && os_version == '10.0' && bits == 64 && !debug) # Bug 1399648, bug 1402502 +[browser_aboutHome_search_telemetry.js] +[browser_aboutNetError.js] +[browser_aboutNetError_csp_iframe.js] +support-files = + iframe_page_csp.html + csp_iframe.sjs +[browser_aboutNetError_xfo_iframe.js] +support-files = + iframe_page_xfo.html + xfo_iframe.sjs +[browser_aboutNewTab_bookmarksToolbar.js] +[browser_aboutNewTab_bookmarksToolbarEmpty.js] +skip-if = tsan # Bug 1676326, highly frequent on TSan +[browser_aboutNewTab_bookmarksToolbarNewWindow.js] +skip-if = fission && tsan # Bug 1674948, perma on Fission+TSan +[browser_aboutNewTab_bookmarksToolbarPrefs.js] +[browser_aboutNewTab_defaultBrowserNotification.js] +skip-if = debug || asan || tsan || ccov # Default browser checks are skipped on debug builds, bug 1660723 +[browser_aboutStopReload.js] +[browser_aboutSupport.js] +[browser_aboutSupport_newtab_security_state.js] +[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..d0ab759831 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutCertError.js @@ -0,0 +1,604 @@ +/* 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.import( + "resource:///modules/sessionstore/TabStateFlusher.jsm" +); + +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) { + Assert.equal( + returnButton.getAttribute("autofocus"), + "true", + "returnButton has autofocus" + ); + } + // 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.loadURI(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.loadURI(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; + let shortDescText = doc.getElementById("errorShortDescText"); + Assert.ok( + shortDescText.textContent.includes("expired.example.com"), + "Should list hostname in error message." + ); + + 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 el; + await ContentTaskUtils.waitForCondition(() => { + el = doc.getElementById("errorCode"); + return el.textContent != ""; + }, "error code has been set inside the advanced button panel"); + + return { textContent: el.textContent, tagName: el.tagName }; + }); + is( + message.textContent, + "SEC_ERROR_EXPIRED_CERTIFICATE", + "Correct error message found" + ); + is(message.tagName, "a", "Error message is a link"); + + message = await SpecialPowers.spawn(bc, [], async function() { + let doc = content.document; + let errorCode = doc.getElementById("errorCode"); + errorCode.click(); + let div = doc.getElementById("certificateErrorDebugInformation"); + let text = doc.getElementById("certificateErrorText"); + + let serhelper = Cc[ + "@mozilla.org/network/serialization-helper;1" + ].getService(Ci.nsISerializationHelper); + let serializable = content.docShell.failedChannel.securityInfo + .QueryInterface(Ci.nsITransportSecurityInfo) + .QueryInterface(Ci.nsISerializable); + let serializedSecurityInfo = serhelper.serializeToString(serializable); + return { + divDisplay: content.getComputedStyle(div).display, + text: text.textContent, + securityInfoAsString: serializedSecurityInfo, + }; + }); + 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 = getCertChain(message.securityInfoAsString); + ok(message.text.includes(certChain), "Found certificate chain"); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); + } +}); + +add_task(async function checkAdvancedDetailsForHSTS() { + info( + "Loading a bad STS cert page and verifying the advanced details section" + ); + for (let useFrame of [false, true]) { + let tab = await openErrorPage(BAD_STS_CERT, useFrame); + let browser = tab.linkedBrowser; + + let bc = browser.browsingContext; + if (useFrame) { + bc = bc.children[0]; + } + + let message = await SpecialPowers.spawn(bc, [], async function() { + let doc = content.document; + let advancedButton = doc.getElementById("advancedButton"); + advancedButton.click(); + + // Wait until fluent sets the errorCode inner text. + let ec; + await ContentTaskUtils.waitForCondition(() => { + ec = doc.getElementById("errorCode"); + return ec.textContent != ""; + }, "error code has been set inside the advanced button panel"); + + let cdl = doc.getElementById("cert_domain_link"); + return { + ecTextContent: ec.textContent, + ecTagName: ec.tagName, + cdlTextContent: cdl.textContent, + cdlTagName: cdl.tagName, + }; + }); + + const badStsUri = Services.io.newURI(BAD_STS_CERT); + is( + message.ecTextContent, + "SSL_ERROR_BAD_CERT_DOMAIN", + "Correct error message found" + ); + is(message.ecTagName, "a", "Error message is a link"); + const url = badStsUri.prePath.slice(badStsUri.prePath.indexOf(".") + 1); + is(message.cdlTextContent, url, "Correct cert_domain_link contents found"); + is(message.cdlTagName, "a", "cert_domain_link is a link"); + + message = await SpecialPowers.spawn(bc, [], async function() { + let doc = content.document; + + let errorCode = doc.getElementById("errorCode"); + errorCode.click(); + let div = doc.getElementById("certificateErrorDebugInformation"); + let text = doc.getElementById("certificateErrorText"); + + let serhelper = Cc[ + "@mozilla.org/network/serialization-helper;1" + ].getService(Ci.nsISerializationHelper); + let serializable = content.docShell.failedChannel.securityInfo + .QueryInterface(Ci.nsITransportSecurityInfo) + .QueryInterface(Ci.nsISerializable); + let serializedSecurityInfo = serhelper.serializeToString(serializable); + return { + divDisplay: content.getComputedStyle(div).display, + text: text.textContent, + securityInfoAsString: serializedSecurityInfo, + }; + }); + 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 = getCertChain(message.securityInfoAsString); + 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 checkCautionClass() { + info("Checking that are potentially more dangerous get a 'caution' class"); + 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]; + } + + await SpecialPowers.spawn(bc, [useFrame], async function(subFrame) { + Assert.equal( + content.document.body.classList.contains("caution"), + !subFrame, + `Cert error body has ${subFrame ? "no" : ""} caution class` + ); + }); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); + + tab = await openErrorPage(BAD_STS_CERT, useFrame); + bc = tab.linkedBrowser.browsingContext; + await SpecialPowers.spawn(bc, [], async function() { + Assert.ok( + !content.document.body.classList.contains("caution"), + "Cert error body has no caution class" + ); + }); + + 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 => { + let titleText = content.document.querySelector(".title-text"); + await ContentTaskUtils.waitForCondition( + () => titleText.textContent, + "Error page title is initialized" + ); + let titleContent = titleText.textContent; + if (_useFrame) { + ok( + titleContent.endsWith("Security Issue"), + "Did Not Connect: Potential Security Issue" + ); + } else { + ok( + titleContent.endsWith("Risk Ahead"), + "Warning: Potential Security Risk Ahead" + ); + } + }); + 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; + + // aboutNetError.js 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. + await ContentTaskUtils.waitForCondition(() => { + let elements = [ + doc.querySelector(".title-text"), + doc.getElementById("errorCode"), + ]; + + return elements.every(elem => !!elem.textContent.trim().length); + }); + + let titleText = doc.querySelector(".title-text"); + Assert.ok( + titleText.textContent.endsWith("Security Issue"), + "Title shows Did Not Connect: Potential Security Issue" + ); + + let el = doc.getElementById("errorCode"); + + Assert.equal( + el.textContent, + "SEC_ERROR_EXPIRED_CERTIFICATE", + "Correct error message found" + ); + Assert.equal(el.tagName, "a", "Error message is 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; + + // Wait until fluent sets the errorCode inner text. + let el; + await ContentTaskUtils.waitForCondition(() => { + el = doc.getElementById("errorCode"); + return el.textContent != ""; + }, "error code has been set inside the advanced button panel"); + Assert.equal( + el.textContent, + "SEC_ERROR_EXPIRED_CERTIFICATE", + "Correct error message found" + ); + Assert.equal(el.tagName, "a", "Error message is a link"); + + let titleText = doc.querySelector(".title-text"); + Assert.equal( + titleText.textContent, + "Warning: Potential Security Risk Ahead" + ); + + let shortDescText = doc.getElementById("errorShortDescText"); + Assert.ok( + shortDescText.textContent.includes("expired.example.com"), + "Should list hostname in error message." + ); + + let whatToDoText = doc.getElementById("errorWhatToDoText"); + Assert.ok( + whatToDoText.textContent.includes("expired.example.com"), + "Should list hostname in what to do text." + ); + }); + + 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..51e7c288ed --- /dev/null +++ b/browser/base/content/test/about/browser_aboutCertError_clockSkew.js @@ -0,0 +1,155 @@ +/* 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("errorShortDescText"); + let systemDateDiv = doc.getElementById("wrongSystemTime_systemDate1"); + 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, + systemDate: systemDateDiv.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) + ); + + let formatter = new Intl.DateTimeFormat("default"); + + // 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 = formatter.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.systemDate.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..cb6a3de3eb --- /dev/null +++ b/browser/base/content/test/about/browser_aboutCertError_exception.js @@ -0,0 +1,222 @@ +/* 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 securityInfoAsString = await SpecialPowers.spawn( + browser, + [], + async function() { + let doc = content.document; + let exceptionButton = doc.getElementById("exceptionDialogButton"); + exceptionButton.click(); + let serhelper = Cc[ + "@mozilla.org/network/serialization-helper;1" + ].getService(Ci.nsISerializationHelper); + let serializable = content.docShell.failedChannel.securityInfo + .QueryInterface(Ci.nsITransportSecurityInfo) + .QueryInterface(Ci.nsISerializable); + return serhelper.serializeToString(serializable); + } + ); + + 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 cert = getSecurityInfo(securityInfoAsString).serverCert; + let hasException = certOverrideService.hasMatchingOverride( + "expired.example.com", + -1, + cert, + {}, + isTemporary + ); + ok(hasException, "Has stored an exception for the page."); + is( + isTemporary.value, + !permanentOverride, + `Has stored a ${ + permanentOverride ? "permanent" : "temporary" + } exception for the page.` + ); + + certOverrideService.clearValidityOverride("expired.example.com", -1); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + } + + Services.prefs.clearUserPref(PREF_PERMANENT_OVERRIDE); +}); + +add_task(async function checkBadStsCert() { + info("Loading a badStsCert and making sure exception button doesn't show up"); + + for (let useFrame of [false, true]) { + let tab = await openErrorPage(BAD_STS_CERT, useFrame); + let browser = tab.linkedBrowser; + + await SpecialPowers.spawn(browser, [{ frame: useFrame }], async function({ + frame, + }) { + let doc = frame + ? content.document.querySelector("iframe").contentDocument + : content.document; + let exceptionButton = doc.getElementById("exceptionDialogButton"); + ok( + ContentTaskUtils.is_hidden(exceptionButton), + "Exception button is hidden." + ); + }); + + let message = await SpecialPowers.spawn( + browser, + [{ frame: useFrame }], + async function({ frame }) { + let doc = frame + ? content.document.querySelector("iframe").contentDocument + : content.document; + let advancedButton = doc.getElementById("advancedButton"); + advancedButton.click(); + + // aboutNetError.js 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.querySelector( + ".exceptionDialogButtonContainer" + ); + ok( + ContentTaskUtils.is_hidden(exceptionButton), + "Exception button is hidden." + ); + }); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); + } + + Services.prefs.clearUserPref("security.certerror.hideAddException"); +}); + +add_task(async function checkhideAddExceptionButtonInFrames() { + info("Loading a bad cert page in a frame and verifying it's hidden."); + let tab = await openErrorPage(BAD_CERT, true); + let browser = tab.linkedBrowser; + + await SpecialPowers.spawn(browser, [], async function() { + let doc = content.document.querySelector("iframe").contentDocument; + let exceptionButton = doc.getElementById("exceptionDialogButton"); + ok( + ContentTaskUtils.is_hidden(exceptionButton), + "Exception button is hidden." + ); + }); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/base/content/test/about/browser_aboutCertError_mitm.js b/browser/base/content/test/about/browser_aboutCertError_mitm.js new file mode 100644 index 0000000000..98a36900d3 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutCertError_mitm.js @@ -0,0 +1,158 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const PREF_MITM_PRIMING = "security.certerrors.mitm.priming.enabled"; +const PREF_MITM_PRIMING_ENDPOINT = "security.certerrors.mitm.priming.endpoint"; +const PREF_MITM_CANARY_ISSUER = "security.pki.mitm_canary_issuer"; +const PREF_MITM_AUTO_ENABLE_ENTERPRISE_ROOTS = + "security.certerrors.mitm.auto_enable_enterprise_roots"; +const PREF_ENTERPRISE_ROOTS = "security.enterprise_roots.enabled"; + +const UNKNOWN_ISSUER = "https://untrusted.example.com"; + +// Check that basic MitM priming works and the MitM error page is displayed successfully. +add_task(async function checkMitmPriming() { + await SpecialPowers.pushPrefEnv({ + set: [ + [PREF_MITM_PRIMING, true], + [PREF_MITM_PRIMING_ENDPOINT, UNKNOWN_ISSUER], + ], + }); + + let browser; + let certErrorLoaded; + await BrowserTestUtils.openNewForegroundTab( + gBrowser, + () => { + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, UNKNOWN_ISSUER); + browser = gBrowser.selectedBrowser; + // The page will reload by itself after the initial canary request, so we wait + // until the AboutNetErrorLoad event has happened twice. + certErrorLoaded = new Promise(resolve => { + let loaded = 0; + let removeEventListener = BrowserTestUtils.addContentEventListener( + browser, + "AboutNetErrorLoad", + () => { + if (++loaded == 2) { + removeEventListener(); + resolve(); + } + }, + { capture: false, wantUntrusted: true } + ); + }); + }, + false + ); + + await certErrorLoaded; + + await SpecialPowers.spawn(browser, [], () => { + is( + content.document.body.getAttribute("code"), + "MOZILLA_PKIX_ERROR_MITM_DETECTED", + "MitM error page has loaded." + ); + }); + + ok(true, "Successfully loaded the MitM error page."); + + is( + Services.prefs.getStringPref(PREF_MITM_CANARY_ISSUER), + "CN=Unknown CA", + "Stored the correct issuer" + ); + + await SpecialPowers.spawn(browser, [], () => { + let mitmName1 = content.document.querySelector( + "#errorShortDescText .mitm-name" + ); + ok( + ContentTaskUtils.is_visible(mitmName1), + "Potential man in the middle is displayed" + ); + is(mitmName1.textContent, "Unknown CA", "Shows the name of the issuer."); + + let mitmName2 = content.document.querySelector( + "#errorWhatToDoText .mitm-name" + ); + ok( + ContentTaskUtils.is_visible(mitmName2), + "Potential man in the middle is displayed" + ); + is(mitmName2.textContent, "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_multiple_errors.js b/browser/base/content/test/about/browser_aboutCertError_multiple_errors.js new file mode 100644 index 0000000000..4460b52b3e --- /dev/null +++ b/browser/base/content/test/about/browser_aboutCertError_multiple_errors.js @@ -0,0 +1,153 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const EXPIRED_CERT = "https://expired.example.com/"; +const BAD_CERT = "https://mismatch.badcertdomain.example.com/"; + +const kErrors = [ + "MOZILLA_PKIX_ERROR_MITM_DETECTED", + "SEC_ERROR_UNKNOWN_ISSUER", + "SEC_ERROR_CA_CERT_INVALID", + "SEC_ERROR_UNTRUSTED_ISSUER", + "SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED", + "SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE", + "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT", + "MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED", +]; + +/** + * This is a quasi-unit test style test to check what happens + * when we encounter certificates with multiple problems. + * + * We should consistently display the most urgent message first. + */ +add_task(async function test_expired_bad_cert() { + let tab = await openErrorPage(EXPIRED_CERT); + const kExpiryLabel = "cert-error-expired-now"; + let browser = tab.linkedBrowser; + await SpecialPowers.spawn(browser, [kExpiryLabel, kErrors], async function( + knownExpiryLabel, + errorCodes + ) { + await ContentTaskUtils.waitForCondition( + () => + !!content.document.querySelectorAll(`#badCertTechnicalInfo label`) + .length + ); + // First check that for this real cert, which is simply expired and nothing else, + // we show the expiry info: + let rightLabel = content.document.querySelector( + `#badCertTechnicalInfo label[data-l10n-id="${knownExpiryLabel}"]` + ); + ok(rightLabel, "Expected a label with the right contents."); + info(content.document.querySelector("#badCertTechnicalInfo").innerHTML); + + const kExpiredErrors = errorCodes.map(err => { + return Cu.cloneInto( + { + errorCodeString: err, + isUntrusted: true, + isNotValidAtThisTime: true, + validNotBefore: 0, + validNotAfter: 86400000, + }, + content.window + ); + }); + for (let err of kExpiredErrors) { + // Test hack: invoke the content-privileged helper method with the fake cert info. + await Cu.waiveXrays(content.window).setTechnicalDetailsOnCertError(err); + let allLabels = content.document.querySelectorAll( + "#badCertTechnicalInfo label" + ); + ok( + allLabels.length, + "There should be an advanced technical description for " + + err.errorCodeString + ); + for (let label of allLabels) { + let id = label.getAttribute("data-l10n-id"); + ok( + id, + `Label for ${err.errorCodeString} should have data-l10n-id (was: ${id}).` + ); + isnot( + id, + knownExpiryLabel, + `Label for ${err.errorCodeString} should not be about expiry.` + ); + } + } + }); + await BrowserTestUtils.removeTab(tab); +}); + +/** + * The same as above, but now for alt-svc domain mismatch certs. + */ +add_task(async function test_alt_svc_bad_cert() { + let tab = await openErrorPage(BAD_CERT); + const kErrKnownPrefix = "cert-error-domain-mismatch"; + const kErrKnownAlt = "cert-error-domain-mismatch-single-nolink"; + let browser = tab.linkedBrowser; + await SpecialPowers.spawn( + browser, + [kErrKnownAlt, kErrKnownPrefix, kErrors], + async function(knownAlt, knownAltPrefix, errorCodes) { + await ContentTaskUtils.waitForCondition( + () => + !!content.document.querySelectorAll(`#badCertTechnicalInfo label`) + .length + ); + // First check that for this real cert, which is simply for the wrong domain and nothing else, + // we show the alt-svc info: + let rightLabel = content.document.querySelector( + `#badCertTechnicalInfo label[data-l10n-id="${knownAlt}"]` + ); + ok(rightLabel, "Expected a label with the right contents."); + info(content.document.querySelector("#badCertTechnicalInfo").innerHTML); + + const kAltSvcErrors = errorCodes.map(err => { + return Cu.cloneInto( + { + errorCodeString: err, + isUntrusted: true, + isDomainMismatch: true, + }, + content.window + ); + }); + for (let err of kAltSvcErrors) { + // Test hack: invoke the content-privileged helper method with the fake cert info. + await Cu.waiveXrays(content.window).setTechnicalDetailsOnCertError(err); + let allLabels = content.document.querySelectorAll( + "#badCertTechnicalInfo label" + ); + ok( + allLabels.length, + "There should be an advanced technical description for " + + err.errorCodeString + ); + for (let label of allLabels) { + let id = label.getAttribute("data-l10n-id"); + ok( + id, + `Label for ${err.errorCodeString} should have data-l10n-id (was: ${id}).` + ); + isnot( + id, + knownAlt, + `Label for ${err.errorCodeString} should not mention other domains.` + ); + ok( + !id.startsWith(knownAltPrefix), + `Label shouldn't start with ${knownAltPrefix}` + ); + } + } + } + ); + await BrowserTestUtils.removeTab(tab); +}); 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_telemetry.js b/browser/base/content/test/about/browser_aboutCertError_telemetry.js new file mode 100644 index 0000000000..5f75e2b0d3 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutCertError_telemetry.js @@ -0,0 +1,160 @@ +/* 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..253b5025fa --- /dev/null +++ b/browser/base/content/test/about/browser_aboutDialog_distribution.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from ../../../../../toolkit/mozapps/update/tests/browser/head.js */ + +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(); + + await BrowserTestUtils.waitForCondition( + () => aboutDialog.document.getElementById("currentChannel").value != "", + "Waiting for init to complete" + ); + + let distroIdField = aboutDialog.document.getElementById("distributionId"); + + is(distroIdField.value, ""); + isnot(distroIdField.style.display, "block"); + + let distroField = aboutDialog.document.getElementById("distribution"); + 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(); + + await BrowserTestUtils.waitForCondition( + () => aboutDialog.document.getElementById("currentChannel").value != "", + "Waiting for init to complete" + ); + + 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..8fb777e5f0 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutHome_search_POST.js @@ -0,0 +1,88 @@ +/* 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"); + + let currEngine = await Services.search.getDefault(); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:home" }, + async browser => { + let observerPromise = new Promise(resolve => { + let searchObserver = async function search_observer( + subject, + topic, + data + ) { + let engine = subject.QueryInterface(Ci.nsISearchEngine); + info("Observer: " + data + " for " + engine.name); + + if (data != "engine-added") { + return; + } + + if (engine.name != "POST Search") { + return; + } + + Services.obs.removeObserver( + searchObserver, + "browser-search-engine-modified" + ); + + resolve(engine); + }; + + Services.obs.addObserver( + searchObserver, + "browser-search-engine-modified" + ); + }); + + let engine; + await promiseContentSearchChange(browser, async () => { + Services.search.addOpenSearchEngine( + "http://test:80/browser/browser/base/content/test/about/POSTSearchEngine.xml", + null + ); + + engine = await observerPromise; + Services.search.setDefault(engine); + 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); + try { + await Services.search.removeEngine(engine); + } catch (ex) {} + } + ); +}); 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..7668e8a371 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutHome_search_composing.js @@ -0,0 +1,128 @@ +/* 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 BrowserTestUtils.withNewTab( + { gBrowser, url: "about:home" }, + async function(browser) { + // Add a test engine that provides suggestions and switch to it. + let currEngine = await Services.search.getDefault(); + + let engine; + await promiseContentSearchChange(browser, async () => { + engine = await promiseNewEngine("searchSuggestionEngine.xml"); + await Services.search.setDefault(engine); + return engine.name; + }); + + // Clear any search history results + await new Promise((resolve, reject) => { + FormHistory.update( + { op: "remove" }, + { + handleError(error) { + reject(error); + }, + handleCompletion(reason) { + if (!reason) { + resolve(); + } else { + reject(); + } + }, + } + ); + }); + + 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 new Promise(resolve => { + let observer = new content.MutationObserver(() => { + if (input.getAttribute("aria-expanded") == "true") { + observer.disconnect(); + ok(!table.hidden, "Search suggestion table unhidden"); + resolve(); + } + }); + observer.observe(input, { + attributes: true, + attributeFilter: ["aria-expanded"], + }); + }); + + 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; + + Services.search.setDefault(currEngine); + try { + await Services.search.removeEngine(engine); + } catch (ex) {} + } + ); +}); 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..d6250191f8 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutHome_search_searchbar.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +ChromeUtils.import( + "resource://testing-common/CustomizableUITestUtils.jsm", + this +); +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..6fe9aa22f0 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutHome_search_suggestion.js @@ -0,0 +1,76 @@ +/* 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 BrowserTestUtils.withNewTab( + { gBrowser, url: "about:home" }, + async function(browser) { + // Add a test engine that provides suggestions and switch to it. + let currEngine = await Services.search.getDefault(); + + let engine; + await promiseContentSearchChange(browser, async () => { + engine = await promiseNewEngine("searchSuggestionEngine.xml"); + await Services.search.setDefault(engine); + 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 new Promise(resolve => { + let observer = new content.MutationObserver(() => { + if (input.getAttribute("aria-expanded") == "true") { + observer.disconnect(); + ok(!table.hidden, "Search suggestion table unhidden"); + resolve(); + } + }); + observer.observe(input, { + attributes: true, + attributeFilter: ["aria-expanded"], + }); + }); + }); + + // 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 Services.search.setDefault(currEngine); + try { + await Services.search.removeEngine(engine); + } catch (ex) {} + } + ); +}); 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..e53bb31ed9 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutHome_search_telemetry.js @@ -0,0 +1,92 @@ +/* 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 BrowserTestUtils.withNewTab( + { gBrowser, url: "about:home" }, + async function(browser) { + let currEngine = await Services.search.getDefault(); + + let engine; + await promiseContentSearchChange(browser, async () => { + engine = await promiseNewEngine("searchSuggestionEngine.xml"); + await Services.search.setDefault(engine); + 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 Services.search.setDefault(currEngine); + try { + await Services.search.removeEngine(engine); + } catch (ex) {} + } + ); +}); 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..c265a5eb52 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutNetError.js @@ -0,0 +1,302 @@ +/* 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/"; + +// This includes all the cipher suite prefs we have. +const CIPHER_SUITE_PREFS = [ + "security.ssl3.dhe_rsa_aes_128_sha", + "security.ssl3.dhe_rsa_aes_256_sha", + "security.ssl3.ecdhe_ecdsa_aes_128_gcm_sha256", + "security.ssl3.ecdhe_ecdsa_aes_128_sha", + "security.ssl3.ecdhe_ecdsa_aes_256_gcm_sha384", + "security.ssl3.ecdhe_ecdsa_aes_256_sha", + "security.ssl3.ecdhe_ecdsa_chacha20_poly1305_sha256", + "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", + "security.ssl3.ecdhe_rsa_aes_128_sha", + "security.ssl3.ecdhe_rsa_aes_256_gcm_sha384", + "security.ssl3.ecdhe_rsa_aes_256_sha", + "security.ssl3.ecdhe_rsa_chacha20_poly1305_sha256", + "security.ssl3.rsa_aes_128_sha", + "security.ssl3.rsa_aes_256_sha", + "security.ssl3.rsa_aes_128_gcm_sha256", + "security.ssl3.rsa_aes_256_gcm_sha384", + "security.ssl3.rsa_des_ede3_sha", + "security.tls13.aes_128_gcm_sha256", + "security.tls13.aes_256_gcm_sha384", + "security.tls13.chacha20_poly1305_sha256", +]; + +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("security.certerrors.tls.version.show-override"); +} + +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" + ); + + // Just twiddling version will trigger the TLS 1.0 offer. So to test the + // broader UX, disable all cipher suites to trigger SSL_ERROR_SSL_DISABLED. + // This can be removed when security.tls.version.enable-deprecated is. + CIPHER_SUITE_PREFS.forEach(suitePref => { + Services.prefs.setBoolPref(suitePref, false); + }); + + // 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"); + ok( + ContentTaskUtils.is_visible(prefResetButton), + "prefResetButton should be visible" + ); + is( + prefResetButton.getAttribute("autofocus"), + "true", + "prefResetButton has autofocus" + ); + prefResetButton.click(); + }); + + info("Waiting for the page to load after the click"); + await finalLoadComplete; + + CIPHER_SUITE_PREFS.forEach(suitePref => { + Services.prefs.clearUserPref(suitePref); + }); + 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 learnMoreLink = doc.getElementById("learnMoreLink"); + ok( + ContentTaskUtils.is_visible(learnMoreLink), + "Learn More link is visible" + ); + is(learnMoreLink.getAttribute("href"), _baseURL + "connection-not-secure"); + }); + + resetPrefs(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +add_task(async function checkEnable10() { + info( + "Load a page with a deprecated TLS version, an option to enable TLS 1.0 is offered and it works" + ); + + Services.prefs.setIntPref("security.tls.version.min", 3); + // Disable TLS 1.3 so that we trigger a SSL_ERROR_UNSUPPORTED_VERSION. + // As NSS generates an alert rather than negotiating a lower version + // if we use the supported_versions extension from TLS 1.3. + Services.prefs.setIntPref("security.tls.version.max", 3); + + 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; + + // Setup an observer for the target page. + const finalLoadComplete = BrowserTestUtils.browserLoaded( + browser, + false, + TLS10_PAGE + ); + + await SpecialPowers.spawn(browser, [], async function() { + const doc = content.document; + ok( + doc.documentURI.startsWith("about:neterror"), + "Should be showing error page" + ); + + const enableTls10Button = doc.getElementById("enableTls10Button"); + ok( + ContentTaskUtils.is_visible(enableTls10Button), + "Option to re-enable TLS 1.0 is visible" + ); + enableTls10Button.click(); + + // It should not also offer to reset preferences instead. + const prefResetButton = doc.getElementById("prefResetButton"); + ok( + !ContentTaskUtils.is_visible(prefResetButton), + "prefResetButton should NOT be visible" + ); + }); + + info("Waiting for the TLS 1.0 page to load after the click"); + await finalLoadComplete; + + resetPrefs(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +add_task(async function dontOffer10WhenAlreadyEnabled() { + info("An option to enable TLS 1.0 is not offered if already enabled"); + + Services.prefs.setIntPref("security.tls.version.min", 3); + Services.prefs.setIntPref("security.tls.version.max", 3); + Services.prefs.setBoolPref("security.tls.version.enable-deprecated", true); + + let browser; + let pageLoaded; + await BrowserTestUtils.openNewForegroundTab( + gBrowser, + () => { + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, SSL3_PAGE); + browser = gBrowser.selectedBrowser; + pageLoaded = BrowserTestUtils.waitForErrorPage(browser); + }, + false + ); + + info("Loading and waiting for the net error"); + await pageLoaded; + + await SpecialPowers.spawn(browser, [], async function() { + const doc = content.document; + ok( + doc.documentURI.startsWith("about:neterror"), + "Should be showing error page" + ); + + const enableTls10Button = doc.getElementById("enableTls10Button"); + ok( + !ContentTaskUtils.is_visible(enableTls10Button), + "Option to re-enable TLS 1.0 is not visible" + ); + + // It should offer to reset preferences instead. + const prefResetButton = doc.getElementById("prefResetButton"); + ok( + ContentTaskUtils.is_visible(prefResetButton), + "prefResetButton should be visible" + ); + }); + + resetPrefs(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +add_task(async function overrideUIPref() { + info("TLS 1.0 override option isn't shown when the pref is set to false"); + + Services.prefs.setIntPref("security.tls.version.min", 3); + Services.prefs.setIntPref("security.tls.version.max", 3); + Services.prefs.setBoolPref( + "security.certerrors.tls.version.show-override", + false + ); + + 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; + + await ContentTask.spawn(browser, null, async function() { + const doc = content.document; + ok( + doc.documentURI.startsWith("about:neterror"), + "Should be showing error page" + ); + + const enableTls10Button = doc.getElementById("enableTls10Button"); + ok( + !ContentTaskUtils.is_visible(enableTls10Button), + "Option to re-enable TLS 1.0 is not visible" + ); + }); + + resetPrefs(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); 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..8b94c71aa6 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutNetError_csp_iframe.js @@ -0,0 +1,143 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const BLOCKED_PAGE = + "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", + "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.loadURI(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(() => + iframe.contentDocument.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.js 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_xfo_iframe.js b/browser/base/content/test/about/browser_aboutNetError_xfo_iframe.js new file mode 100644 index 0000000000..617fe3561c --- /dev/null +++ b/browser/base/content/test/about/browser_aboutNetError_xfo_iframe.js @@ -0,0 +1,129 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const BLOCKED_PAGE = + "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", + "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.loadURI(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(() => + iframe.contentDocument.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..3f9bff724c --- /dev/null +++ b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbar.js @@ -0,0 +1,349 @@ +/* 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() { + for (let featureEnabled of [true, false]) { + info( + "Testing with the feature " + (featureEnabled ? "enabled" : "disabled") + ); + await SpecialPowers.pushPrefEnv({ + set: [["browser.toolbars.bookmarks.2h2020", featureEnabled]], + }); + 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 + if (featureEnabled) { + await waitForBookmarksToolbarVisibility({ + visible: true, + message: "Toolbar should be visible on newtab if enabled", + }); + } + is( + isBookmarksToolbarVisible(), + featureEnabled, + "Toolbar should be visible on newtab if enabled" + ); + + // 2: Test that the toolbar is hidden when the browser is navigated away from newtab + BrowserTestUtils.loadURI(newtab.linkedBrowser, "https://example.com"); + await BrowserTestUtils.browserLoaded(newtab.linkedBrowser); + if (featureEnabled) { + 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.loadURI(newtab.linkedBrowser, "about:newtab"); + await BrowserTestUtils.browserLoaded(newtab.linkedBrowser); + if (featureEnabled) { + await waitForBookmarksToolbarVisibility({ + visible: true, + message: "Toolbar should be visible on newtab", + }); + } + is( + isBookmarksToolbarVisible(), + featureEnabled, + "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); + if (featureEnabled) { + await waitForBookmarksToolbarVisibility({ + visible: true, + message: "Toolbar is visible with switch to newtab if enabled", + }); + } + is( + isBookmarksToolbarVisible(), + featureEnabled, + "Toolbar is visible with switch to newtab if enabled" + ); + + // 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.loadURI(example.linkedBrowser, "about:newtab"); + await BrowserTestUtils.browserLoaded(example.linkedBrowser); + if (featureEnabled) { + await waitForBookmarksToolbarVisibility({ + visible: true, + message: "Toolbar is visible with newtab load if enabled", + }); + } + is( + isBookmarksToolbarVisible(), + featureEnabled, + "Toolbar is visible with newtab load if enabled" + ); + + // 8: Switching back and forth between two browsers showing about:newtab will still show the toolbar + await BrowserTestUtils.switchTab(gBrowser, newtab); + is( + isBookmarksToolbarVisible(), + featureEnabled, + "Toolbar is visible with switch to newtab if enabled" + ); + await BrowserTestUtils.switchTab(gBrowser, example); + is( + isBookmarksToolbarVisible(), + featureEnabled, + "Toolbar is visible with switch to example(newtab) if enabled" + ); + + // 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.loadURI(example.linkedBrowser, AboutNewTab.newTabURL); + await BrowserTestUtils.browserLoaded(example.linkedBrowser); + await BrowserTestUtils.switchTab(gBrowser, example); + if (featureEnabled) { + await waitForBookmarksToolbarVisibility({ visible: true }); + } + is( + isBookmarksToolbarVisible(), + featureEnabled, + "Toolbar is visible with switch to custom newtab if enabled" + ); + + await BrowserTestUtils.removeTab(newtab); + await BrowserTestUtils.removeTab(example); + AboutNewTab.newTabURL = oldNewTab; + } +}); + +add_task(async function bookmarks_toolbar_open_persisted() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.toolbars.bookmarks.2h2020", true]], + }); + 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"); + EventUtils.synthesizeMouseAtCenter(bookmarksToolbarMenu, {}); + 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"); + + EventUtils.synthesizeMouseAtCenter(alwaysMenuItem, {}); + + await waitForBookmarksToolbarVisibility({ visible: true }); + popupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown"); + EventUtils.synthesizeMouseAtCenter( + menuButton, + { type: "contextmenu" }, + window + ); + await popupShown; + bookmarksToolbarMenu = document.querySelector("#toggle_PersonalToolbar"); + EventUtils.synthesizeMouseAtCenter(bookmarksToolbarMenu, {}); + subMenu = bookmarksToolbarMenu.querySelector("menupopup"); + popupShown = BrowserTestUtils.waitForEvent(subMenu, "popupshown"); + EventUtils.synthesizeMouseAtCenter(bookmarksToolbarMenu, {}); + 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"); + EventUtils.synthesizeMouseAtCenter(bookmarksToolbarMenu, {}); + subMenu = bookmarksToolbarMenu.querySelector("menupopup"); + popupShown = BrowserTestUtils.waitForEvent(subMenu, "popupshown"); + EventUtils.synthesizeMouseAtCenter(bookmarksToolbarMenu, {}); + 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"); + EventUtils.synthesizeMouseAtCenter(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.toolbars.bookmarks.2h2020", true], + ["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", + }); + 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; + is(newtab.linkedBrowser.currentURI.spec, "about:blank", "blank is loaded"); + await waitForBookmarksToolbarVisibility({ + visible: false, + message: "Toolbar is not visible with NTP disabled", + }); + await BrowserTestUtils.removeTab(newtab); + + await SpecialPowers.pushPrefEnv({ + set: [["browser.newtabpage.enabled", true]], + }); +}); + +add_task(async function test_history_pushstate() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.toolbars.bookmarks.2h2020", true]], + }); + 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..c3fc32db4f --- /dev/null +++ b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarEmpty.js @@ -0,0 +1,176 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const bookmarksInfo = [ + { + title: "firefox", + url: "http://example.com", + }, + { + title: "rules", + url: "http://example.com/2", + }, + { + title: "yo", + url: "http://example.com/2", + }, +]; + +add_task(async function setup() { + 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() { + for (let featureEnabled of [true, false]) { + info( + "Testing with the feature " + (featureEnabled ? "enabled" : "disabled") + ); + await SpecialPowers.pushPrefEnv({ + set: [["browser.toolbars.bookmarks.2h2020", featureEnabled]], + }); + let bookmarks = await PlacesUtils.bookmarks.insertTree({ + guid: PlacesUtils.bookmarks.toolbarGuid, + children: bookmarksInfo, + }); + let example = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "https://example.com", + }); + let newtab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:newtab", + }); + let emptyMessage = document.getElementById("personal-toolbar-empty"); + + // 1: Test that the toolbar is shown in a newly opened foreground about:newtab + if (featureEnabled) { + await waitForBookmarksToolbarVisibility({ + visible: true, + message: "Toolbar should be visible on newtab if enabled", + }); + ok(emptyMessage.hidden, "Empty message is hidden with toolbar populated"); + } + + // 2: Toolbar should get hidden when switching tab to example.com + await BrowserTestUtils.switchTab(gBrowser, example); + await waitForBookmarksToolbarVisibility({ + visible: false, + message: "Toolbar should be hidden on example.com", + }); + + // 3: Remove all children of the Bookmarks Toolbar and confirm that + // the toolbar should not become visible when switching to newtab + CustomizableUI.addWidgetToArea( + "personal-bookmarks", + CustomizableUI.AREA_TABSTRIP + ); + CustomizableUI.removeWidgetFromArea("import-button"); + await BrowserTestUtils.switchTab(gBrowser, newtab); + if (featureEnabled) { + await waitForBookmarksToolbarVisibility({ + visible: true, + message: + "Toolbar is visible when there are no items in the toolbar area", + }); + ok(!emptyMessage.hidden, "Empty message is shown with toolbar empty"); + // Click the link and check we open the library: + let winPromise = BrowserTestUtils.domWindowOpenedAndLoaded(); + EventUtils.synthesizeMouseAtCenter( + emptyMessage.querySelector(".text-link"), + {} + ); + let libraryWin = await winPromise; + is( + libraryWin.document.location.href, + "chrome://browser/content/places/places.xhtml", + "Should have opened library." + ); + await BrowserTestUtils.closeWindow(libraryWin); + } + + // 4: Put personal-bookmarks back in the toolbar and confirm the toolbar is visible now + CustomizableUI.addWidgetToArea( + "personal-bookmarks", + CustomizableUI.AREA_BOOKMARKS + ); + await BrowserTestUtils.switchTab(gBrowser, example); + await BrowserTestUtils.switchTab(gBrowser, newtab); + if (featureEnabled) { + await waitForBookmarksToolbarVisibility({ + visible: true, + message: + "Toolbar should be visible with Bookmarks Toolbar Items restored", + }); + ok(emptyMessage.hidden, "Empty message is hidden with toolbar populated"); + } + + // 5: Remove all the bookmarks in the toolbar and confirm that the toolbar + // is hidden on the New Tab now + await PlacesUtils.bookmarks.remove(bookmarks); + await BrowserTestUtils.switchTab(gBrowser, example); + await BrowserTestUtils.switchTab(gBrowser, newtab); + if (featureEnabled) { + await waitForBookmarksToolbarVisibility({ + visible: true, + message: + "Toolbar is visible when there are no items or nested bookmarks in the toolbar area", + }); + ok(!emptyMessage.hidden, "Empty message is shown with toolbar empty"); + } + + // 6: Add a toolbarbutton and make sure that the toolbar appears when the button is visible + CustomizableUI.addWidgetToArea( + "characterencoding-button", + CustomizableUI.AREA_BOOKMARKS + ); + await BrowserTestUtils.switchTab(gBrowser, example); + await BrowserTestUtils.switchTab(gBrowser, newtab); + if (featureEnabled) { + await waitForBookmarksToolbarVisibility({ + visible: true, + message: + "Toolbar is visible when there is a visible button in the toolbar", + }); + ok(emptyMessage.hidden, "Empty message is hidden with button in toolbar"); + } + + await BrowserTestUtils.removeTab(newtab); + await BrowserTestUtils.removeTab(example); + CustomizableUI.reset(); + } +}); diff --git a/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarNewWindow.js b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarNewWindow.js new file mode 100644 index 0000000000..db75376aac --- /dev/null +++ b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarNewWindow.js @@ -0,0 +1,57 @@ +/* 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 url = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "slow_loading_page.sjs"; + for (let featureEnabled of [true, false]) { + for (let newTabEnabled of [true, false]) { + info( + `Testing with the feature ${ + featureEnabled ? "enabled" : "disabled" + } and newtab ${newTabEnabled ? "enabled" : "disabled"}` + ); + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.toolbars.bookmarks.2h2020", featureEnabled], + ["browser.newtabpage.enabled", newTabEnabled], + ], + }); + + let newWindowOpened = BrowserTestUtils.domWindowOpened(); + let triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); + openUILinkIn(url, "window", { triggeringPrincipal }); + + let newWin = await newWindowOpened; + + let exitConditions = { + visible: true, + }; + let slowSiteLoaded = BrowserTestUtils.firstBrowserLoaded(newWin, false); + slowSiteLoaded.then(result => { + exitConditions.earlyExit = result; + }); + + let result = await waitForBookmarksToolbarVisibilityWithExitConditions({ + win: newWin, + exitConditions, + message: "Toolbar should not become visible when loading slow site", + }); + + // The visibility promise will resolve to a Boolean whereas the browser + // load promise will resolve to an Event object. + ok( + typeof result != "boolean", + "The bookmarks toolbar should not become visible before the site is loaded" + ); + ok(!isBookmarksToolbarVisible(newWin), "Toolbar hidden on slow site"); + + await BrowserTestUtils.closeWindow(newWin); + } + } +}); 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..76f603a025 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutNewTab_bookmarksToolbarPrefs.js @@ -0,0 +1,88 @@ +/* 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 bookmarksFeatureStates = [ + ["browser.toolbars.bookmarks.2h2020", true], + ["browser.toolbars.bookmarks.2h2020", false], + ]; + let bookmarksToolbarVisibilityStates = [ + ["browser.toolbars.bookmarks.visibility", "newtab"], + ["browser.toolbars.bookmarks.visibility", "always"], + ["browser.toolbars.bookmarks.visibility", "never"], + ]; + for (let featureState of bookmarksFeatureStates) { + for (let visibilityState of bookmarksToolbarVisibilityStates) { + await SpecialPowers.pushPrefEnv({ + set: [featureState, visibilityState], + }); + + for (let privateWin of [true, false]) { + info( + `Testing with ${featureState} and ${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, + }); + } + + if (featureState[1]) { + 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], + }); + } else { + 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: "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_aboutNewTab_defaultBrowserNotification.js b/browser/base/content/test/about/browser_aboutNewTab_defaultBrowserNotification.js new file mode 100644 index 0000000000..dbda75a21b --- /dev/null +++ b/browser/base/content/test/about/browser_aboutNewTab_defaultBrowserNotification.js @@ -0,0 +1,344 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +let { DefaultBrowserNotification } = ChromeUtils.import( + "resource:///actors/AboutNewTabParent.jsm", + {} +); + +add_task(async function notification_shown_on_first_newtab_when_not_default() { + await test_with_mock_shellservice({ isDefault: false }, async function() { + ok( + !gBrowser.getNotificationBox(gBrowser.selectedBrowser) + .currentNotification, + "There shouldn't be a notification when the test starts" + ); + let firstTab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:newtab", + waitForLoad: false, + }); + let notification = await TestUtils.waitForCondition( + () => + firstTab.linkedBrowser && + gBrowser.getNotificationBox(firstTab.linkedBrowser) && + gBrowser.getNotificationBox(firstTab.linkedBrowser).currentNotification, + "waiting for notification" + ); + ok(notification, "A notification should be shown on the new tab page"); + is( + notification.getAttribute("value"), + "default-browser", + "Notification should be default browser" + ); + + let secondTab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:newtab", + waitForLoad: false, + }); + + ok( + secondTab.linkedBrowser && + gBrowser.getNotificationBox(secondTab.linkedBrowser) && + !gBrowser.getNotificationBox(secondTab.linkedBrowser) + .currentNotification, + "A notification should not be shown on the second new tab page" + ); + + gBrowser.removeTab(firstTab); + gBrowser.removeTab(secondTab); + }); +}); + +add_task(async function notification_bar_removes_itself_on_navigation() { + await test_with_mock_shellservice({ isDefault: false }, async function() { + let firstTab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:newtab", + waitForLoad: false, + }); + let notification = await TestUtils.waitForCondition( + () => + firstTab.linkedBrowser && + gBrowser.getNotificationBox(firstTab.linkedBrowser) && + gBrowser.getNotificationBox(firstTab.linkedBrowser).currentNotification, + "waiting for notification" + ); + ok(notification, "A notification should be shown on the new tab page"); + is( + notification.getAttribute("value"), + "default-browser", + "Notification should be default browser" + ); + + BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://example.com"); + + let notificationRemoved = await TestUtils.waitForCondition( + () => + firstTab.linkedBrowser && + gBrowser.getNotificationBox(firstTab.linkedBrowser) && + !gBrowser.getNotificationBox(firstTab.linkedBrowser) + .currentNotification, + "waiting for notification to get removed" + ); + ok( + notificationRemoved, + "A notification should not be shown after navigation" + ); + + gBrowser.removeTab(firstTab); + }); +}); + +add_task(async function notification_appears_on_first_navigation_to_homepage() { + await test_with_mock_shellservice({ isDefault: false }, async function() { + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:robots", + }); + ok( + tab.linkedBrowser && + gBrowser.getNotificationBox(tab.linkedBrowser) && + !gBrowser.getNotificationBox(tab.linkedBrowser).currentNotification, + "a notification should not be shown on about:robots" + ); + BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "about:home"); + + let notification = await TestUtils.waitForCondition( + () => + tab.linkedBrowser && + gBrowser.getNotificationBox(tab.linkedBrowser) && + gBrowser.getNotificationBox(tab.linkedBrowser).currentNotification, + "waiting for notification to appear on about:home after coming from about:robots" + ); + ok( + notification, + "A notification should be shown after navigation to about:home" + ); + is( + notification.getAttribute("value"), + "default-browser", + "Notification should be default browser" + ); + + gBrowser.removeTab(tab); + }); +}); + +add_task(async function clicking_button_on_notification_calls_setAsDefault() { + await test_with_mock_shellservice({ isDefault: false }, async function() { + let firstTab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:newtab", + waitForLoad: false, + }); + let notification = await TestUtils.waitForCondition( + () => + firstTab.linkedBrowser && + gBrowser.getNotificationBox(firstTab.linkedBrowser) && + gBrowser.getNotificationBox(firstTab.linkedBrowser).currentNotification, + "waiting for notification" + ); + ok(notification, "A notification should be shown on the new tab page"); + is( + notification.getAttribute("value"), + "default-browser", + "Notification should be default browser" + ); + + let shellService = window.getShellService(); + ok( + !shellService.isDefaultBrowser(), + "should not be default prior to clicking button" + ); + let button = notification.querySelector(".notification-button"); + button.click(); + ok( + shellService.isDefaultBrowser(), + "should be default after clicking button" + ); + + gBrowser.removeTab(firstTab); + }); +}); + +add_task(async function notification_not_displayed_on_private_window() { + let privateWin = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + await test_with_mock_shellservice( + { win: privateWin, isDefault: false }, + async function() { + await BrowserTestUtils.openNewForegroundTab({ + gBrowser: privateWin.gBrowser, + opening: "about:newtab", + waitForLoad: false, + }); + ok( + !privateWin.gBrowser.getNotificationBox( + privateWin.gBrowser.selectedBrowser + ).currentNotification, + "There shouldn't be a notification in the private window" + ); + await BrowserTestUtils.closeWindow(privateWin); + } + ); +}); + +add_task(async function notification_displayed_on_perm_private_window() { + SpecialPowers.pushPrefEnv({ + set: [["browser.privatebrowsing.autostart", true]], + }); + let privateWin = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + await test_with_mock_shellservice( + { win: privateWin, isDefault: false }, + async function() { + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser: privateWin.gBrowser, + opening: "about:newtab", + waitForLoad: false, + }); + ok( + PrivateBrowsingUtils.isBrowserPrivate( + privateWin.gBrowser.selectedBrowser + ), + "Browser should be private" + ); + let notification = await TestUtils.waitForCondition( + () => + tab.linkedBrowser && + gBrowser.getNotificationBox(tab.linkedBrowser) && + gBrowser.getNotificationBox(tab.linkedBrowser).currentNotification, + "waiting for notification" + ); + ok(notification, "A notification should be shown on the new tab page"); + is( + notification.getAttribute("value"), + "default-browser", + "Notification should be default browser" + ); + await BrowserTestUtils.closeWindow(privateWin); + } + ); +}); + +add_task(async function clicking_dismiss_disables_default_browser_checking() { + await test_with_mock_shellservice({ isDefault: false }, async function() { + let firstTab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:newtab", + waitForLoad: false, + }); + let notification = await TestUtils.waitForCondition( + () => + firstTab.linkedBrowser && + gBrowser.getNotificationBox(firstTab.linkedBrowser) && + gBrowser.getNotificationBox(firstTab.linkedBrowser).currentNotification, + "waiting for notification" + ); + ok(notification, "A notification should be shown on the new tab page"); + is( + notification.getAttribute("value"), + "default-browser", + "Notification should be default browser" + ); + + let closeButton = notification.querySelector(".close-icon"); + closeButton.click(); + ok( + !Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"), + "checkDefaultBrowser bar pref should be false after dismissing notification" + ); + + gBrowser.removeTab(firstTab); + }); +}); + +add_task(async function notification_not_shown_on_first_newtab_when_default() { + await test_with_mock_shellservice({ isDefault: true }, async function() { + ok( + !gBrowser.getNotificationBox(gBrowser.selectedBrowser) + .currentNotification, + "There shouldn't be a notification when the test starts" + ); + let firstTab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:newtab", + waitForLoad: false, + }); + ok( + firstTab.linkedBrowser && + gBrowser.getNotificationBox(firstTab.linkedBrowser) && + !gBrowser.getNotificationBox(firstTab.linkedBrowser) + .currentNotification, + "No notification on first tab when browser is default" + ); + + gBrowser.removeTab(firstTab); + }); +}); + +add_task(async function modal_notification_shown_when_bar_disabled() { + await test_with_mock_shellservice({ useModal: true }, async function() { + let modalOpenPromise = BrowserTestUtils.promiseAlertDialogOpen("cancel"); + + // This method is called during startup. Call it now so we don't have to test startup. + let { BrowserGlue } = ChromeUtils.import( + "resource:///modules/BrowserGlue.jsm", + {} + ); + BrowserGlue.prototype._maybeShowDefaultBrowserPrompt(); + + await modalOpenPromise; + }); +}); + +async function test_with_mock_shellservice(options, testFn) { + let win = options.win || window; + let oldShellService = win.getShellService; + let mockShellService = { + _isDefault: !!options.isDefault, + canSetDesktopBackground() {}, + isDefaultBrowserOptOut() { + return false; + }, + get shouldCheckDefaultBrowser() { + return true; + }, + isDefaultBrowser() { + return this._isDefault; + }, + setAsDefault() { + this.setDefaultBrowser(); + }, + setDefaultBrowser() { + this._isDefault = true; + }, + }; + win.getShellService = function() { + return mockShellService; + }; + let prefs = { + set: [ + ["browser.shell.checkDefaultBrowser", true], + ["browser.defaultbrowser.notificationbar", !options.useModal], + ["browser.defaultbrowser.notificationbar.checkcount", 0], + ], + }; + if (options.useModal) { + prefs.set.push(["browser.shell.skipDefaultBrowserCheckOnFirstRun", false]); + } + await SpecialPowers.pushPrefEnv(prefs); + + // Reset the state so the notification can be shown multiple times in one session + DefaultBrowserNotification.reset(); + + await testFn(); + + win.getShellService = oldShellService; +} 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..40e26238fd --- /dev/null +++ b/browser/base/content/test/about/browser_aboutStopReload.js @@ -0,0 +1,169 @@ +async function waitForNoAnimation(elt) { + return TestUtils.waitForCondition(() => !elt.hasAttribute("animate")); +} + +async function getAnimatePromise(elt) { + return BrowserTestUtils.waitForAttribute("animate", elt).then(() => + Assert.ok(true, `${elt.id} should animate`) + ); +} + +function stopReloadMutationCallback() { + Assert.ok( + false, + "stop-reload's animate attribute should not have been mutated" + ); +} + +// Force-enable the animation +gReduceMotionOverride = false; + +add_task(async function checkDontShowStopOnNewTab() { + let stopReloadContainer = document.getElementById("stop-reload-button"); + let stopReloadContainerObserver = new MutationObserver( + stopReloadMutationCallback + ); + + await waitForNoAnimation(stopReloadContainer); + stopReloadContainerObserver.observe(stopReloadContainer, { + attributeFilter: ["animate"], + }); + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:robots", + waitForStateStop: true, + }); + BrowserTestUtils.removeTab(tab); + + Assert.ok( + true, + "Test finished: stop-reload does not animate when navigating to local URI on new tab" + ); + stopReloadContainerObserver.disconnect(); +}); + +add_task(async function checkDontShowStopFromLocalURI() { + let stopReloadContainer = document.getElementById("stop-reload-button"); + let stopReloadContainerObserver = new MutationObserver( + stopReloadMutationCallback + ); + + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:robots", + waitForStateStop: true, + }); + await waitForNoAnimation(stopReloadContainer); + stopReloadContainerObserver.observe(stopReloadContainer, { + attributeFilter: ["animate"], + }); + BrowserTestUtils.loadURI(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.loadURI(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.loadURI(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.loadURI(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..8ea27a7c88 --- /dev/null +++ b/browser/base/content/test/about/browser_aboutSupport.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +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"); + } + ); +}); 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..1c49608d04 --- /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.loadOneTab("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_bug435325.js b/browser/base/content/test/about/browser_bug435325.js new file mode 100644 index 0000000000..0edd37b515 --- /dev/null +++ b/browser/base/content/test/about/browser_bug435325.js @@ -0,0 +1,57 @@ +/* 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); + + BrowserTestUtils.loadURI(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..0ade48e635 --- /dev/null +++ b/browser/base/content/test/about/browser_bug633691.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function test() { + const URL = "data:text/html,<iframe width='700' height='700'></iframe>"; + await BrowserTestUtils.withNewTab({ gBrowser, url: URL }, async function( + browser + ) { + let context = await SpecialPowers.spawn(browser, [], function() { + let iframe = content.document.querySelector("iframe"); + iframe.src = "https://expired.example.com/"; + return BrowsingContext.getFromWindow(iframe.contentWindow); + }); + await TestUtils.waitForCondition(() => { + let frame = context.currentWindowGlobal; + return frame && frame.documentURI.spec.startsWith("about:certerror"); + }); + await SpecialPowers.spawn(context, [], async function() { + await ContentTaskUtils.waitForCondition( + () => content.document.readyState == "interactive" + ); + let aP = content.document.getElementById("badCertAdvancedPanel"); + Assert.ok(aP, "Advanced content should exist"); + Assert.ok( + ContentTaskUtils.is_hidden(aP), + "Advanced content should not be visible by default" + ); + }); + }); +}); diff --git a/browser/base/content/test/about/csp_iframe.sjs b/browser/base/content/test/about/csp_iframe.sjs new file mode 100644 index 0000000000..72aa06920d --- /dev/null +++ b/browser/base/content/test/about/csp_iframe.sjs @@ -0,0 +1,29 @@ +/* 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..60b152fdf9 --- /dev/null +++ b/browser/base/content/test/about/head.js @@ -0,0 +1,278 @@ +/* eslint-env mozilla/frame-script */ + +XPCOMUtils.defineLazyModuleGetters(this, { + FormHistory: "resource://gre/modules/FormHistory.jsm", +}); + +function getSecurityInfo(securityInfoAsString) { + const serhelper = Cc[ + "@mozilla.org/network/serialization-helper;1" + ].getService(Ci.nsISerializationHelper); + let securityInfo = serhelper.deserializeObject(securityInfoAsString); + securityInfo.QueryInterface(Ci.nsITransportSecurityInfo); + return securityInfo; +} + +function getCertChain(securityInfoAsString) { + let certChain = ""; + let securityInfo = getSecurityInfo(securityInfoAsString); + for (let cert of securityInfo.failedCertChain) { + certChain += getPEMString(cert); + } + return certChain; +} + +function getPEMString(cert) { + var derb64 = cert.getBase64DERString(); + // 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.loadURI(tab.linkedBrowser, url); + } + + return loaded; +} + +/** + * Wait for the search engine to change. searchEngineChangeFn is a function + * that will be called to change the search engine. + */ +async function promiseContentSearchChange(browser, searchEngineChangeFn) { + // Add an event listener manually then perform the action, rather than using + // BrowserTestUtils.addContentEventListener as that doesn't add the listener + // early enough. + await SpecialPowers.spawn(browser, [], async () => { + // Store the results in a temporary place. + content._searchDetails = { + defaultEnginesList: [], + listener: event => { + if (event.detail.type == "CurrentState") { + content._searchDetails.defaultEnginesList.push( + content.wrappedJSObject.gContentSearchController.defaultEngine.name + ); + } + }, + }; + + // Listen using the system group to ensure that it fires after + // the default behaviour. + content.addEventListener( + "ContentSearchService", + content._searchDetails.listener, + { mozSystemGroup: true } + ); + }); + + let expectedEngineName = await searchEngineChangeFn(); + + await SpecialPowers.spawn( + browser, + [expectedEngineName], + async expectedEngineNameChild => { + await ContentTaskUtils.waitForCondition( + () => + content._searchDetails.defaultEnginesList && + content._searchDetails.defaultEnginesList[ + content._searchDetails.defaultEnginesList.length - 1 + ] == expectedEngineNameChild + ); + content.removeEventListener( + "ContentSearchService", + content._searchDetails.listener, + { mozSystemGroup: true } + ); + delete content._searchDetails; + } + ); +} + +/** + * Wait for the search engine to be added. + */ +async function promiseNewEngine(basename) { + info("Waiting for engine to be added: " + basename); + let url = getRootDirectory(gTestPath) + basename; + let engine; + try { + engine = await Services.search.addOpenSearchEngine(url, ""); + } catch (errCode) { + ok(false, "addEngine failed with error code " + errCode); + throw errCode; + } + + info("Search engine added: " + basename); + registerCleanupFunction(async () => { + try { + await Services.search.removeEngine(engine); + } catch (ex) { + /* Can't remove the engine more than once */ + } + }); + + return engine; +} + +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; +} + +async function waitForBookmarksToolbarVisibilityWithExitConditions({ + win = window, + exitConditions, + message, +}) { + let result = await TestUtils.waitForCondition(() => { + if (exitConditions.earlyExit) { + return exitConditions.earlyExit; + } + let toolbar = win.document.getElementById("PersonalToolbar"); + return ( + toolbar && + (exitConditions.visible ? !toolbar.collapsed : toolbar.collapsed) + ); + }, message || "waiting for toolbar to become " + (exitConditions.visible ? "visible" : "hidden")); + if (exitConditions.earlyExit) { + ok(true, "Early exit condition met"); + } else { + ok(false, message); + } + return exitConditions.earlyExit || result; +} 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..4175a24805 --- /dev/null +++ b/browser/base/content/test/about/print_postdata.sjs @@ -0,0 +1,22 @@ +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..d7d99e31c3 --- /dev/null +++ b/browser/base/content/test/about/xfo_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 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); + } +} |