diff options
Diffstat (limited to 'browser/base/content/test/siteIdentity')
88 files changed, 6850 insertions, 0 deletions
diff --git a/browser/base/content/test/siteIdentity/browser.ini b/browser/base/content/test/siteIdentity/browser.ini new file mode 100644 index 0000000000..724669f18a --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser.ini @@ -0,0 +1,152 @@ +[DEFAULT] +support-files = + head.js + dummy_page.html + !/image/test/mochitest/blue.png + +[browser_about_blank_same_document_tabswitch.js] +https_first_disabled = true +support-files = + open-self-from-frame.html +[browser_bug1045809.js] +tags = mcb +support-files = + file_bug1045809_1.html + file_bug1045809_2.html +[browser_bug822367.js] +tags = mcb +support-files = + file_bug822367_1.html + file_bug822367_1.js + file_bug822367_2.html + file_bug822367_3.html + file_bug822367_4.html + file_bug822367_4.js + file_bug822367_4B.html + file_bug822367_5.html + file_bug822367_6.html +[browser_bug902156.js] +tags = mcb +support-files = + file_bug902156.js + file_bug902156_1.html + file_bug902156_2.html + file_bug902156_3.html +[browser_bug906190.js] +tags = mcb +support-files = + file_bug906190_1.html + file_bug906190_2.html + file_bug906190_3_4.html + file_bug906190_redirected.html + file_bug906190.js + file_bug906190.sjs +[browser_check_identity_state.js] +https_first_disabled = true +skip-if = + os == "win" && os_version == "6.1" # Skip on Azure - frequent failure +[browser_check_identity_state_pdf.js] +https_first_disabled = true +support-files = + file_pdf.pdf + file_pdf_blob.html +[browser_csp_block_all_mixedcontent.js] +tags = mcb +support-files = + file_csp_block_all_mixedcontent.html + file_csp_block_all_mixedcontent.js +[browser_deprecatedTLSVersions.js] +[browser_geolocation_indicator.js] +[browser_getSecurityInfo.js] +https_first_disabled = true +support-files = + dummy_iframe_page.html +[browser_identityBlock_flicker.js] +[browser_identityBlock_focus.js] +support-files = ../permissions/permissions.html +[browser_identityIcon_img_url.js] +https_first_disabled = true +support-files = + file_mixedPassiveContent.html + file_csp_block_all_mixedcontent.html +[browser_identityPopup_HttpsOnlyMode.js] +[browser_identityPopup_clearSiteData.js] +skip-if = (os == "linux" && bits == 64) # Bug 1577395 +[browser_identityPopup_clearSiteData_extensions.js] +[browser_identityPopup_custom_roots.js] +https_first_disabled = true +[browser_identityPopup_focus.js] +skip-if = + verify + os == "linux" && (asan || tsan) # Bug 1723899 +[browser_identity_UI.js] +https_first_disabled = true +[browser_iframe_navigation.js] +https_first_disabled = true +support-files = + iframe_navigation.html +[browser_ignore_same_page_navigation.js] +[browser_mcb_redirect.js] +https_first_disabled = true +tags = mcb +support-files = + test_mcb_redirect.html + test_mcb_redirect_image.html + test_mcb_double_redirect_image.html + test_mcb_redirect.js + test_mcb_redirect.sjs +[browser_mixedContentFramesOnHttp.js] +https_first_disabled = true +tags = mcb +support-files = + file_mixedContentFramesOnHttp.html + file_mixedPassiveContent.html +[browser_mixedContentFromOnunload.js] +https_first_disabled = true +tags = mcb +support-files = + file_mixedContentFromOnunload.html + file_mixedContentFromOnunload_test1.html + file_mixedContentFromOnunload_test2.html +[browser_mixed_content_cert_override.js] +skip-if = verify +tags = mcb +support-files = + test-mixedcontent-securityerrors.html +[browser_mixed_content_with_navigation.js] +tags = mcb +support-files = + file_mixedPassiveContent.html + file_bug1045809_1.html +[browser_mixed_passive_content_indicator.js] +tags = mcb +support-files = + simple_mixed_passive.html +[browser_mixedcontent_securityflags.js] +tags = mcb +support-files = + test-mixedcontent-securityerrors.html +[browser_navigation_failures.js] +[browser_no_mcb_for_loopback.js] +tags = mcb +support-files = + ../general/moz.png + test_no_mcb_for_loopback.html +[browser_no_mcb_for_onions.js] +tags = mcb +support-files = + test_no_mcb_for_onions.html +[browser_no_mcb_on_http_site.js] +https_first_disabled = true +tags = mcb +support-files = + test_no_mcb_on_http_site_img.html + test_no_mcb_on_http_site_img.css + test_no_mcb_on_http_site_font.html + test_no_mcb_on_http_site_font.css + test_no_mcb_on_http_site_font2.html + test_no_mcb_on_http_site_font2.css +[browser_secure_transport_insecure_scheme.js] +https_first_disabled = true +[browser_session_store_pageproxystate.js] +[browser_tab_sharing_state.js] diff --git a/browser/base/content/test/siteIdentity/browser_about_blank_same_document_tabswitch.js b/browser/base/content/test/siteIdentity/browser_about_blank_same_document_tabswitch.js new file mode 100644 index 0000000000..5e58b4bedb --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_about_blank_same_document_tabswitch.js @@ -0,0 +1,79 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_PATH = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.org" +); + +const TEST_PAGE = TEST_PATH + "open-self-from-frame.html"; + +add_task(async function test_identityBlock_inherited_blank() { + await BrowserTestUtils.withNewTab("https://example.com", async browser => { + let identityBox = document.getElementById("identity-box"); + // Ensure we remove the 3rd party storage permission for example.org, or + // it'll mess up other tests: + let principal = browser.contentPrincipal; + registerCleanupFunction(() => { + Services.perms.removeFromPrincipal( + principal, + "3rdPartyStorage^http://example.org" + ); + }); + is( + identityBox.className, + "verifiedDomain", + "Should indicate a secure site." + ); + // Open a popup from the web content. + let popupPromise = BrowserTestUtils.waitForNewWindow(); + await SpecialPowers.spawn(browser, [TEST_PAGE], testPage => { + content.open(testPage, "_blank", "height=300,width=300"); + }); + // Open a tab back in the main window: + let popup = await popupPromise; + info("Opened popup"); + let popupBC = popup.gBrowser.selectedBrowser.browsingContext; + await TestUtils.waitForCondition( + () => popupBC.children[0]?.currentWindowGlobal + ); + + info("Waiting for button to appear"); + await SpecialPowers.spawn(popupBC.children[0], [], async () => { + await ContentTaskUtils.waitForCondition(() => + content.document.querySelector("button") + ); + }); + + info("Got frame contents."); + + let otherTabPromise = BrowserTestUtils.waitForLocationChange( + gBrowser, + TEST_PAGE + ); + info("Clicking button"); + await BrowserTestUtils.synthesizeMouseAtCenter( + "button", + {}, + popupBC.children[0] + ); + info("Waiting for tab"); + await otherTabPromise; + + ok( + gURLBar.value.startsWith("example.org/"), + "URL bar value should be correct, was " + gURLBar.value + ); + is( + identityBox.className, + "notSecure", + "Identity box should have been updated." + ); + + await BrowserTestUtils.removeTab(gBrowser.selectedTab); + await BrowserTestUtils.closeWindow(popup); + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_bug1045809.js b/browser/base/content/test/siteIdentity/browser_bug1045809.js new file mode 100644 index 0000000000..b39d669d0b --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_bug1045809.js @@ -0,0 +1,105 @@ +// Test that the Mixed Content Doorhanger Action to re-enable protection works + +const PREF_ACTIVE = "security.mixed_content.block_active_content"; +const PREF_INSECURE = "security.insecure_connection_icon.enabled"; +const TEST_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "file_bug1045809_1.html"; + +var origBlockActive; + +add_task(async function () { + registerCleanupFunction(function () { + Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive); + gBrowser.removeCurrentTab(); + }); + + // Store original preferences so we can restore settings after testing + origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE); + + // Make sure mixed content blocking is on + Services.prefs.setBoolPref(PREF_ACTIVE, true); + + let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser)); + + // Check with insecure lock disabled + await SpecialPowers.pushPrefEnv({ set: [[PREF_INSECURE, false]] }); + await runTests(tab); + + // Check with insecure lock disabled + await SpecialPowers.pushPrefEnv({ set: [[PREF_INSECURE, true]] }); + await runTests(tab); +}); + +async function runTests(tab) { + // Test 1: mixed content must be blocked + await promiseTabLoadEvent(tab, TEST_URL); + await test1(gBrowser.getBrowserForTab(tab)); + + await promiseTabLoadEvent(tab); + // Test 2: mixed content must NOT be blocked + await test2(gBrowser.getBrowserForTab(tab)); + + // Test 3: mixed content must be blocked again + await promiseTabLoadEvent(tab); + await test3(gBrowser.getBrowserForTab(tab)); +} + +async function test1(gTestBrowser) { + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + + await SpecialPowers.spawn(gTestBrowser, [], function () { + let iframe = content.document.getElementsByTagName("iframe")[0]; + + SpecialPowers.spawn(iframe, [], () => { + let container = content.document.getElementById("mixedContentContainer"); + is(container, null, "Mixed Content is NOT to be found in Test1"); + }); + }); + + // Disable Mixed Content Protection for the page (and reload) + gIdentityHandler.disableMixedContentProtection(); +} + +async function test2(gTestBrowser) { + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: true, + activeBlocked: false, + passiveLoaded: false, + }); + + await SpecialPowers.spawn(gTestBrowser, [], function () { + let iframe = content.document.getElementsByTagName("iframe")[0]; + + SpecialPowers.spawn(iframe, [], () => { + let container = content.document.getElementById("mixedContentContainer"); + isnot(container, null, "Mixed Content is to be found in Test2"); + }); + }); + + // Re-enable Mixed Content Protection for the page (and reload) + gIdentityHandler.enableMixedContentProtection(); +} + +async function test3(gTestBrowser) { + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + + await SpecialPowers.spawn(gTestBrowser, [], function () { + let iframe = content.document.getElementsByTagName("iframe")[0]; + + SpecialPowers.spawn(iframe, [], () => { + let container = content.document.getElementById("mixedContentContainer"); + is(container, null, "Mixed Content is NOT to be found in Test3"); + }); + }); +} diff --git a/browser/base/content/test/siteIdentity/browser_bug822367.js b/browser/base/content/test/siteIdentity/browser_bug822367.js new file mode 100644 index 0000000000..881c920899 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_bug822367.js @@ -0,0 +1,254 @@ +/* + * User Override Mixed Content Block - Tests for Bug 822367 + */ + +const PREF_DISPLAY = "security.mixed_content.block_display_content"; +const PREF_DISPLAY_UPGRADE = "security.mixed_content.upgrade_display_content"; +const PREF_ACTIVE = "security.mixed_content.block_active_content"; + +// We alternate for even and odd test cases to simulate different hosts +const HTTPS_TEST_ROOT = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); +const HTTPS_TEST_ROOT_2 = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://test1.example.com" +); + +var gTestBrowser = null; + +add_task(async function test() { + await SpecialPowers.pushPrefEnv({ + set: [ + [PREF_DISPLAY, true], + [PREF_DISPLAY_UPGRADE, false], + [PREF_ACTIVE, true], + ], + }); + + var newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + gTestBrowser = gBrowser.selectedBrowser; + newTab.linkedBrowser.stop(); + + // Mixed Script Test + var url = HTTPS_TEST_ROOT + "file_bug822367_1.html"; + BrowserTestUtils.loadURIString(gTestBrowser, url); + await BrowserTestUtils.browserLoaded(gTestBrowser, false, url); +}); + +// Mixed Script Test +add_task(async function MixedTest1A() { + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + + gTestBrowser.ownerGlobal.gIdentityHandler.disableMixedContentProtection(); + await BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(async function MixedTest1B() { + await SpecialPowers.spawn(gTestBrowser, [], async function () { + await ContentTaskUtils.waitForCondition( + () => content.document.getElementById("p1").innerHTML == "hello", + "Waited too long for mixed script to run in Test 1" + ); + }); + gTestBrowser.ownerGlobal.gIdentityHandler.enableMixedContentProtectionNoReload(); +}); + +// Mixed Display Test - Doorhanger should not appear +add_task(async function MixedTest2() { + var url = HTTPS_TEST_ROOT_2 + "file_bug822367_2.html"; + BrowserTestUtils.loadURIString(gTestBrowser, url); + await BrowserTestUtils.browserLoaded(gTestBrowser, false, url); + + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: false, + activeBlocked: false, + passiveLoaded: false, + }); +}); + +// Mixed Script and Display Test - User Override should cause both the script and the image to load. +add_task(async function MixedTest3() { + var url = HTTPS_TEST_ROOT + "file_bug822367_3.html"; + BrowserTestUtils.loadURIString(gTestBrowser, url); + await BrowserTestUtils.browserLoaded(gTestBrowser, false, url); +}); + +add_task(async function MixedTest3A() { + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + + gTestBrowser.ownerGlobal.gIdentityHandler.disableMixedContentProtection(); + await BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(async function MixedTest3B() { + await SpecialPowers.spawn(gTestBrowser, [], async function () { + let p1 = ContentTaskUtils.waitForCondition( + () => content.document.getElementById("p1").innerHTML == "hello", + "Waited too long for mixed script to run in Test 3" + ); + let p2 = ContentTaskUtils.waitForCondition( + () => content.document.getElementById("p2").innerHTML == "bye", + "Waited too long for mixed image to load in Test 3" + ); + await Promise.all([p1, p2]); + }); + + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: true, + activeBlocked: false, + passiveLoaded: true, + }); + gTestBrowser.ownerGlobal.gIdentityHandler.enableMixedContentProtectionNoReload(); +}); + +// Location change - User override on one page doesn't propagate to another page after location change. +add_task(async function MixedTest4() { + var url = HTTPS_TEST_ROOT_2 + "file_bug822367_4.html"; + BrowserTestUtils.loadURIString(gTestBrowser, url); + await BrowserTestUtils.browserLoaded(gTestBrowser, false, url); +}); + +let preLocationChangePrincipal = null; +add_task(async function MixedTest4A() { + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + + preLocationChangePrincipal = gTestBrowser.contentPrincipal; + gTestBrowser.ownerGlobal.gIdentityHandler.disableMixedContentProtection(); + await BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(async function MixedTest4B() { + let url = HTTPS_TEST_ROOT + "file_bug822367_4B.html"; + await SpecialPowers.spawn(gTestBrowser, [url], async function (wantedUrl) { + await ContentTaskUtils.waitForCondition( + () => content.document.location == wantedUrl, + "Waited too long for mixed script to run in Test 4" + ); + }); +}); + +add_task(async function MixedTest4C() { + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + + await SpecialPowers.spawn(gTestBrowser, [], async function () { + await ContentTaskUtils.waitForCondition( + () => content.document.getElementById("p1").innerHTML == "", + "Mixed script loaded in test 4 after location change!" + ); + }); + SitePermissions.removeFromPrincipal( + preLocationChangePrincipal, + "mixed-content" + ); +}); + +// Mixed script attempts to load in a document.open() +add_task(async function MixedTest5() { + var url = HTTPS_TEST_ROOT + "file_bug822367_5.html"; + BrowserTestUtils.loadURIString(gTestBrowser, url); + await BrowserTestUtils.browserLoaded(gTestBrowser, false, url); +}); + +add_task(async function MixedTest5A() { + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + + gTestBrowser.ownerGlobal.gIdentityHandler.disableMixedContentProtection(); + await BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(async function MixedTest5B() { + await SpecialPowers.spawn(gTestBrowser, [], async function () { + await ContentTaskUtils.waitForCondition( + () => content.document.getElementById("p1").innerHTML == "hello", + "Waited too long for mixed script to run in Test 5" + ); + }); + gTestBrowser.ownerGlobal.gIdentityHandler.enableMixedContentProtectionNoReload(); +}); + +// Mixed script attempts to load in a document.open() that is within an iframe. +add_task(async function MixedTest6() { + var url = HTTPS_TEST_ROOT_2 + "file_bug822367_6.html"; + BrowserTestUtils.loadURIString(gTestBrowser, url); + await BrowserTestUtils.browserLoaded(gTestBrowser, false, url); +}); + +add_task(async function MixedTest6A() { + gTestBrowser.removeEventListener("load", MixedTest6A, true); + let { gIdentityHandler } = gTestBrowser.ownerGlobal; + + await BrowserTestUtils.waitForCondition( + () => + gIdentityHandler._identityBox.classList.contains("mixedActiveBlocked"), + "Waited too long for control center to get mixed active blocked state" + ); +}); + +add_task(async function MixedTest6B() { + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + + gTestBrowser.ownerGlobal.gIdentityHandler.disableMixedContentProtection(); + + await BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +add_task(async function MixedTest6C() { + await SpecialPowers.spawn(gTestBrowser, [], async function () { + function test() { + try { + return ( + content.document + .getElementById("f1") + .contentDocument.getElementById("p1").innerHTML == "hello" + ); + } catch (e) { + return false; + } + } + + await ContentTaskUtils.waitForCondition( + test, + "Waited too long for mixed script to run in Test 6" + ); + }); +}); + +add_task(async function MixedTest6D() { + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: true, + activeBlocked: false, + passiveLoaded: false, + }); + gTestBrowser.ownerGlobal.gIdentityHandler.enableMixedContentProtectionNoReload(); +}); + +add_task(async function cleanup() { + gBrowser.removeCurrentTab(); +}); diff --git a/browser/base/content/test/siteIdentity/browser_bug902156.js b/browser/base/content/test/siteIdentity/browser_bug902156.js new file mode 100644 index 0000000000..3485771427 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_bug902156.js @@ -0,0 +1,171 @@ +/* + * Description of the Tests for + * - Bug 902156: Persist "disable protection" option for Mixed Content Blocker + * + * 1. Navigate to the same domain via document.location + * - Load a html page which has mixed content + * - Control Center button to disable protection appears - we disable it + * - Load a new page from the same origin using document.location + * - Control Center button should not appear anymore! + * + * 2. Navigate to the same domain via simulateclick for a link on the page + * - Load a html page which has mixed content + * - Control Center button to disable protection appears - we disable it + * - Load a new page from the same origin simulating a click + * - Control Center button should not appear anymore! + * + * 3. Navigate to a differnet domain and show the content is still blocked + * - Load a different html page which has mixed content + * - Control Center button to disable protection should appear again because + * we navigated away from html page where we disabled the protection. + * + * Note, for all tests we set gHttpTestRoot to use 'https'. + */ + +const PREF_ACTIVE = "security.mixed_content.block_active_content"; + +// We alternate for even and odd test cases to simulate different hosts. +const HTTPS_TEST_ROOT_1 = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://test1.example.com" +); +const HTTPS_TEST_ROOT_2 = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://test2.example.com" +); + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ set: [[PREF_ACTIVE, true]] }); +}); + +add_task(async function test1() { + let url = HTTPS_TEST_ROOT_1 + "file_bug902156_1.html"; + await BrowserTestUtils.withNewTab(url, async function (browser) { + await assertMixedContentBlockingState(browser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + + // Disable Mixed Content Protection for the page (and reload) + let browserLoaded = BrowserTestUtils.browserLoaded(browser, false, url); + let { gIdentityHandler } = browser.ownerGlobal; + gIdentityHandler.disableMixedContentProtection(); + await browserLoaded; + + await SpecialPowers.spawn(browser, [], async function () { + let expected = "Mixed Content Blocker disabled"; + await ContentTaskUtils.waitForCondition( + () => + content.document.getElementById("mctestdiv").innerHTML == expected, + "Error: Waited too long for mixed script to run in Test 1" + ); + + let actual = content.document.getElementById("mctestdiv").innerHTML; + is( + actual, + "Mixed Content Blocker disabled", + "OK: Executed mixed script in Test 1" + ); + }); + + // The Script loaded after we disabled the page, now we are going to reload the + // page and see if our decision is persistent + url = HTTPS_TEST_ROOT_1 + "file_bug902156_2.html"; + browserLoaded = BrowserTestUtils.browserLoaded(browser, false, url); + BrowserTestUtils.loadURIString(browser, url); + await browserLoaded; + + // The Control Center button should appear but isMixedContentBlocked should be NOT true, + // because our decision of disabling the mixed content blocker is persistent. + await assertMixedContentBlockingState(browser, { + activeLoaded: true, + activeBlocked: false, + passiveLoaded: false, + }); + await SpecialPowers.spawn(browser, [], function () { + let actual = content.document.getElementById("mctestdiv").innerHTML; + is( + actual, + "Mixed Content Blocker disabled", + "OK: Executed mixed script in Test 1" + ); + }); + gIdentityHandler.enableMixedContentProtection(); + }); +}); + +// ------------------------ Test 2 ------------------------------ + +add_task(async function test2() { + let url = HTTPS_TEST_ROOT_2 + "file_bug902156_2.html"; + await BrowserTestUtils.withNewTab(url, async function (browser) { + await assertMixedContentBlockingState(browser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + + // Disable Mixed Content Protection for the page (and reload) + let browserLoaded = BrowserTestUtils.browserLoaded(browser, false, url); + let { gIdentityHandler } = browser.ownerGlobal; + gIdentityHandler.disableMixedContentProtection(); + await browserLoaded; + + await SpecialPowers.spawn(browser, [], async function () { + let expected = "Mixed Content Blocker disabled"; + await ContentTaskUtils.waitForCondition( + () => + content.document.getElementById("mctestdiv").innerHTML == expected, + "Error: Waited too long for mixed script to run in Test 2" + ); + + let actual = content.document.getElementById("mctestdiv").innerHTML; + is( + actual, + "Mixed Content Blocker disabled", + "OK: Executed mixed script in Test 2" + ); + }); + + // The Script loaded after we disabled the page, now we are going to reload the + // page and see if our decision is persistent + url = HTTPS_TEST_ROOT_2 + "file_bug902156_1.html"; + browserLoaded = BrowserTestUtils.browserLoaded(browser, false, url); + // reload the page using the provided link in the html file + await SpecialPowers.spawn(browser, [], function () { + let mctestlink = content.document.getElementById("mctestlink"); + mctestlink.click(); + }); + await browserLoaded; + + // The Control Center button should appear but isMixedContentBlocked should be NOT true, + // because our decision of disabling the mixed content blocker is persistent. + await assertMixedContentBlockingState(browser, { + activeLoaded: true, + activeBlocked: false, + passiveLoaded: false, + }); + + await SpecialPowers.spawn(browser, [], function () { + let actual = content.document.getElementById("mctestdiv").innerHTML; + is( + actual, + "Mixed Content Blocker disabled", + "OK: Executed mixed script in Test 2" + ); + }); + gIdentityHandler.enableMixedContentProtection(); + }); +}); + +add_task(async function test3() { + let url = HTTPS_TEST_ROOT_1 + "file_bug902156_3.html"; + await BrowserTestUtils.withNewTab(url, async function (browser) { + await assertMixedContentBlockingState(browser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_bug906190.js b/browser/base/content/test/siteIdentity/browser_bug906190.js new file mode 100644 index 0000000000..a0410e76cb --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_bug906190.js @@ -0,0 +1,340 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Tests the persistence of the "disable protection" option for Mixed Content + * Blocker in child tabs (bug 906190). + */ + +requestLongerTimeout(2); + +// We use the different urls for testing same origin checks before allowing +// mixed content on child tabs. +const HTTPS_TEST_ROOT_1 = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://test1.example.com" +); +const HTTPS_TEST_ROOT_2 = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://test2.example.com" +); + +/** + * For all tests, we load the pages over HTTPS and test both: + * - |CTRL+CLICK| + * - |RIGHT CLICK -> OPEN LINK IN TAB| + */ +async function doTest( + parentTabSpec, + childTabSpec, + testTaskFn, + waitForMetaRefresh +) { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: parentTabSpec, + }, + async function (browser) { + // As a sanity check, test that active content has been blocked as expected. + await assertMixedContentBlockingState(gBrowser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + + // Disable the Mixed Content Blocker for the page, which reloads it. + let promiseReloaded = BrowserTestUtils.browserLoaded(browser); + let principal = gBrowser.contentPrincipal; + gIdentityHandler.disableMixedContentProtection(); + await promiseReloaded; + + // Wait for the script in the page to update the contents of the test div. + await SpecialPowers.spawn( + browser, + [childTabSpec], + async childTabSpecContent => { + let testDiv = content.document.getElementById("mctestdiv"); + await ContentTaskUtils.waitForCondition( + () => testDiv.innerHTML == "Mixed Content Blocker disabled" + ); + + // Add the link for the child tab to the page. + let mainDiv = content.document.createElement("div"); + + // eslint-disable-next-line no-unsanitized/property + mainDiv.innerHTML = + '<p><a id="linkToOpenInNewTab" href="' + + childTabSpecContent + + '">Link</a></p>'; + content.document.body.appendChild(mainDiv); + } + ); + + // Execute the test in the child tabs with the two methods to open it. + for (let openFn of [simulateCtrlClick, simulateContextMenuOpenInTab]) { + let promiseTabLoaded = waitForSomeTabToLoad(); + openFn(browser); + await promiseTabLoaded; + gBrowser.selectTabAtIndex(2); + + if (waitForMetaRefresh) { + await waitForSomeTabToLoad(); + } + + await testTaskFn(); + + gBrowser.removeCurrentTab(); + } + + SitePermissions.removeFromPrincipal(principal, "mixed-content"); + } + ); +} + +function simulateCtrlClick(browser) { + BrowserTestUtils.synthesizeMouseAtCenter( + "#linkToOpenInNewTab", + { ctrlKey: true, metaKey: true }, + browser + ); +} + +function simulateContextMenuOpenInTab(browser) { + BrowserTestUtils.waitForEvent(document, "popupshown", false, event => { + // These are operations that must be executed synchronously with the event. + document.getElementById("context-openlinkintab").doCommand(); + event.target.hidePopup(); + return true; + }); + BrowserTestUtils.synthesizeMouseAtCenter( + "#linkToOpenInNewTab", + { type: "contextmenu", button: 2 }, + browser + ); +} + +// Waits for a load event somewhere in the browser but ignore events coming +// from <xul:browser>s without a tab assigned. That are most likely browsers +// that preload the new tab page. +function waitForSomeTabToLoad() { + return BrowserTestUtils.firstBrowserLoaded(window, true, browser => { + let tab = gBrowser.getTabForBrowser(browser); + return !!tab; + }); +} + +/** + * Ensure the Mixed Content Blocker is enabled. + */ +add_task(async function test_initialize() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["security.mixed_content.block_active_content", true], + // We need to disable the dFPI heuristic. So, we won't have unnecessary + // 3rd party cookie permission that could affect following tests because + // it will create a permission icon on the URL bar. + ["privacy.restrict3rdpartystorage.heuristic.recently_visited", false], + ], + }); +}); + +/** + * 1. - Load a html page which has mixed content + * - Doorhanger to disable protection appears - we disable it + * - Load a subpage from the same origin in a new tab simulating a click + * - Doorhanger should >> NOT << appear anymore! + */ +add_task(async function test_same_origin() { + await doTest( + HTTPS_TEST_ROOT_1 + "file_bug906190_1.html", + HTTPS_TEST_ROOT_1 + "file_bug906190_2.html", + async function () { + // The doorhanger should appear but activeBlocked should be >> NOT << true, + // because our decision of disabling the mixed content blocker is persistent + // across tabs. + await assertMixedContentBlockingState(gBrowser, { + activeLoaded: true, + activeBlocked: false, + passiveLoaded: false, + }); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { + Assert.equal( + content.document.getElementById("mctestdiv").innerHTML, + "Mixed Content Blocker disabled", + "OK: Executed mixed script" + ); + }); + } + ); +}); + +/** + * 2. - Load a html page which has mixed content + * - Doorhanger to disable protection appears - we disable it + * - Load a new page from a different origin in a new tab simulating a click + * - Doorhanger >> SHOULD << appear again! + */ +add_task(async function test_different_origin() { + await doTest( + HTTPS_TEST_ROOT_1 + "file_bug906190_2.html", + HTTPS_TEST_ROOT_2 + "file_bug906190_2.html", + async function () { + // The doorhanger should appear and activeBlocked should be >> TRUE <<, + // because our decision of disabling the mixed content blocker should only + // persist if pages are from the same domain. + await assertMixedContentBlockingState(gBrowser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { + Assert.equal( + content.document.getElementById("mctestdiv").innerHTML, + "Mixed Content Blocker enabled", + "OK: Blocked mixed script" + ); + }); + } + ); +}); + +/** + * 3. - Load a html page which has mixed content + * - Doorhanger to disable protection appears - we disable it + * - Load a new page from the same origin in a new tab simulating a click + * - Redirect to another page from the same origin using meta-refresh + * - Doorhanger should >> NOT << appear again! + */ +add_task(async function test_same_origin_metarefresh_same_origin() { + // file_bug906190_3_4.html redirects to page test1.example.com/* using meta-refresh + await doTest( + HTTPS_TEST_ROOT_1 + "file_bug906190_1.html", + HTTPS_TEST_ROOT_1 + "file_bug906190_3_4.html", + async function () { + // The doorhanger should appear but activeBlocked should be >> NOT << true! + await assertMixedContentBlockingState(gBrowser, { + activeLoaded: true, + activeBlocked: false, + passiveLoaded: false, + }); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { + Assert.equal( + content.document.getElementById("mctestdiv").innerHTML, + "Mixed Content Blocker disabled", + "OK: Executed mixed script" + ); + }); + }, + true + ); +}); + +/** + * 4. - Load a html page which has mixed content + * - Doorhanger to disable protection appears - we disable it + * - Load a new page from the same origin in a new tab simulating a click + * - Redirect to another page from a different origin using meta-refresh + * - Doorhanger >> SHOULD << appear again! + */ +add_task(async function test_same_origin_metarefresh_different_origin() { + await doTest( + HTTPS_TEST_ROOT_2 + "file_bug906190_1.html", + HTTPS_TEST_ROOT_2 + "file_bug906190_3_4.html", + async function () { + // The doorhanger should appear and activeBlocked should be >> TRUE <<. + await assertMixedContentBlockingState(gBrowser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { + Assert.equal( + content.document.getElementById("mctestdiv").innerHTML, + "Mixed Content Blocker enabled", + "OK: Blocked mixed script" + ); + }); + }, + true + ); +}); + +/** + * 5. - Load a html page which has mixed content + * - Doorhanger to disable protection appears - we disable it + * - Load a new page from the same origin in a new tab simulating a click + * - Redirect to another page from the same origin using 302 redirect + */ +add_task(async function test_same_origin_302redirect_same_origin() { + // the sjs files returns a 302 redirect- note, same origins + await doTest( + HTTPS_TEST_ROOT_1 + "file_bug906190_1.html", + HTTPS_TEST_ROOT_1 + "file_bug906190.sjs", + async function () { + // The doorhanger should appear but activeBlocked should be >> NOT << true. + // Currently it is >> TRUE << - see follow up bug 914860 + ok( + !gIdentityHandler._identityBox.classList.contains("mixedActiveBlocked"), + "OK: Mixed Content is NOT being blocked" + ); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { + Assert.equal( + content.document.getElementById("mctestdiv").innerHTML, + "Mixed Content Blocker disabled", + "OK: Executed mixed script" + ); + }); + } + ); +}); + +/** + * 6. - Load a html page which has mixed content + * - Doorhanger to disable protection appears - we disable it + * - Load a new page from the same origin in a new tab simulating a click + * - Redirect to another page from a different origin using 302 redirect + */ +add_task(async function test_same_origin_302redirect_different_origin() { + // the sjs files returns a 302 redirect - note, different origins + await doTest( + HTTPS_TEST_ROOT_2 + "file_bug906190_1.html", + HTTPS_TEST_ROOT_2 + "file_bug906190.sjs", + async function () { + // The doorhanger should appear and activeBlocked should be >> TRUE <<. + await assertMixedContentBlockingState(gBrowser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { + Assert.equal( + content.document.getElementById("mctestdiv").innerHTML, + "Mixed Content Blocker enabled", + "OK: Blocked mixed script" + ); + }); + } + ); +}); + +/** + * 7. - Test memory leak issue on redirection error. See Bug 1269426. + */ +add_task(async function test_bad_redirection() { + // the sjs files returns a 302 redirect - note, different origins + await doTest( + HTTPS_TEST_ROOT_2 + "file_bug906190_1.html", + HTTPS_TEST_ROOT_2 + "file_bug906190.sjs?bad-redirection=1", + function () { + // Nothing to do. Just see if memory leak is reported in the end. + ok(true, "Nothing to do"); + } + ); +}); diff --git a/browser/base/content/test/siteIdentity/browser_check_identity_state.js b/browser/base/content/test/siteIdentity/browser_check_identity_state.js new file mode 100644 index 0000000000..e5ecbc66f2 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_check_identity_state.js @@ -0,0 +1,882 @@ +/* + * Test the identity mode UI for a variety of page types + */ + +"use strict"; + +const DUMMY = "browser/browser/base/content/test/siteIdentity/dummy_page.html"; +const INSECURE_ICON_PREF = "security.insecure_connection_icon.enabled"; +const INSECURE_TEXT_PREF = "security.insecure_connection_text.enabled"; +const INSECURE_PBMODE_ICON_PREF = + "security.insecure_connection_icon.pbmode.enabled"; +const HTTPS_FIRST_PBM_PREF = "dom.security.https_first_pbm"; + +function loadNewTab(url) { + return BrowserTestUtils.openNewForegroundTab(gBrowser, url, true); +} + +function getConnectionState() { + // Prevents items that are being lazy loaded causing issues + document.getElementById("identity-icon-box").click(); + gIdentityHandler.refreshIdentityPopup(); + return document.getElementById("identity-popup").getAttribute("connection"); +} + +function getSecurityConnectionBG() { + // Get the background image of the security connection. + document.getElementById("identity-icon-box").click(); + gIdentityHandler.refreshIdentityPopup(); + return gBrowser.ownerGlobal + .getComputedStyle( + document + .getElementById("identity-popup-mainView") + .getElementsByClassName("identity-popup-security-connection")[0] + ) + .getPropertyValue("list-style-image"); +} + +async function getReaderModeURL() { + // Gets the reader mode URL from "identity-popup mainView panel header span" + document.getElementById("identity-icon-box").click(); + gIdentityHandler.refreshIdentityPopup(); + + let headerSpan = document.getElementById( + "identity-popup-mainView-panel-header-span" + ); + await BrowserTestUtils.waitForCondition(() => + headerSpan.innerHTML.includes("example.com") + ); + return headerSpan.innerHTML; +} + +// This test is slow on Linux debug e10s +requestLongerTimeout(2); + +add_task(async function chromeUITest() { + // needs to be set due to bug in ion.js that occurs when testing + SpecialPowers.pushPrefEnv({ + set: [ + ["toolkit.pioneer.testCachedContent", "[]"], + ["toolkit.pioneer.testCachedAddons", "[]"], + ], + }); + // Might needs to be extended with new secure chrome pages + // about:debugging is a secure chrome UI but is not tested for causing problems. + let secureChromePages = [ + "addons", + "cache", + "certificate", + "compat", + "config", + "downloads", + "ion", + "license", + "logins", + "loginsimportreport", + "performance", + "plugins", + "policies", + "preferences", + "processes", + "profiles", + "profiling", + "protections", + "rights", + "sessionrestore", + "studies", + "support", + "telemetry", + "welcomeback", + ]; + + // else skip about:crashes, it is only available with plugin + if (AppConstants.MOZ_CRASHREPORTER) { + secureChromePages.push("crashes"); + } + + let nonSecureExamplePages = [ + "about:about", + "about:credits", + "about:home", + "about:logo", + "about:memory", + "about:mozilla", + "about:networking", + "about:privatebrowsing", + "about:robots", + "about:serviceWorkers", + "about:sync-log", + "about:unloads", + "about:url-classifier", + "about:webrtc", + "about:welcome", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com/" + DUMMY, + ]; + + for (let i = 0; i < secureChromePages.length; i++) { + await BrowserTestUtils.withNewTab("about:" + secureChromePages[i], () => { + is(getIdentityMode(), "chromeUI", "Identity should be chromeUI"); + }); + } + + for (let i = 0; i < nonSecureExamplePages.length; i++) { + console.log(nonSecureExamplePages[i]); + await BrowserTestUtils.withNewTab(nonSecureExamplePages[i], () => { + ok(getIdentityMode() != "chromeUI", "Identity should not be chromeUI"); + }); + } +}); + +async function webpageTest(secureCheck) { + await SpecialPowers.pushPrefEnv({ set: [[INSECURE_ICON_PREF, secureCheck]] }); + let oldTab = await loadNewTab("about:robots"); + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let newTab = await loadNewTab("http://example.com/" + DUMMY); + if (secureCheck) { + is(getIdentityMode(), "notSecure", "Identity should be not secure"); + } else { + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + } + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "localResource", "Identity should be localResource"); + + gBrowser.selectedTab = newTab; + if (secureCheck) { + is(getIdentityMode(), "notSecure", "Identity should be not secure"); + } else { + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + } + + gBrowser.removeTab(newTab); + gBrowser.removeTab(oldTab); + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_webpage() { + await webpageTest(false); + await webpageTest(true); +}); + +async function webpageTestTextWarning(secureCheck) { + await SpecialPowers.pushPrefEnv({ set: [[INSECURE_TEXT_PREF, secureCheck]] }); + let oldTab = await loadNewTab("about:robots"); + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let newTab = await loadNewTab("http://example.com/" + DUMMY); + if (secureCheck) { + is( + getIdentityMode(), + "notSecure notSecureText", + "Identity should have not secure text" + ); + } else { + is(getIdentityMode(), "notSecure", "Identity should be not secure"); + } + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "localResource", "Identity should be localResource"); + + gBrowser.selectedTab = newTab; + if (secureCheck) { + is( + getIdentityMode(), + "notSecure notSecureText", + "Identity should have not secure text" + ); + } else { + is(getIdentityMode(), "notSecure", "Identity should be not secure"); + } + + gBrowser.removeTab(newTab); + gBrowser.removeTab(oldTab); + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_webpage_text_warning() { + await webpageTestTextWarning(false); + await webpageTestTextWarning(true); +}); + +async function webpageTestTextWarningCombined(secureCheck) { + await SpecialPowers.pushPrefEnv({ + set: [ + [INSECURE_TEXT_PREF, secureCheck], + [INSECURE_ICON_PREF, secureCheck], + ], + }); + let oldTab = await loadNewTab("about:robots"); + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let newTab = await loadNewTab("http://example.com/" + DUMMY); + if (secureCheck) { + is( + getIdentityMode(), + "notSecure notSecureText", + "Identity should be not secure" + ); + } else { + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + } + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + + gBrowser.selectedTab = newTab; + if (secureCheck) { + is( + getIdentityMode(), + "notSecure notSecureText", + "Identity should be not secure" + ); + } else { + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + } + + gBrowser.removeTab(newTab); + gBrowser.removeTab(oldTab); + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_webpage_text_warning_combined() { + await webpageTestTextWarning(false); + await webpageTestTextWarning(true); +}); + +async function blankPageTest(secureCheck) { + let oldTab = await loadNewTab("about:robots"); + await SpecialPowers.pushPrefEnv({ set: [[INSECURE_ICON_PREF, secureCheck]] }); + + let newTab = await loadNewTab("about:blank"); + is( + gURLBar.getAttribute("pageproxystate"), + "invalid", + "pageproxystate should be invalid" + ); + + gBrowser.selectedTab = oldTab; + is( + gURLBar.getAttribute("pageproxystate"), + "valid", + "pageproxystate should be valid" + ); + is(getIdentityMode(), "localResource", "Identity should be localResource"); + + gBrowser.selectedTab = newTab; + is( + gURLBar.getAttribute("pageproxystate"), + "invalid", + "pageproxystate should be invalid" + ); + + gBrowser.removeTab(newTab); + gBrowser.removeTab(oldTab); + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_blank() { + await blankPageTest(true); + await blankPageTest(false); +}); + +async function secureTest(secureCheck) { + let oldTab = await loadNewTab("about:robots"); + await SpecialPowers.pushPrefEnv({ set: [[INSECURE_ICON_PREF, secureCheck]] }); + + let newTab = await loadNewTab("https://example.com/" + DUMMY); + is(getIdentityMode(), "verifiedDomain", "Identity should be verified"); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "localResource", "Identity should be localResource"); + + gBrowser.selectedTab = newTab; + is(getIdentityMode(), "verifiedDomain", "Identity should be verified"); + + gBrowser.removeTab(newTab); + gBrowser.removeTab(oldTab); + + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_secure_enabled() { + await secureTest(true); + await secureTest(false); +}); + +async function viewSourceTest() { + let sourceTab = await loadNewTab("view-source:https://example.com/" + DUMMY); + + gBrowser.selectedTab = sourceTab; + is( + getIdentityMode(), + "verifiedDomain", + "Identity should be verified while viewing source" + ); + + gBrowser.removeTab(sourceTab); +} + +add_task(async function test_viewSource() { + await viewSourceTest(); +}); + +async function insecureTest(secureCheck) { + let oldTab = await loadNewTab("about:robots"); + await SpecialPowers.pushPrefEnv({ set: [[INSECURE_ICON_PREF, secureCheck]] }); + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let newTab = await loadNewTab("http://example.com/" + DUMMY); + if (secureCheck) { + is(getIdentityMode(), "notSecure", "Identity should be not secure"); + is( + document.getElementById("identity-icon").getAttribute("tooltiptext"), + gNavigatorBundle.getString("identity.notSecure.tooltip"), + "The insecure lock icon has a correct tooltip text." + ); + } else { + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + } + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "localResource", "Identity should be localResource"); + + gBrowser.selectedTab = newTab; + if (secureCheck) { + is(getIdentityMode(), "notSecure", "Identity should be not secure"); + is( + document.getElementById("identity-icon").getAttribute("tooltiptext"), + gNavigatorBundle.getString("identity.notSecure.tooltip"), + "The insecure lock icon has a correct tooltip text." + ); + } else { + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + } + + gBrowser.removeTab(newTab); + gBrowser.removeTab(oldTab); + + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_insecure() { + await insecureTest(true); + await insecureTest(false); +}); + +async function addonsTest(secureCheck) { + let oldTab = await loadNewTab("about:robots"); + await SpecialPowers.pushPrefEnv({ set: [[INSECURE_ICON_PREF, secureCheck]] }); + + let newTab = await loadNewTab("about:addons"); + is(getIdentityMode(), "chromeUI", "Identity should be chrome"); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "localResource", "Identity should be localResource"); + + gBrowser.selectedTab = newTab; + is(getIdentityMode(), "chromeUI", "Identity should be chrome"); + + gBrowser.removeTab(newTab); + gBrowser.removeTab(oldTab); + + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_addons() { + await addonsTest(true); + await addonsTest(false); +}); + +async function fileTest(secureCheck) { + let oldTab = await loadNewTab("about:robots"); + await SpecialPowers.pushPrefEnv({ set: [[INSECURE_ICON_PREF, secureCheck]] }); + let fileURI = getTestFilePath(""); + + let newTab = await loadNewTab(fileURI); + is(getConnectionState(), "file", "Connection should be file"); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "localResource", "Identity should be localResource"); + + gBrowser.selectedTab = newTab; + is(getConnectionState(), "file", "Connection should be file"); + + gBrowser.removeTab(newTab); + gBrowser.removeTab(oldTab); + + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_file() { + await fileTest(true); + await fileTest(false); +}); + +async function resourceUriTest(secureCheck) { + let oldTab = await loadNewTab("about:robots"); + await SpecialPowers.pushPrefEnv({ set: [[INSECURE_ICON_PREF, secureCheck]] }); + let dataURI = "resource://gre/modules/XPCOMUtils.sys.mjs"; + + let newTab = await loadNewTab(dataURI); + + is(getConnectionState(), "file", "Connection should be file"); + + gBrowser.selectedTab = oldTab; + is( + getIdentityMode(), + "localResource", + "Identity should be a local a resource" + ); + + gBrowser.selectedTab = newTab; + is(getConnectionState(), "file", "Connection should be file"); + + gBrowser.removeTab(newTab); + gBrowser.removeTab(oldTab); + + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_resource_uri() { + await resourceUriTest(true); + await resourceUriTest(false); +}); + +async function noCertErrorTest(secureCheck) { + let oldTab = await loadNewTab("about:robots"); + await SpecialPowers.pushPrefEnv({ set: [[INSECURE_ICON_PREF, secureCheck]] }); + let newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + + let promise = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser); + BrowserTestUtils.loadURIString(gBrowser, "https://nocert.example.com/"); + await promise; + is( + getIdentityMode(), + "certErrorPage notSecureText", + "Identity should be the cert error page." + ); + is( + getConnectionState(), + "cert-error-page", + "Connection should be the cert error page." + ); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "localResource", "Identity should be localResource"); + + gBrowser.selectedTab = newTab; + is( + getIdentityMode(), + "certErrorPage notSecureText", + "Identity should be the cert error page." + ); + is( + getConnectionState(), + "cert-error-page", + "Connection should be the cert error page." + ); + + gBrowser.removeTab(newTab); + gBrowser.removeTab(oldTab); + + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_about_net_error_uri() { + await noCertErrorTest(true); + await noCertErrorTest(false); +}); + +add_task(async function httpsOnlyErrorTest() { + let oldTab = await loadNewTab("about:robots"); + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_only_mode", true]], + }); + let newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + + let promise = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + BrowserTestUtils.loadURIString(gBrowser, "http://nocert.example.com/"); + await promise; + is( + getIdentityMode(), + "httpsOnlyErrorPage", + "Identity should be the https-only mode error page." + ); + is( + getConnectionState(), + "https-only-error-page", + "Connection should be the https-only mode error page." + ); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "localResource", "Identity should be localResource"); + + gBrowser.selectedTab = newTab; + is( + getIdentityMode(), + "httpsOnlyErrorPage", + "Identity should be the https-only mode error page." + ); + is( + getConnectionState(), + "https-only-error-page", + "Connection should be the https-only mode page." + ); + + gBrowser.removeTab(newTab); + gBrowser.removeTab(oldTab); + + await SpecialPowers.popPrefEnv(); +}); + +async function noCertErrorFromNavigationTest(secureCheck) { + await SpecialPowers.pushPrefEnv({ set: [[INSECURE_ICON_PREF, secureCheck]] }); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let newTab = await loadNewTab("http://example.com/" + DUMMY); + + let promise = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () { + content.document.getElementById("no-cert").click(); + }); + await promise; + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () { + is( + content.window.location.href, + "https://nocert.example.com/", + "Should be the cert error URL" + ); + }); + + is( + newTab.linkedBrowser.documentURI.spec.startsWith("about:certerror?"), + true, + "Should be an about:certerror" + ); + is( + getIdentityMode(), + "certErrorPage notSecureText", + "Identity should be the cert error page." + ); + is( + getConnectionState(), + "cert-error-page", + "Connection should be the cert error page." + ); + + gBrowser.removeTab(newTab); + + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_about_net_error_uri_from_navigation_tab() { + await noCertErrorFromNavigationTest(true); + await noCertErrorFromNavigationTest(false); +}); + +add_task(async function tlsErrorPageTest() { + const TLS10_PAGE = "https://tls1.example.com/"; + await SpecialPowers.pushPrefEnv({ + set: [ + ["security.tls.version.min", 3], + ["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; + + await SpecialPowers.spawn(browser, [], function () { + const doc = content.document; + ok( + doc.documentURI.startsWith("about:neterror"), + "Should be showing error page" + ); + }); + + is( + getConnectionState(), + "cert-error-page", + "Connection state should be the cert error page." + ); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); + + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function netErrorPageTest() { + // Connect to a server that rejects all requests, to test network error pages: + let { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js"); + let server = new HttpServer(); + server.registerPrefixHandler("/", (req, res) => + res.abort(new Error("Noooope.")) + ); + server.start(-1); + let port = server.identity.primaryPort; + const ERROR_PAGE = `http://localhost:${port}/`; + + let browser; + let pageLoaded; + await BrowserTestUtils.openNewForegroundTab( + gBrowser, + () => { + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, ERROR_PAGE); + browser = gBrowser.selectedBrowser; + pageLoaded = BrowserTestUtils.waitForErrorPage(browser); + }, + false + ); + + info("Loading and waiting for the net error"); + await pageLoaded; + + await SpecialPowers.spawn(browser, [], function () { + const doc = content.document; + ok( + doc.documentURI.startsWith("about:neterror"), + "Should be showing error page" + ); + }); + + is( + getConnectionState(), + "net-error-page", + "Connection should be the net error page." + ); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +async function aboutBlockedTest(secureCheck) { + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let url = "http://www.itisatrap.org/firefox/its-an-attack.html"; + let oldTab = await loadNewTab("about:robots"); + await SpecialPowers.pushPrefEnv({ + set: [ + [INSECURE_ICON_PREF, secureCheck], + ["urlclassifier.blockedTable", "moztest-block-simple"], + ], + }); + let newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + + BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, url); + + await BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + url, + true + ); + + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown."); + is(getConnectionState(), "not-secure", "Connection should be not secure."); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "localResource", "Identity should be localResource"); + + gBrowser.selectedTab = newTab; + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown."); + is(getConnectionState(), "not-secure", "Connection should be not secure."); + + gBrowser.removeTab(newTab); + gBrowser.removeTab(oldTab); + + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_about_blocked() { + await aboutBlockedTest(true); + await aboutBlockedTest(false); +}); + +add_task(async function noCertErrorSecurityConnectionBGTest() { + let tab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = tab; + let promise = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser); + BrowserTestUtils.loadURIString(gBrowser, "https://nocert.example.com/"); + await promise; + + is( + getSecurityConnectionBG(), + `url("chrome://global/skin/icons/security-warning.svg")`, + "Security connection should show a warning lock icon." + ); + + BrowserTestUtils.removeTab(tab); +}); + +async function aboutUriTest(secureCheck) { + let oldTab = await loadNewTab("about:robots"); + await SpecialPowers.pushPrefEnv({ set: [[INSECURE_ICON_PREF, secureCheck]] }); + let aboutURI = "about:robots"; + + let newTab = await loadNewTab(aboutURI); + is(getConnectionState(), "file", "Connection should be file"); + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "localResource", "Identity should be localResource"); + + gBrowser.selectedTab = newTab; + is(getConnectionState(), "file", "Connection should be file"); + + gBrowser.removeTab(newTab); + gBrowser.removeTab(oldTab); + + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_about_uri() { + await aboutUriTest(true); + await aboutUriTest(false); +}); + +async function readerUriTest(secureCheck) { + await SpecialPowers.pushPrefEnv({ set: [[INSECURE_ICON_PREF, secureCheck]] }); + + let newTab = await loadNewTab("about:reader?url=http://example.com"); + gBrowser.selectedTab = newTab; + let readerURL = await getReaderModeURL(); + is( + readerURL, + "Site information for example.com", + "should be the correct URI in reader mode" + ); + + gBrowser.removeTab(newTab); + + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_reader_uri() { + await readerUriTest(true); + await readerUriTest(false); +}); + +async function dataUriTest(secureCheck) { + let oldTab = await loadNewTab("about:robots"); + await SpecialPowers.pushPrefEnv({ set: [[INSECURE_ICON_PREF, secureCheck]] }); + let dataURI = "data:text/html,hi"; + + let newTab = await loadNewTab(dataURI); + if (secureCheck) { + is(getIdentityMode(), "notSecure", "Identity should be not secure"); + } else { + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + } + + gBrowser.selectedTab = oldTab; + is(getIdentityMode(), "localResource", "Identity should be localResource"); + + gBrowser.selectedTab = newTab; + if (secureCheck) { + is(getIdentityMode(), "notSecure", "Identity should be not secure"); + } else { + is(getIdentityMode(), "unknownIdentity", "Identity should be unknown"); + } + + gBrowser.removeTab(newTab); + gBrowser.removeTab(oldTab); + + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_data_uri() { + await dataUriTest(true); + await dataUriTest(false); +}); + +async function pbModeTest(prefs, secureCheck) { + await SpecialPowers.pushPrefEnv({ set: prefs }); + + let privateWin = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + let oldTab = await BrowserTestUtils.openNewForegroundTab( + privateWin.gBrowser, + "about:robots" + ); + let newTab = await BrowserTestUtils.openNewForegroundTab( + privateWin.gBrowser, + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com/" + DUMMY + ); + + if (secureCheck) { + is( + getIdentityMode(privateWin), + "notSecure", + "Identity should be not secure" + ); + } else { + is( + getIdentityMode(privateWin), + "unknownIdentity", + "Identity should be unknown" + ); + } + + privateWin.gBrowser.selectedTab = oldTab; + is( + getIdentityMode(privateWin), + "localResource", + "Identity should be localResource" + ); + + privateWin.gBrowser.selectedTab = newTab; + if (secureCheck) { + is( + getIdentityMode(privateWin), + "notSecure", + "Identity should be not secure" + ); + } else { + is( + getIdentityMode(privateWin), + "unknownIdentity", + "Identity should be unknown" + ); + } + + await BrowserTestUtils.closeWindow(privateWin); + + await SpecialPowers.popPrefEnv(); +} + +add_task(async function test_pb_mode() { + let prefs = [ + [INSECURE_ICON_PREF, true], + [INSECURE_PBMODE_ICON_PREF, true], + [HTTPS_FIRST_PBM_PREF, false], + ]; + await pbModeTest(prefs, true); + prefs = [ + [INSECURE_ICON_PREF, false], + [INSECURE_PBMODE_ICON_PREF, true], + [HTTPS_FIRST_PBM_PREF, false], + ]; + await pbModeTest(prefs, true); + prefs = [ + [INSECURE_ICON_PREF, false], + [INSECURE_PBMODE_ICON_PREF, false], + [HTTPS_FIRST_PBM_PREF, false], + ]; + await pbModeTest(prefs, false); +}); diff --git a/browser/base/content/test/siteIdentity/browser_check_identity_state_pdf.js b/browser/base/content/test/siteIdentity/browser_check_identity_state_pdf.js new file mode 100644 index 0000000000..8180238e84 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_check_identity_state_pdf.js @@ -0,0 +1,77 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Tests that sites opened via the PDF viewer have the correct identity state. + */ + +"use strict"; + +function testIdentityMode(uri, expectedState, message) { + return BrowserTestUtils.withNewTab(uri, () => { + is(getIdentityMode(), expectedState, message); + }); +} + +/** + * Test site identity state for PDFs served via file URI. + */ +add_task(async function test_pdf_fileURI() { + let path = getTestFilePath("./file_pdf.pdf"); + info("path:" + path); + + await testIdentityMode( + path, + "localResource", + "Identity should be localResource for a PDF served via file URI" + ); +}); + +/** + * Test site identity state for PDFs served via blob URI. + */ +add_task(async function test_pdf_blobURI() { + let uri = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "file_pdf_blob.html"; + + await BrowserTestUtils.withNewTab(uri, async browser => { + let newTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, null, true); + + await BrowserTestUtils.synthesizeMouseAtCenter("a", {}, browser); + await newTabOpened; + + is( + getIdentityMode(), + "localResource", + "Identity should be localResource for a PDF served via blob URI" + ); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); + }); +}); + +/** + * Test site identity state for PDFs served via HTTP. + */ +add_task(async function test_pdf_http() { + const PDF_URI_NOSCHEME = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "example.com" + ) + "file_pdf.pdf"; + + await testIdentityMode( + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://" + PDF_URI_NOSCHEME, + "notSecure", + "Identity should be notSecure for a PDF served via HTTP." + ); + await testIdentityMode( + "https://" + PDF_URI_NOSCHEME, + "verifiedDomain", + "Identity should be verifiedDomain for a PDF served via HTTPS." + ); +}); diff --git a/browser/base/content/test/siteIdentity/browser_csp_block_all_mixedcontent.js b/browser/base/content/test/siteIdentity/browser_csp_block_all_mixedcontent.js new file mode 100644 index 0000000000..693c9418de --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_csp_block_all_mixedcontent.js @@ -0,0 +1,60 @@ +/* + * Description of the Test: + * We load an https page which uses a CSP including block-all-mixed-content. + * The page tries to load a script over http. We make sure the UI is not + * influenced when blocking the mixed content. In particular the page + * should still appear fully encrypted with a green lock. + */ + +const PRE_PATH = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); +var gTestBrowser = null; + +// ------------------------------------------------------ +function cleanUpAfterTests() { + gBrowser.removeCurrentTab(); + window.focus(); + finish(); +} + +// ------------------------------------------------------ +async function verifyUInotDegraded() { + // make sure that not mixed content is loaded and also not blocked + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: false, + activeBlocked: false, + passiveLoaded: false, + }); + // clean up and finish test + cleanUpAfterTests(); +} + +// ------------------------------------------------------ +function runTests() { + var newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + gTestBrowser = gBrowser.selectedBrowser; + newTab.linkedBrowser.stop(); + + // Starting the test + var url = PRE_PATH + "file_csp_block_all_mixedcontent.html"; + BrowserTestUtils.browserLoaded(gTestBrowser, false, url).then( + verifyUInotDegraded + ); + BrowserTestUtils.loadURIString(gTestBrowser, url); +} + +// ------------------------------------------------------ +function test() { + // Performing async calls, e.g. 'onload', we have to wait till all of them finished + waitForExplicitFinish(); + + SpecialPowers.pushPrefEnv( + { set: [["security.mixed_content.block_active_content", true]] }, + function () { + runTests(); + } + ); +} diff --git a/browser/base/content/test/siteIdentity/browser_deprecatedTLSVersions.js b/browser/base/content/test/siteIdentity/browser_deprecatedTLSVersions.js new file mode 100644 index 0000000000..22fa33f3c2 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_deprecatedTLSVersions.js @@ -0,0 +1,94 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * Tests for Bug 1535210 - Set SSL STATE_IS_BROKEN flag for TLS1.0 and TLS 1.1 connections + */ + +const HTTPS_TLS1_0 = "https://tls1.example.com"; +const HTTPS_TLS1_1 = "https://tls11.example.com"; +const HTTPS_TLS1_2 = "https://tls12.example.com"; +const HTTPS_TLS1_3 = "https://tls13.example.com"; + +function getIdentityMode(aWindow = window) { + return aWindow.document.getElementById("identity-box").className; +} + +function closeIdentityPopup() { + let promise = BrowserTestUtils.waitForEvent( + gIdentityHandler._identityPopup, + "popuphidden" + ); + gIdentityHandler._identityPopup.hidePopup(); + return promise; +} + +async function checkConnectionState(state) { + await openIdentityPopup(); + is(getConnectionState(), state, "connectionState should be " + state); + await closeIdentityPopup(); +} + +function getConnectionState() { + return document.getElementById("identity-popup").getAttribute("connection"); +} + +registerCleanupFunction(function () { + // Set preferences back to their original values + Services.prefs.clearUserPref("security.tls.version.min"); + Services.prefs.clearUserPref("security.tls.version.max"); +}); + +add_task(async function () { + // Run with all versions enabled for this test. + Services.prefs.setIntPref("security.tls.version.min", 1); + Services.prefs.setIntPref("security.tls.version.max", 4); + + await BrowserTestUtils.withNewTab("about:blank", async function (browser) { + // Try deprecated versions + BrowserTestUtils.loadURIString(browser, HTTPS_TLS1_0); + await BrowserTestUtils.browserLoaded(browser); + isSecurityState(browser, "broken"); + is( + getIdentityMode(), + "unknownIdentity weakCipher", + "Identity should be unknownIdentity" + ); + await checkConnectionState("not-secure"); + + BrowserTestUtils.loadURIString(browser, HTTPS_TLS1_1); + await BrowserTestUtils.browserLoaded(browser); + isSecurityState(browser, "broken"); + is( + getIdentityMode(), + "unknownIdentity weakCipher", + "Identity should be unknownIdentity" + ); + await checkConnectionState("not-secure"); + + // Transition to secure + BrowserTestUtils.loadURIString(browser, HTTPS_TLS1_2); + await BrowserTestUtils.browserLoaded(browser); + isSecurityState(browser, "secure"); + is(getIdentityMode(), "verifiedDomain", "Identity should be verified"); + await checkConnectionState("secure"); + + // Transition back to broken + BrowserTestUtils.loadURIString(browser, HTTPS_TLS1_1); + await BrowserTestUtils.browserLoaded(browser); + isSecurityState(browser, "broken"); + is( + getIdentityMode(), + "unknownIdentity weakCipher", + "Identity should be unknownIdentity" + ); + await checkConnectionState("not-secure"); + + // TLS1.3 for completeness + BrowserTestUtils.loadURIString(browser, HTTPS_TLS1_3); + await BrowserTestUtils.browserLoaded(browser); + isSecurityState(browser, "secure"); + is(getIdentityMode(), "verifiedDomain", "Identity should be verified"); + await checkConnectionState("secure"); + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_geolocation_indicator.js b/browser/base/content/test/siteIdentity/browser_geolocation_indicator.js new file mode 100644 index 0000000000..078b7ab975 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_geolocation_indicator.js @@ -0,0 +1,381 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +requestLongerTimeout(2); + +const { PermissionUI } = ChromeUtils.importESModule( + "resource:///modules/PermissionUI.sys.mjs" +); + +const { PermissionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PermissionTestUtils.sys.mjs" +); + +const CP = Cc["@mozilla.org/content-pref/service;1"].getService( + Ci.nsIContentPrefService2 +); + +const EXAMPLE_PAGE_URL = "https://example.com"; +const EXAMPLE_PAGE_URI = Services.io.newURI(EXAMPLE_PAGE_URL); +const EXAMPLE_PAGE_PRINCIPAL = + Services.scriptSecurityManager.createContentPrincipal(EXAMPLE_PAGE_URI, {}); +const GEO_CONTENT_PREF_KEY = "permissions.geoLocation.lastAccess"; +const POLL_INTERVAL_FALSE_STATE = 50; + +async function testGeoSharingIconVisible(state = true) { + let sharingIcon = document.getElementById("geo-sharing-icon"); + ok(sharingIcon, "Geo sharing icon exists"); + + try { + await TestUtils.waitForCondition( + () => sharingIcon.hasAttribute("sharing") === true, + "Waiting for geo sharing icon visibility state", + // If we wait for sharing icon to *not* show, waitForCondition will always timeout on correct state. + // In these cases we want to reduce the wait time from 5 seconds to 2.5 seconds to prevent test duration timeouts + !state ? POLL_INTERVAL_FALSE_STATE : undefined + ); + } catch (e) { + ok(!state, "Geo sharing icon not showing"); + return; + } + ok(state, "Geo sharing icon showing"); +} + +async function checkForDOMElement(state, id) { + info(`Testing state ${state} of element ${id}`); + let el; + try { + await TestUtils.waitForCondition( + () => { + el = document.getElementById(id); + return el != null; + }, + `Waiting for ${id}`, + !state ? POLL_INTERVAL_FALSE_STATE : undefined + ); + } catch (e) { + ok(!state, `${id} has correct state`); + return el; + } + ok(state, `${id} has correct state`); + + return el; +} + +async function testPermissionPopupGeoContainer( + containerVisible, + timestampVisible +) { + // The container holds the timestamp element, therefore we can't have a + // visible timestamp without the container. + if (timestampVisible && !containerVisible) { + ok(false, "Can't have timestamp without container"); + } + + // Only call openPermissionPopup if popup is closed, otherwise it does not resolve + if (!gPermissionPanel._identityPermissionBox.hasAttribute("open")) { + await openPermissionPopup(); + } + + let checkContainer = checkForDOMElement( + containerVisible, + "permission-popup-geo-container" + ); + + if (containerVisible && timestampVisible) { + // Wait for the geo container to be fully populated. + // The time label is computed async. + let container = await checkContainer; + await TestUtils.waitForCondition( + () => container.childElementCount == 2, + "permission-popup-geo-container should have two elements." + ); + is( + container.childNodes[0].classList[0], + "permission-popup-permission-item", + "Geo container should have permission item." + ); + is( + container.childNodes[1].id, + "geo-access-indicator-item", + "Geo container should have indicator item." + ); + } + let checkAccessIndicator = checkForDOMElement( + timestampVisible, + "geo-access-indicator-item" + ); + + return Promise.all([checkContainer, checkAccessIndicator]); +} + +function openExamplePage(tabbrowser = gBrowser) { + return BrowserTestUtils.openNewForegroundTab(tabbrowser, EXAMPLE_PAGE_URL); +} + +function requestGeoLocation(browser) { + return SpecialPowers.spawn(browser, [], () => { + return new Promise(resolve => { + content.navigator.geolocation.getCurrentPosition( + () => resolve(true), + error => resolve(error.code !== 1) // PERMISSION_DENIED = 1 + ); + }); + }); +} + +function answerGeoLocationPopup(allow, remember = false) { + let notification = PopupNotifications.getNotification("geolocation"); + ok( + PopupNotifications.isPanelOpen && notification, + "Geolocation notification is open" + ); + + let rememberCheck = PopupNotifications.panel.querySelector( + ".popup-notification-checkbox" + ); + rememberCheck.checked = remember; + + let popupHidden = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popuphidden" + ); + if (allow) { + let allowBtn = PopupNotifications.panel.querySelector( + ".popup-notification-primary-button" + ); + allowBtn.click(); + } else { + let denyBtn = PopupNotifications.panel.querySelector( + ".popup-notification-secondary-button" + ); + denyBtn.click(); + } + return popupHidden; +} + +function setGeoLastAccess(browser, state) { + return new Promise(resolve => { + let host = browser.currentURI.host; + let handler = { + handleCompletion: () => resolve(), + }; + + if (!state) { + CP.removeByDomainAndName( + host, + GEO_CONTENT_PREF_KEY, + browser.loadContext, + handler + ); + return; + } + CP.set( + host, + GEO_CONTENT_PREF_KEY, + new Date().toString(), + browser.loadContext, + handler + ); + }); +} + +async function testGeoLocationLastAccessSet(browser) { + let timestamp = await new Promise(resolve => { + let lastAccess = null; + CP.getByDomainAndName( + gBrowser.currentURI.spec, + GEO_CONTENT_PREF_KEY, + browser.loadContext, + { + handleResult(pref) { + lastAccess = pref.value; + }, + handleCompletion() { + resolve(lastAccess); + }, + } + ); + }); + + ok(timestamp != null, "Geo last access timestamp set"); + + let parseSuccess = true; + try { + timestamp = new Date(timestamp); + } catch (e) { + parseSuccess = false; + } + ok( + parseSuccess && !isNaN(timestamp), + "Geo last access timestamp is valid Date" + ); +} + +async function cleanup(tab) { + await setGeoLastAccess(tab.linkedBrowser, false); + SitePermissions.removeFromPrincipal( + tab.linkedBrowser.contentPrincipal, + "geo", + tab.linkedBrowser + ); + gBrowser.resetBrowserSharing(tab.linkedBrowser); + BrowserTestUtils.removeTab(tab); +} + +async function testIndicatorGeoSharingState(active) { + let tab = await openExamplePage(); + gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: active }); + await testGeoSharingIconVisible(active); + + await cleanup(tab); +} + +async function testIndicatorExplicitAllow(persistent) { + let tab = await openExamplePage(); + + let popupShown = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown" + ); + info("Requesting geolocation"); + let request = requestGeoLocation(tab.linkedBrowser); + await popupShown; + info("Allowing geolocation via popup"); + answerGeoLocationPopup(true, persistent); + await request; + + await Promise.all([ + testGeoSharingIconVisible(true), + testPermissionPopupGeoContainer(true, true), + testGeoLocationLastAccessSet(tab.linkedBrowser), + ]); + + await cleanup(tab); +} + +// Indicator and permission popup entry shown after explicit PermissionUI geolocation allow +add_task(function test_indicator_and_timestamp_after_explicit_allow() { + return testIndicatorExplicitAllow(false); +}); +add_task(function test_indicator_and_timestamp_after_explicit_allow_remember() { + return testIndicatorExplicitAllow(true); +}); + +// Indicator and permission popup entry shown after auto PermissionUI geolocation allow +add_task(async function test_indicator_and_timestamp_after_implicit_allow() { + PermissionTestUtils.add( + EXAMPLE_PAGE_URI, + "geo", + Services.perms.ALLOW_ACTION, + Services.perms.EXPIRE_NEVER + ); + let tab = await openExamplePage(); + let result = await requestGeoLocation(tab.linkedBrowser); + ok(result, "Request should be allowed"); + + await Promise.all([ + testGeoSharingIconVisible(true), + testPermissionPopupGeoContainer(true, true), + testGeoLocationLastAccessSet(tab.linkedBrowser), + ]); + + await cleanup(tab); +}); + +// Indicator shown when manually setting sharing state to true +add_task(function test_indicator_sharing_state_active() { + return testIndicatorGeoSharingState(true); +}); + +// Indicator not shown when manually setting sharing state to false +add_task(function test_indicator_sharing_state_inactive() { + return testIndicatorGeoSharingState(false); +}); + +// Permission popup shows permission if geo permission is set to persistent allow +add_task(async function test_permission_popup_permission_scope_permanent() { + PermissionTestUtils.add( + EXAMPLE_PAGE_URI, + "geo", + Services.perms.ALLOW_ACTION, + Services.perms.EXPIRE_NEVER + ); + let tab = await openExamplePage(); + + await testPermissionPopupGeoContainer(true, false); // Expect permission to be visible, but not lastAccess indicator + + await cleanup(tab); +}); + +// Sharing state set, but no permission +add_task(async function test_permission_popup_permission_sharing_state() { + let tab = await openExamplePage(); + gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: true }); + await testPermissionPopupGeoContainer(true, false); + + await cleanup(tab); +}); + +// Permission popup has correct state if sharing state and last geo access timestamp are set +add_task( + async function test_permission_popup_permission_sharing_state_timestamp() { + let tab = await openExamplePage(); + gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: true }); + await setGeoLastAccess(tab.linkedBrowser, true); + + await testPermissionPopupGeoContainer(true, true); + + await cleanup(tab); + } +); + +// Clicking permission clear button clears permission and resets geo sharing state +add_task(async function test_permission_popup_permission_clear() { + PermissionTestUtils.add( + EXAMPLE_PAGE_URI, + "geo", + Services.perms.ALLOW_ACTION, + Services.perms.EXPIRE_NEVER + ); + let tab = await openExamplePage(); + gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: true }); + + await openPermissionPopup(); + + let clearButton = document.querySelector( + "#permission-popup-geo-container button" + ); + ok(clearButton, "Clear button is visible"); + clearButton.click(); + + await Promise.all([ + testGeoSharingIconVisible(false), + testPermissionPopupGeoContainer(false, false), + TestUtils.waitForCondition(() => { + let sharingState = tab._sharingState; + return ( + sharingState == null || + sharingState.geo == null || + sharingState.geo === false + ); + }, "Waiting for geo sharing state to reset"), + ]); + await cleanup(tab); +}); + +/** + * Tests that we only show the last access label once when the sharing + * state is updated multiple times while the popup is open. + */ +add_task(async function test_permission_no_duplicate_last_access_label() { + let tab = await openExamplePage(); + await setGeoLastAccess(tab.linkedBrowser, true); + await openPermissionPopup(); + gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: true }); + gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: true }); + await testPermissionPopupGeoContainer(true, true); + await cleanup(tab); +}); diff --git a/browser/base/content/test/siteIdentity/browser_getSecurityInfo.js b/browser/base/content/test/siteIdentity/browser_getSecurityInfo.js new file mode 100644 index 0000000000..b3b086f39d --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_getSecurityInfo.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE; +const MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT = MOZILLA_PKIX_ERROR_BASE + 14; + +add_task(async function test() { + await BrowserTestUtils.withNewTab("about:blank", async function (browser) { + let loaded = BrowserTestUtils.waitForErrorPage(browser); + BrowserTestUtils.loadURIString(browser, "https://self-signed.example.com"); + await loaded; + let securityInfo = gBrowser.securityUI.secInfo; + ok(!securityInfo, "Found no security info"); + + loaded = BrowserTestUtils.browserLoaded(browser); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + BrowserTestUtils.loadURIString(browser, "http://example.com"); + await loaded; + securityInfo = gBrowser.securityUI.secInfo; + ok(!securityInfo, "Found no security info"); + + loaded = BrowserTestUtils.browserLoaded(browser); + BrowserTestUtils.loadURIString(browser, "https://example.com"); + await loaded; + securityInfo = gBrowser.securityUI.secInfo; + ok(securityInfo, "Found some security info"); + ok(securityInfo.succeededCertChain, "Has a succeeded cert chain"); + is(securityInfo.errorCode, 0, "Has no error code"); + is( + securityInfo.serverCert.commonName, + "example.com", + "Has the correct certificate" + ); + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_identityBlock_flicker.js b/browser/base/content/test/siteIdentity/browser_identityBlock_flicker.js new file mode 100644 index 0000000000..de2a137100 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_identityBlock_flicker.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Tests that the identity icons don't flicker when navigating, + * i.e. the box should show no intermediate identity state. */ + +add_task(async function test() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:robots", + true + ); + let identityBox = document.getElementById("identity-box"); + + is( + identityBox.className, + "localResource", + "identity box has the correct class" + ); + + let observerOptions = { + attributes: true, + attributeFilter: ["class"], + }; + let classChanges = 0; + + let observer = new MutationObserver(function (mutations) { + for (let mutation of mutations) { + is(mutation.type, "attributes"); + is(mutation.attributeName, "class"); + classChanges++; + is( + identityBox.className, + "verifiedDomain", + "identity box class changed correctly" + ); + } + }); + observer.observe(identityBox, observerOptions); + + let loaded = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + false, + "https://example.com/" + ); + BrowserTestUtils.loadURIString(tab.linkedBrowser, "https://example.com"); + await loaded; + + is(classChanges, 1, "Changed the className once"); + observer.disconnect(); + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/siteIdentity/browser_identityBlock_focus.js b/browser/base/content/test/siteIdentity/browser_identityBlock_focus.js new file mode 100644 index 0000000000..858cd3d632 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_identityBlock_focus.js @@ -0,0 +1,126 @@ +/* Tests that the identity block can be reached via keyboard + * shortcuts and that it has the correct tab order. + */ + +const TEST_PATH = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); +const PERMISSIONS_PAGE = TEST_PATH + "permissions.html"; + +// The DevEdition has the DevTools button in the toolbar by default. Remove it +// to prevent branch-specific rules what button should be focused. +CustomizableUI.removeWidgetFromArea("developer-button"); + +registerCleanupFunction(async function resetToolbar() { + await CustomizableUI.reset(); +}); + +add_task(async function setupHomeButton() { + // Put the home button in the pre-proton placement to test focus states. + CustomizableUI.addWidgetToArea( + "home-button", + "nav-bar", + CustomizableUI.getPlacementOfWidget("stop-reload-button").position + 1 + ); +}); + +function synthesizeKeyAndWaitForFocus(element, keyCode, options) { + let focused = BrowserTestUtils.waitForEvent(element, "focus"); + EventUtils.synthesizeKey(keyCode, options); + return focused; +} + +// Checks that the tracking protection icon container is the next element after +// the urlbar to be focused if there are no active notification anchors. +add_task(async function testWithoutNotifications() { + await SpecialPowers.pushPrefEnv({ set: [["accessibility.tabfocus", 7]] }); + await BrowserTestUtils.withNewTab("https://example.com", async function () { + await synthesizeKeyAndWaitForFocus(gURLBar, "l", { accelKey: true }); + is(document.activeElement, gURLBar.inputField, "urlbar should be focused"); + await synthesizeKeyAndWaitForFocus( + gProtectionsHandler._trackingProtectionIconContainer, + "VK_TAB", + { shiftKey: true } + ); + is( + document.activeElement, + gProtectionsHandler._trackingProtectionIconContainer, + "tracking protection icon container should be focused" + ); + }); +}); + +// Checks that when there is a notification anchor, it will receive +// focus before the identity block. +add_task(async function testWithNotifications() { + await SpecialPowers.pushPrefEnv({ set: [["accessibility.tabfocus", 7]] }); + await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, async function (browser) { + let popupshown = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown" + ); + // Request a permission; + BrowserTestUtils.synthesizeMouseAtCenter("#geo", {}, browser); + await popupshown; + + await synthesizeKeyAndWaitForFocus(gURLBar, "l", { accelKey: true }); + is(document.activeElement, gURLBar.inputField, "urlbar should be focused"); + await synthesizeKeyAndWaitForFocus( + gProtectionsHandler._trackingProtectionIconContainer, + "VK_TAB", + { shiftKey: true } + ); + is( + document.activeElement, + gProtectionsHandler._trackingProtectionIconContainer, + "tracking protection icon container should be focused" + ); + await synthesizeKeyAndWaitForFocus( + gIdentityHandler._identityIconBox, + "ArrowRight" + ); + is( + document.activeElement, + gIdentityHandler._identityIconBox, + "identity block should be focused" + ); + let geoIcon = document.getElementById("geo-notification-icon"); + await synthesizeKeyAndWaitForFocus(geoIcon, "ArrowRight"); + is( + document.activeElement, + geoIcon, + "notification anchor should be focused" + ); + }); +}); + +// Checks that with invalid pageproxystate the identity block is ignored. +add_task(async function testInvalidPageProxyState() { + await SpecialPowers.pushPrefEnv({ set: [["accessibility.tabfocus", 7]] }); + await BrowserTestUtils.withNewTab("about:blank", async function (browser) { + // Loading about:blank will automatically focus the urlbar, which, however, can + // race with the test code. So we only send the shortcut if the urlbar isn't focused yet. + if (document.activeElement != gURLBar.inputField) { + await synthesizeKeyAndWaitForFocus(gURLBar, "l", { accelKey: true }); + } + is(document.activeElement, gURLBar.inputField, "urlbar should be focused"); + await synthesizeKeyAndWaitForFocus( + document.getElementById("home-button"), + "VK_TAB", + { shiftKey: true } + ); + await synthesizeKeyAndWaitForFocus( + document.getElementById("tabs-newtab-button"), + "VK_TAB", + { shiftKey: true } + ); + isnot( + document.activeElement, + gProtectionsHandler._trackingProtectionIconContainer, + "tracking protection icon container should not be focused" + ); + // Restore focus to the url bar. + gURLBar.focus(); + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_identityIcon_img_url.js b/browser/base/content/test/siteIdentity/browser_identityIcon_img_url.js new file mode 100644 index 0000000000..9ef8b6dfed --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_identityIcon_img_url.js @@ -0,0 +1,148 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +/** + * Test Bug 1562881 - Ensuring the identity icon loads correct img in different + * circumstances. + */ + +const kBaseURI = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); + +const kBaseURILocalhost = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://127.0.0.1" +); + +const TEST_CASES = [ + { + type: "http", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + testURL: "http://example.com", + img_url: `url("chrome://global/skin/icons/security-broken.svg")`, + }, + { + type: "https", + testURL: "https://example.com", + img_url: `url("chrome://global/skin/icons/security.svg")`, + }, + { + type: "non-chrome about page", + testURL: "about:about", + img_url: `url("chrome://global/skin/icons/page-portrait.svg")`, + }, + { + type: "chrome about page", + testURL: "about:preferences", + img_url: `url("chrome://branding/content/icon${ + window.devicePixelRatio > 1 ? 32 : 16 + }.png")`, + }, + { + type: "file", + testURL: "dummy_page.html", + img_url: `url("chrome://global/skin/icons/page-portrait.svg")`, + }, + { + type: "resource", + testURL: "resource://gre/modules/Log.sys.mjs", + img_url: `url("chrome://global/skin/icons/page-portrait.svg")`, + }, + { + type: "mixedPassiveContent", + testURL: kBaseURI + "file_mixedPassiveContent.html", + img_url: `url("chrome://global/skin/icons/security-warning.svg")`, + }, + { + type: "mixedActiveContent", + testURL: kBaseURI + "file_csp_block_all_mixedcontent.html", + img_url: `url("chrome://global/skin/icons/security.svg")`, + }, + { + type: "certificateError", + testURL: "https://self-signed.example.com", + img_url: `url("chrome://global/skin/icons/security-warning.svg")`, + }, + { + type: "localhost", + testURL: "http://127.0.0.1", + img_url: `url("chrome://global/skin/icons/page-portrait.svg")`, + }, + { + type: "localhost + http frame", + testURL: kBaseURILocalhost + "file_csp_block_all_mixedcontent.html", + img_url: `url("chrome://global/skin/icons/page-portrait.svg")`, + }, + { + type: "data URI", + testURL: "data:text/html,<div>", + img_url: `url("chrome://global/skin/icons/security-broken.svg")`, + }, + { + type: "view-source HTTP", + testURL: "view-source:http://example.com/", + img_url: `url("chrome://global/skin/icons/security-broken.svg")`, + }, + { + type: "view-source HTTPS", + testURL: "view-source:https://example.com/", + img_url: `url("chrome://global/skin/icons/security.svg")`, + }, +]; + +add_task(async function test() { + await SpecialPowers.pushPrefEnv({ + set: [ + // By default, proxies don't apply to 127.0.0.1. We need them to for this test, though: + ["network.proxy.allow_hijacking_localhost", true], + ["security.mixed_content.upgrade_display_content", false], + ], + }); + + for (let testData of TEST_CASES) { + info(`Testing for ${testData.type}`); + // Open the page for testing. + let testURL = testData.testURL; + + // Overwrite the url if it is testing the file url. + if (testData.type === "file") { + let dir = getChromeDir(getResolvedURI(gTestPath)); + dir.append(testURL); + dir.normalize(); + testURL = Services.io.newFileURI(dir).spec; + } + + let pageLoaded; + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + () => { + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, testURL); + let browser = gBrowser.selectedBrowser; + if (testData.type === "certificateError") { + pageLoaded = BrowserTestUtils.waitForErrorPage(browser); + } else { + pageLoaded = BrowserTestUtils.browserLoaded(browser); + } + }, + false + ); + await pageLoaded; + + let identityIcon = document.getElementById("identity-icon"); + + // Get the image url from the identity icon. + let identityIconImageURL = gBrowser.ownerGlobal + .getComputedStyle(identityIcon) + .getPropertyValue("list-style-image"); + + is( + identityIconImageURL, + testData.img_url, + "The identity icon has a correct image url." + ); + + BrowserTestUtils.removeTab(tab); + } +}); diff --git a/browser/base/content/test/siteIdentity/browser_identityPopup_HttpsOnlyMode.js b/browser/base/content/test/siteIdentity/browser_identityPopup_HttpsOnlyMode.js new file mode 100644 index 0000000000..a83f38e1f6 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_identityPopup_HttpsOnlyMode.js @@ -0,0 +1,191 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const HTTPS_ONLY_PERMISSION = "https-only-load-insecure"; +const WEBSITE = scheme => `${scheme}://example.com`; + +add_task(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_only_mode", true]], + }); + + // Site is already HTTPS, so the UI should not be visible. + await runTest({ + name: "No HTTPS-Only UI", + initialScheme: "https", + initialPermission: 0, + permissionScheme: "https", + isUiVisible: false, + }); + + // Site gets upgraded to HTTPS, so the UI should be visible. + // Disabling HTTPS-Only Mode through the menulist should reload the page and + // set the permission accordingly. + await runTest({ + name: "Disable HTTPS-Only", + initialScheme: "http", + initialPermission: 0, + permissionScheme: "https", + isUiVisible: true, + selectPermission: 1, + expectReload: true, + finalScheme: "https", + }); + + // HTTPS-Only Mode is disabled for this site, so the UI should be visible. + // Disabling HTTPS-Only Mode through the menulist should not reload the page + // but set the permission accordingly. + await runTest({ + name: "Switch between off states", + initialScheme: "http", + initialPermission: 1, + permissionScheme: "http", + isUiVisible: true, + selectPermission: 2, + expectReload: false, + finalScheme: "http", + }); + + // HTTPS-Only Mode is disabled for this site, so the UI should be visible. + // Enabling HTTPS-Only Mode through the menulist should reload and upgrade the + // page and set the permission accordingly. + await runTest({ + name: "Enable HTTPS-Only again", + initialScheme: "http", + initialPermission: 2, + permissionScheme: "http", + isUiVisible: true, + selectPermission: 0, + expectReload: true, + finalScheme: "https", + }); +}); + +async function runTest(options) { + // Set the initial permission + setPermission(WEBSITE(options.permissionScheme), options.initialPermission); + + await BrowserTestUtils.withNewTab( + WEBSITE(options.initialScheme), + async function (browser) { + const name = options.name + " | "; + + // Check if the site has the expected scheme + is( + browser.currentURI.scheme, + options.permissionScheme, + name + "Expected scheme should match actual scheme" + ); + + // Open the identity popup. + let { gIdentityHandler } = gBrowser.ownerGlobal; + let promisePanelOpen = BrowserTestUtils.waitForEvent( + gBrowser.ownerGlobal, + "popupshown", + true, + event => event.target == gIdentityHandler._identityPopup + ); + gIdentityHandler._identityIconBox.click(); + await promisePanelOpen; + + // Check if the HTTPS-Only UI is visible + const httpsOnlyUI = document.getElementById( + "identity-popup-security-httpsonlymode" + ); + is( + gBrowser.ownerGlobal.getComputedStyle(httpsOnlyUI).display != "none", + options.isUiVisible, + options.isUiVisible + ? name + "HTTPS-Only UI should be visible." + : name + "HTTPS-Only UI shouldn't be visible." + ); + + // If it's not visible we can't do much else :) + if (!options.isUiVisible) { + return; + } + + // Check if the value of the menulist matches the initial permission + const httpsOnlyMenulist = document.getElementById( + "identity-popup-security-httpsonlymode-menulist" + ); + is( + parseInt(httpsOnlyMenulist.value, 10), + options.initialPermission, + name + "Menulist value should match expected permission value." + ); + + // Select another HTTPS-Only state and potentially wait for the page to reload + if (options.expectReload) { + const loaded = BrowserTestUtils.browserLoaded(browser); + httpsOnlyMenulist.getItemAtIndex(options.selectPermission).doCommand(); + await loaded; + } else { + httpsOnlyMenulist.getItemAtIndex(options.selectPermission).doCommand(); + } + + // Check if the site has the expected scheme + is( + browser.currentURI.scheme, + options.finalScheme, + name + "Unexpected scheme after page reloaded." + ); + + // Check if the permission was sucessfully changed + is( + getPermission(WEBSITE(options.permissionScheme)), + options.selectPermission, + name + "Set permission should match the one selected from the menulist." + ); + } + ); + + // Reset permission + Services.perms.removeAll(); +} + +function setPermission(url, newValue) { + let uri = Services.io.newURI(url); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + if (newValue === 0) { + Services.perms.removeFromPrincipal(principal, HTTPS_ONLY_PERMISSION); + } else if (newValue === 1) { + Services.perms.addFromPrincipal( + principal, + HTTPS_ONLY_PERMISSION, + Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW, + Ci.nsIPermissionManager.EXPIRE_NEVER + ); + } else { + Services.perms.addFromPrincipal( + principal, + HTTPS_ONLY_PERMISSION, + Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + } +} + +function getPermission(url) { + let uri = Services.io.newURI(url); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + const state = Services.perms.testPermissionFromPrincipal( + principal, + HTTPS_ONLY_PERMISSION + ); + switch (state) { + case Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION: + return 2; // Off temporarily + case Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW: + return 1; // Off + default: + return 0; // On + } +} diff --git a/browser/base/content/test/siteIdentity/browser_identityPopup_clearSiteData.js b/browser/base/content/test/siteIdentity/browser_identityPopup_clearSiteData.js new file mode 100644 index 0000000000..efc4f34310 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_identityPopup_clearSiteData.js @@ -0,0 +1,245 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TEST_ORIGIN = "https://example.com"; +const TEST_SUB_ORIGIN = "https://test1.example.com"; +const TEST_ORIGIN_2 = "https://example.net"; +const REMOVE_DIALOG_URL = + "chrome://browser/content/preferences/dialogs/siteDataRemoveSelected.xhtml"; + +// Greek IDN for 'example.test'. +const TEST_IDN_ORIGIN = + "https://\u03C0\u03B1\u03C1\u03AC\u03B4\u03B5\u03B9\u03B3\u03BC\u03B1.\u03B4\u03BF\u03BA\u03B9\u03BC\u03AE"; +const TEST_PUNY_ORIGIN = "https://xn--hxajbheg2az3al.xn--jxalpdlp/"; +const TEST_PUNY_SUB_ORIGIN = "https://sub1.xn--hxajbheg2az3al.xn--jxalpdlp/"; + +ChromeUtils.defineESModuleGetters(this, { + SiteDataTestUtils: "resource://testing-common/SiteDataTestUtils.sys.mjs", +}); + +async function testClearing( + testQuota, + testCookies, + testURI, + originA, + subOriginA, + originB +) { + // Create a variant of originB which is partitioned under top level originA. + let { scheme, host } = Services.io.newURI(originA); + let partitionKey = `(${scheme},${host})`; + + let { origin: originBPartitioned } = + Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI(originB), + { partitionKey } + ); + + // Add some test quota storage. + if (testQuota) { + await SiteDataTestUtils.addToIndexedDB(originA); + await SiteDataTestUtils.addToIndexedDB(subOriginA); + await SiteDataTestUtils.addToIndexedDB(originBPartitioned); + } + + // Add some test cookies. + if (testCookies) { + SiteDataTestUtils.addToCookies({ + origin: originA, + name: "test1", + value: "1", + }); + SiteDataTestUtils.addToCookies({ + origin: originA, + name: "test2", + value: "2", + }); + SiteDataTestUtils.addToCookies({ + origin: subOriginA, + name: "test3", + value: "1", + }); + + SiteDataTestUtils.addToCookies({ + origin: originBPartitioned, + name: "test4", + value: "1", + }); + } + + await BrowserTestUtils.withNewTab(testURI, async function (browser) { + // Verify we have added quota storage. + if (testQuota) { + let usage = await SiteDataTestUtils.getQuotaUsage(originA); + Assert.greater(usage, 0, "Should have data for the base origin."); + + usage = await SiteDataTestUtils.getQuotaUsage(subOriginA); + Assert.greater(usage, 0, "Should have data for the sub origin."); + + usage = await SiteDataTestUtils.getQuotaUsage(originBPartitioned); + Assert.greater(usage, 0, "Should have data for the partitioned origin."); + } + + // Open the identity popup. + let { gIdentityHandler } = gBrowser.ownerGlobal; + let promisePanelOpen = BrowserTestUtils.waitForEvent( + gBrowser.ownerGlobal, + "popupshown", + true, + event => event.target == gIdentityHandler._identityPopup + ); + gIdentityHandler._identityIconBox.click(); + await promisePanelOpen; + + let clearFooter = document.getElementById( + "identity-popup-clear-sitedata-footer" + ); + let clearButton = document.getElementById( + "identity-popup-clear-sitedata-button" + ); + TestUtils.waitForCondition( + () => !clearFooter.hidden, + "The clear data footer is not hidden." + ); + + let cookiesCleared; + if (testCookies) { + cookiesCleared = Promise.all([ + TestUtils.topicObserved( + "cookie-changed", + (subj, data) => data == "deleted" && subj.name == "test1" + ), + TestUtils.topicObserved( + "cookie-changed", + (subj, data) => data == "deleted" && subj.name == "test2" + ), + TestUtils.topicObserved( + "cookie-changed", + (subj, data) => data == "deleted" && subj.name == "test3" + ), + TestUtils.topicObserved( + "cookie-changed", + (subj, data) => data == "deleted" && subj.name == "test4" + ), + ]); + } + + // Click the "Clear data" button. + let siteDataUpdated = TestUtils.topicObserved( + "sitedatamanager:sites-updated" + ); + let hideEvent = BrowserTestUtils.waitForEvent( + gIdentityHandler._identityPopup, + "popuphidden" + ); + let removeDialogPromise = BrowserTestUtils.promiseAlertDialogOpen( + "accept", + REMOVE_DIALOG_URL + ); + clearButton.click(); + await hideEvent; + await removeDialogPromise; + + await siteDataUpdated; + + // Check that cookies were deleted. + if (testCookies) { + await cookiesCleared; + let uri = Services.io.newURI(originA); + is( + Services.cookies.countCookiesFromHost(uri.host), + 0, + "Cookies from the base domain should be cleared" + ); + uri = Services.io.newURI(subOriginA); + is( + Services.cookies.countCookiesFromHost(uri.host), + 0, + "Cookies from the sub domain should be cleared" + ); + ok( + !SiteDataTestUtils.hasCookies(originBPartitioned), + "Partitioned cookies should be cleared" + ); + } + + // Check that quota storage was deleted. + if (testQuota) { + await TestUtils.waitForCondition(async () => { + let usage = await SiteDataTestUtils.getQuotaUsage(originA); + return usage == 0; + }, "Should have no data for the base origin."); + + let usage = await SiteDataTestUtils.getQuotaUsage(subOriginA); + is(usage, 0, "Should have no data for the sub origin."); + + usage = await SiteDataTestUtils.getQuotaUsage(originBPartitioned); + is(usage, 0, "Should have no data for the partitioned origin."); + } + + // Open the site identity panel again to check that the button isn't shown anymore. + promisePanelOpen = BrowserTestUtils.waitForEvent( + gIdentityHandler._identityPopup, + "popupshown" + ); + gIdentityHandler._identityIconBox.click(); + await promisePanelOpen; + + // Wait for a second to see if the button is shown. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(c => setTimeout(c, 1000)); + + ok( + clearFooter.hidden, + "The clear data footer is hidden after clearing data." + ); + }); +} + +// Test removing quota managed storage. +add_task(async function test_ClearSiteData() { + await testClearing( + true, + false, + TEST_ORIGIN, + TEST_ORIGIN, + TEST_SUB_ORIGIN, + TEST_ORIGIN_2 + ); +}); + +// Test removing cookies. +add_task(async function test_ClearCookies() { + await testClearing( + false, + true, + TEST_ORIGIN, + TEST_ORIGIN, + TEST_SUB_ORIGIN, + TEST_ORIGIN_2 + ); +}); + +// Test removing both. +add_task(async function test_ClearCookiesAndSiteData() { + await testClearing( + true, + true, + TEST_ORIGIN, + TEST_ORIGIN, + TEST_SUB_ORIGIN, + TEST_ORIGIN_2 + ); +}); + +// Test IDN Domains +add_task(async function test_IDN_ClearCookiesAndSiteData() { + await testClearing( + true, + true, + TEST_IDN_ORIGIN, + TEST_PUNY_ORIGIN, + TEST_PUNY_SUB_ORIGIN, + TEST_ORIGIN_2 + ); +}); diff --git a/browser/base/content/test/siteIdentity/browser_identityPopup_clearSiteData_extensions.js b/browser/base/content/test/siteIdentity/browser_identityPopup_clearSiteData_extensions.js new file mode 100644 index 0000000000..2d0d9f7068 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_identityPopup_clearSiteData_extensions.js @@ -0,0 +1,80 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +/* + * Test for Bug 1661534 - Extension page: "Clear Cookies and Site Data" + * does nothing. + * + * Expected behavior: when viewing a page controlled by a WebExtension, + * the "Clear Cookies and Site Data..." button should not be visible. + */ + +add_task(async function testClearSiteDataFooterHiddenForExtensions() { + // Create an extension that opens an options page + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "temporary", + + manifest: { + permissions: ["tabs"], + options_ui: { + page: "options.html", + open_in_tab: true, + }, + }, + files: { + "options.html": `<!DOCTYPE html> + <html> + <head> + <meta charset="utf-8"> + </head> + <body> + <h1>This is a test options page for a WebExtension</h1> + </body> + </html>`, + }, + async background() { + await browser.runtime.openOptionsPage(); + browser.test.sendMessage("optionsopened"); + }, + }); + + // Run the extension and wait until its options page has finished loading + let browser = gBrowser.selectedBrowser; + let browserLoadedPromise = BrowserTestUtils.browserLoaded(browser); + await extension.startup(); + await extension.awaitMessage("optionsopened"); + await browserLoadedPromise; + + await SpecialPowers.spawn(browser, [], () => { + ok( + content.document.documentURI.startsWith("moz-extension://"), + "Extension page has now finished loading in the browser window" + ); + }); + + // Open the site identity popup + let { gIdentityHandler } = gBrowser.ownerGlobal; + let promisePanelOpen = BrowserTestUtils.waitForEvent( + gBrowser.ownerGlobal, + "popupshown", + true, + event => event.target == gIdentityHandler._identityPopup + ); + gIdentityHandler._identityIconBox.click(); + await promisePanelOpen; + + let clearSiteDataFooter = document.getElementById( + "identity-popup-clear-sitedata-footer" + ); + + ok( + clearSiteDataFooter.hidden, + "The clear site data footer is hidden on a WebExtension page." + ); + + // Unload the extension + await extension.unload(); +}); diff --git a/browser/base/content/test/siteIdentity/browser_identityPopup_custom_roots.js b/browser/base/content/test/siteIdentity/browser_identityPopup_custom_roots.js new file mode 100644 index 0000000000..2b9ef53bb0 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_identityPopup_custom_roots.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Test that the UI for imported root certificates shows up correctly in the identity popup. + */ + +const TEST_PATH = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); + +// This test is incredibly simple, because our test framework already +// imports root certificates by default, so we just visit example.com +// and verify that the custom root certificates UI is visible. +add_task(async function test_https() { + await BrowserTestUtils.withNewTab("https://example.com", async function () { + let promisePanelOpen = BrowserTestUtils.waitForEvent( + window, + "popupshown", + true, + event => event.target == gIdentityHandler._identityPopup + ); + + gIdentityHandler._identityIconBox.click(); + await promisePanelOpen; + let customRootWarning = document.getElementById( + "identity-popup-security-decription-custom-root" + ); + ok( + BrowserTestUtils.is_visible(customRootWarning), + "custom root warning is visible" + ); + + let securityView = document.getElementById("identity-popup-securityView"); + let shown = BrowserTestUtils.waitForEvent(securityView, "ViewShown"); + document.getElementById("identity-popup-security-button").click(); + await shown; + + let subPanelInfo = document.getElementById( + "identity-popup-content-verifier-unknown" + ); + ok( + BrowserTestUtils.is_visible(subPanelInfo), + "custom root warning in sub panel is visible" + ); + }); +}); + +// Also check that there are conditions where this isn't shown. +add_task(async function test_http() { + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + await BrowserTestUtils.withNewTab("http://example.com", async function () { + let promisePanelOpen = BrowserTestUtils.waitForEvent( + window, + "popupshown", + true, + event => event.target == gIdentityHandler._identityPopup + ); + gIdentityHandler._identityIconBox.click(); + await promisePanelOpen; + let customRootWarning = document.getElementById( + "identity-popup-security-decription-custom-root" + ); + ok( + BrowserTestUtils.is_hidden(customRootWarning), + "custom root warning is hidden" + ); + + let securityView = document.getElementById("identity-popup-securityView"); + let shown = BrowserTestUtils.waitForEvent(securityView, "ViewShown"); + document.getElementById("identity-popup-security-button").click(); + await shown; + + let subPanelInfo = document.getElementById( + "identity-popup-content-verifier-unknown" + ); + ok( + BrowserTestUtils.is_hidden(subPanelInfo), + "custom root warning in sub panel is hidden" + ); + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js b/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js new file mode 100644 index 0000000000..80e70619ff --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js @@ -0,0 +1,120 @@ +/* Tests the focus behavior of the identity popup. */ + +// Focusing on the identity box is handled by the ToolbarKeyboardNavigator +// component (see browser/base/content/browser-toolbarKeyNav.js). +async function focusIdentityBox() { + gURLBar.inputField.focus(); + is(document.activeElement, gURLBar.inputField, "urlbar should be focused"); + const focused = BrowserTestUtils.waitForEvent( + gIdentityHandler._identityIconBox, + "focus" + ); + EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }); + EventUtils.synthesizeKey("ArrowRight"); + await focused; +} + +// Access the identity popup via mouseclick. Focus should not be moved inside. +add_task(async function testIdentityPopupFocusClick() { + await SpecialPowers.pushPrefEnv({ set: [["accessibility.tabfocus", 7]] }); + await BrowserTestUtils.withNewTab("https://example.com", async function () { + let shown = BrowserTestUtils.waitForEvent( + window, + "popupshown", + true, + event => event.target == gIdentityHandler._identityPopup + ); + EventUtils.synthesizeMouseAtCenter(gIdentityHandler._identityIconBox, {}); + await shown; + isnot( + Services.focus.focusedElement, + document.getElementById("identity-popup-security-button") + ); + }); +}); + +// Access the identity popup via keyboard. Focus should be moved inside. +add_task(async function testIdentityPopupFocusKeyboard() { + await SpecialPowers.pushPrefEnv({ set: [["accessibility.tabfocus", 7]] }); + await BrowserTestUtils.withNewTab("https://example.com", async function () { + await focusIdentityBox(); + let shown = BrowserTestUtils.waitForEvent( + window, + "popupshown", + true, + event => event.target == gIdentityHandler._identityPopup + ); + EventUtils.sendString(" "); + await shown; + is( + Services.focus.focusedElement, + document.getElementById("identity-popup-security-button") + ); + }); +}); + +// Access the Site Security panel, then move focus with the tab key. +// Tabbing should be able to reach the More Information button. +add_task(async function testSiteSecurityTabOrder() { + await SpecialPowers.pushPrefEnv({ set: [["accessibility.tabfocus", 7]] }); + await BrowserTestUtils.withNewTab("https://example.com", async function () { + // 1. Access the identity popup. + await focusIdentityBox(); + let shown = BrowserTestUtils.waitForEvent( + window, + "popupshown", + true, + event => event.target == gIdentityHandler._identityPopup + ); + EventUtils.sendString(" "); + await shown; + is( + Services.focus.focusedElement, + document.getElementById("identity-popup-security-button") + ); + + // 2. Access the Site Security section. + let securityView = document.getElementById("identity-popup-securityView"); + shown = BrowserTestUtils.waitForEvent(securityView, "ViewShown"); + EventUtils.sendString(" "); + await shown; + + // 3. Custom root learn more info should be focused by default + // This is probably not present in real-world scenarios, but needs to be present in our test infrastructure. + let customRootLearnMore = document.getElementById( + "identity-popup-custom-root-learn-more" + ); + is( + Services.focus.focusedElement, + customRootLearnMore, + "learn more option for custom roots is focused" + ); + + // 4. First press of tab should move to the More Information button. + let moreInfoButton = document.getElementById("identity-popup-more-info"); + let focused = BrowserTestUtils.waitForEvent( + gIdentityHandler._identityPopup, + "focusin" + ); + EventUtils.sendKey("tab"); + await focused; + is( + Services.focus.focusedElement, + moreInfoButton, + "more info button is focused" + ); + + // 5. Second press of tab should focus the Back button. + let backButton = gIdentityHandler._identityPopup.querySelector( + ".subviewbutton-back" + ); + // Wait for focus to move somewhere. We use focusin because focus doesn't bubble. + focused = BrowserTestUtils.waitForEvent( + gIdentityHandler._identityPopup, + "focusin" + ); + EventUtils.sendKey("tab"); + await focused; + is(Services.focus.focusedElement, backButton, "back button is focused"); + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_identity_UI.js b/browser/base/content/test/siteIdentity/browser_identity_UI.js new file mode 100644 index 0000000000..bda5e225b0 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_identity_UI.js @@ -0,0 +1,192 @@ +/* Tests for correct behaviour of getHostForDisplay on identity handler */ + +requestLongerTimeout(2); + +// Greek IDN for 'example.test'. +var idnDomain = + "\u03C0\u03B1\u03C1\u03AC\u03B4\u03B5\u03B9\u03B3\u03BC\u03B1.\u03B4\u03BF\u03BA\u03B9\u03BC\u03AE"; +var tests = [ + { + name: "normal domain", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + location: "http://test1.example.org/", + hostForDisplay: "test1.example.org", + hasSubview: true, + }, + { + name: "view-source", + location: "view-source:http://example.com/", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + newURI: "http://example.com/", + hostForDisplay: "example.com", + hasSubview: true, + }, + { + name: "normal HTTPS", + location: "https://example.com/", + hostForDisplay: "example.com", + hasSubview: true, + }, + { + name: "IDN subdomain", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + location: "http://sub1.xn--hxajbheg2az3al.xn--jxalpdlp/", + hostForDisplay: "sub1." + idnDomain, + hasSubview: true, + }, + { + name: "subdomain with port", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + location: "http://sub1.test1.example.org:8000/", + hostForDisplay: "sub1.test1.example.org", + hasSubview: true, + }, + { + name: "subdomain HTTPS", + location: "https://test1.example.com/", + hostForDisplay: "test1.example.com", + hasSubview: true, + }, + { + name: "view-source HTTPS", + location: "view-source:https://example.com/", + newURI: "https://example.com/", + hostForDisplay: "example.com", + hasSubview: true, + }, + { + name: "IP address", + location: "http://127.0.0.1:8888/", + hostForDisplay: "127.0.0.1", + hasSubview: false, + }, + { + name: "about:certificate", + location: + "about:certificate?cert=MIIHQjCCBiqgAwIBAgIQCgYwQn9bvO&cert=1pVzllk7ZFHzANBgkqhkiG9w0BAQ", + hostForDisplay: "about:certificate", + hasSubview: false, + }, + { + name: "about:reader", + location: "about:reader?url=http://example.com", + hostForDisplay: "example.com", + hasSubview: false, + }, + { + name: "chrome:", + location: "chrome://global/skin/in-content/info-pages.css", + hostForDisplay: "chrome://global/skin/in-content/info-pages.css", + hasSubview: false, + }, +]; + +add_task(async function test() { + ok(gIdentityHandler, "gIdentityHandler should exist"); + + await BrowserTestUtils.openNewForegroundTab(gBrowser); + + for (let i = 0; i < tests.length; i++) { + await runTest(i, true); + } + + gBrowser.removeCurrentTab(); + await BrowserTestUtils.openNewForegroundTab(gBrowser); + + for (let i = tests.length - 1; i >= 0; i--) { + await runTest(i, false); + } + + gBrowser.removeCurrentTab(); +}); + +async function runTest(i, forward) { + let currentTest = tests[i]; + let testDesc = "#" + i + " (" + currentTest.name + ")"; + if (!forward) { + testDesc += " (second time)"; + } + + info("Running test " + testDesc); + + let popupHidden = null; + if ((forward && i > 0) || (!forward && i < tests.length - 1)) { + popupHidden = BrowserTestUtils.waitForEvent( + gIdentityHandler._identityPopup, + "popuphidden" + ); + } + + let loaded = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + currentTest.location + ); + BrowserTestUtils.loadURIString( + gBrowser.selectedBrowser, + currentTest.location + ); + await loaded; + await popupHidden; + ok( + !gIdentityHandler._identityPopup || + BrowserTestUtils.is_hidden(gIdentityHandler._identityPopup), + "Control Center is hidden" + ); + + // Sanity check other values, and the value of gIdentityHandler.getHostForDisplay() + is( + gIdentityHandler._uri.spec, + currentTest.newURI || currentTest.location, + "location matches for test " + testDesc + ); + // getHostForDisplay can't be called for all modes + if (currentTest.hostForDisplay !== null) { + is( + gIdentityHandler.getHostForDisplay(), + currentTest.hostForDisplay, + "hostForDisplay matches for test " + testDesc + ); + } + + // Open the Control Center and make sure it closes after nav (Bug 1207542). + let popupShown = BrowserTestUtils.waitForEvent( + window, + "popupshown", + true, + event => event.target == gIdentityHandler._identityPopup + ); + gIdentityHandler._identityIconBox.click(); + info("Waiting for the Control Center to be shown"); + await popupShown; + ok( + !BrowserTestUtils.is_hidden(gIdentityHandler._identityPopup), + "Control Center is visible" + ); + let displayedHost = currentTest.hostForDisplay || currentTest.location; + ok( + gIdentityHandler._identityPopupMainViewHeaderLabel.textContent.includes( + displayedHost + ), + "identity UI header shows the host for test " + testDesc + ); + + let securityButton = gBrowser.ownerDocument.querySelector( + "#identity-popup-security-button" + ); + is( + securityButton.disabled, + !currentTest.hasSubview, + "Security button has correct disabled state" + ); + if (currentTest.hasSubview) { + // Show the subview, which is an easy way in automation to reproduce + // Bug 1207542, where the CC wouldn't close on navigation. + let promiseViewShown = BrowserTestUtils.waitForEvent( + gIdentityHandler._identityPopup, + "ViewShown" + ); + securityButton.click(); + await promiseViewShown; + } +} diff --git a/browser/base/content/test/siteIdentity/browser_iframe_navigation.js b/browser/base/content/test/siteIdentity/browser_iframe_navigation.js new file mode 100644 index 0000000000..ac2884d31a --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_iframe_navigation.js @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the site identity icon and related machinery reflects the correct +// security state after navigating an iframe in various contexts. +// See bug 1490982. + +const ROOT_URI = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); +const SECURE_TEST_URI = ROOT_URI + "iframe_navigation.html"; +// eslint-disable-next-line @microsoft/sdl/no-insecure-url +const INSECURE_TEST_URI = SECURE_TEST_URI.replace("https://", "http://"); + +// From a secure URI, navigate the iframe to about:blank (should still be +// secure). +add_task(async function () { + let uri = SECURE_TEST_URI + "#blank"; + await BrowserTestUtils.withNewTab(uri, async browser => { + let identityMode = window.document.getElementById("identity-box").className; + is(identityMode, "verifiedDomain", "identity should be secure before"); + + await SpecialPowers.spawn(browser, [], async () => { + content.postMessage("", "*"); // This kicks off the navigation. + await ContentTaskUtils.waitForCondition(() => { + return !content.document.body.classList.contains("running"); + }); + }); + + let newIdentityMode = + window.document.getElementById("identity-box").className; + is(newIdentityMode, "verifiedDomain", "identity should be secure after"); + }); +}); + +// From a secure URI, navigate the iframe to an insecure URI (http://...) +// (mixed active content should be blocked, should still be secure). +add_task(async function () { + let uri = SECURE_TEST_URI + "#insecure"; + await BrowserTestUtils.withNewTab(uri, async browser => { + let identityMode = window.document.getElementById("identity-box").className; + is(identityMode, "verifiedDomain", "identity should be secure before"); + + await SpecialPowers.spawn(browser, [], async () => { + content.postMessage("", "*"); // This kicks off the navigation. + await ContentTaskUtils.waitForCondition(() => { + return !content.document.body.classList.contains("running"); + }); + }); + + let newIdentityMode = + window.document.getElementById("identity-box").classList; + ok( + newIdentityMode.contains("mixedActiveBlocked"), + "identity should be blocked mixed active content after" + ); + ok( + newIdentityMode.contains("verifiedDomain"), + "identity should still contain 'verifiedDomain'" + ); + is(newIdentityMode.length, 2, "shouldn't have any other identity states"); + }); +}); + +// From an insecure URI (http://..), navigate the iframe to about:blank (should +// still be insecure). +add_task(async function () { + let uri = INSECURE_TEST_URI + "#blank"; + await BrowserTestUtils.withNewTab(uri, async browser => { + let identityMode = window.document.getElementById("identity-box").className; + is(identityMode, "notSecure", "identity should be 'not secure' before"); + + await SpecialPowers.spawn(browser, [], async () => { + content.postMessage("", "*"); // This kicks off the navigation. + await ContentTaskUtils.waitForCondition(() => { + return !content.document.body.classList.contains("running"); + }); + }); + + let newIdentityMode = + window.document.getElementById("identity-box").className; + is(newIdentityMode, "notSecure", "identity should be 'not secure' after"); + }); +}); + +// From an insecure URI (http://..), navigate the iframe to a secure URI +// (https://...) (should still be insecure). +add_task(async function () { + let uri = INSECURE_TEST_URI + "#secure"; + await BrowserTestUtils.withNewTab(uri, async browser => { + let identityMode = window.document.getElementById("identity-box").className; + is(identityMode, "notSecure", "identity should be 'not secure' before"); + + await SpecialPowers.spawn(browser, [], async () => { + content.postMessage("", "*"); // This kicks off the navigation. + await ContentTaskUtils.waitForCondition(() => { + return !content.document.body.classList.contains("running"); + }); + }); + + let newIdentityMode = + window.document.getElementById("identity-box").className; + is(newIdentityMode, "notSecure", "identity should be 'not secure' after"); + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_ignore_same_page_navigation.js b/browser/base/content/test/siteIdentity/browser_ignore_same_page_navigation.js new file mode 100644 index 0000000000..86ec70a0cf --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_ignore_same_page_navigation.js @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that the nsISecureBrowserUI implementation doesn't send extraneous OnSecurityChange events +// when it receives OnLocationChange events with the LOCATION_CHANGE_SAME_DOCUMENT flag set. + +add_task(async function () { + await BrowserTestUtils.withNewTab("about:blank", async browser => { + let onLocationChangeCount = 0; + let onSecurityChangeCount = 0; + let progressListener = { + onStateChange() {}, + onLocationChange() { + onLocationChangeCount++; + }, + onSecurityChange() { + onSecurityChangeCount++; + }, + onProgressChange() {}, + onStatusChange() {}, + + QueryInterface: ChromeUtils.generateQI([ + "nsIWebProgressListener", + "nsISupportsWeakReference", + ]), + }; + browser.addProgressListener(progressListener, Ci.nsIWebProgress.NOTIFY_ALL); + + let uri = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "dummy_page.html"; + BrowserTestUtils.loadURIString(browser, uri); + await BrowserTestUtils.browserLoaded(browser, false, uri); + is(onLocationChangeCount, 1, "should have 1 onLocationChange event"); + is(onSecurityChangeCount, 1, "should have 1 onSecurityChange event"); + await SpecialPowers.spawn(browser, [], async () => { + content.history.pushState({}, "", "https://example.com"); + }); + is(onLocationChangeCount, 2, "should have 2 onLocationChange events"); + is( + onSecurityChangeCount, + 1, + "should still have only 1 onSecurityChange event" + ); + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_mcb_redirect.js b/browser/base/content/test/siteIdentity/browser_mcb_redirect.js new file mode 100644 index 0000000000..df7f6be15c --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_mcb_redirect.js @@ -0,0 +1,360 @@ +/* + * Description of the Tests for + * - Bug 418354 - Call Mixed content blocking on redirects + * + * Single redirect script tests + * 1. Load a script over https inside an https page + * - the server responds with a 302 redirect to a >> HTTP << script + * - the doorhanger should appear! + * + * 2. Load a script over https inside an http page + * - the server responds with a 302 redirect to a >> HTTP << script + * - the doorhanger should not appear! + * + * Single redirect image tests + * 3. Load an image over https inside an https page + * - the server responds with a 302 redirect to a >> HTTP << image + * - the image should not load + * + * 4. Load an image over https inside an http page + * - the server responds with a 302 redirect to a >> HTTP << image + * - the image should load and get cached + * + * Single redirect cached image tests + * 5. Using offline mode to ensure we hit the cache, load a cached image over + * https inside an http page + * - the server would have responded with a 302 redirect to a >> HTTP << + * image, but instead we try to use the cached image. + * - the image should load + * + * 6. Using offline mode to ensure we hit the cache, load a cached image over + * https inside an https page + * - the server would have responded with a 302 redirect to a >> HTTP << + * image, but instead we try to use the cached image. + * - the image should not load + * + * Double redirect image test + * 7. Load an image over https inside an http page + * - the server responds with a 302 redirect to a >> HTTP << server + * - the HTTP server responds with a 302 redirect to a >> HTTPS << image + * - the image should load and get cached + * + * Double redirect cached image tests + * 8. Using offline mode to ensure we hit the cache, load a cached image over + * https inside an http page + * - the image would have gone through two redirects: HTTPS->HTTP->HTTPS, + * but instead we try to use the cached image. + * - the image should load + * + * 9. Using offline mode to ensure we hit the cache, load a cached image over + * https inside an https page + * - the image would have gone through two redirects: HTTPS->HTTP->HTTPS, + * but instead we try to use the cached image. + * - the image should not load + */ + +const PREF_ACTIVE = "security.mixed_content.block_active_content"; +const PREF_DISPLAY = "security.mixed_content.block_display_content"; +const PREF_DISPLAY_UPGRADE = "security.mixed_content.upgrade_display_content"; +const HTTPS_TEST_ROOT = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); +const HTTP_TEST_ROOT = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com" +); +const PREF_INSECURE_ICON = "security.insecure_connection_icon.enabled"; + +var origBlockActive; +var origBlockDisplay; +var origUpgradeDisplay; +var origInsecurePref; +var gTestBrowser = null; + +// ------------------------ Helper Functions --------------------- + +registerCleanupFunction(function () { + // Set preferences back to their original values + Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive); + Services.prefs.setBoolPref(PREF_DISPLAY, origBlockDisplay); + Services.prefs.setBoolPref(PREF_DISPLAY_UPGRADE, origUpgradeDisplay); + Services.prefs.setBoolPref(PREF_INSECURE_ICON, origInsecurePref); + + // Make sure we are online again + Services.io.offline = false; +}); + +function cleanUpAfterTests() { + gBrowser.removeCurrentTab(); + window.focus(); + finish(); +} + +// ------------------------ Test 1 ------------------------------ + +function test1() { + Services.prefs.setBoolPref(PREF_INSECURE_ICON, false); + + var url = HTTPS_TEST_ROOT + "test_mcb_redirect.html"; + BrowserTestUtils.browserLoaded(gTestBrowser, false, url).then( + checkUIForTest1 + ); + BrowserTestUtils.loadURIString(gTestBrowser, url); +} + +function testInsecure1() { + Services.prefs.setBoolPref(PREF_INSECURE_ICON, true); + + var url = HTTPS_TEST_ROOT + "test_mcb_redirect.html"; + BrowserTestUtils.browserLoaded(gTestBrowser, false, url).then( + checkUIForTest1 + ); + BrowserTestUtils.loadURIString(gTestBrowser, url); +} + +async function checkUIForTest1() { + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); + + SpecialPowers.spawn(gTestBrowser, [], async function () { + var expected = "script blocked"; + await ContentTaskUtils.waitForCondition( + () => content.document.getElementById("mctestdiv").innerHTML == expected, + "OK: Expected result in innerHTML for Test1!" + ); + }).then(test2); +} + +// ------------------------ Test 2 ------------------------------ + +function test2() { + var url = HTTP_TEST_ROOT + "test_mcb_redirect.html"; + BrowserTestUtils.browserLoaded(gTestBrowser, false, url).then( + checkUIForTest2 + ); + BrowserTestUtils.loadURIString(gTestBrowser, url); +} + +async function checkUIForTest2() { + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: false, + activeBlocked: false, + passiveLoaded: false, + }); + + SpecialPowers.spawn(gTestBrowser, [], async function () { + var expected = "script executed"; + await ContentTaskUtils.waitForCondition( + () => content.document.getElementById("mctestdiv").innerHTML == expected, + "OK: Expected result in innerHTML for Test2!" + ); + }).then(test3); +} + +// ------------------------ Test 3 ------------------------------ +// HTTPS page loading insecure image +function test3() { + info("test3"); + var url = HTTPS_TEST_ROOT + "test_mcb_redirect_image.html"; + BrowserTestUtils.browserLoaded(gTestBrowser, false, url).then( + checkLoadEventForTest3 + ); + BrowserTestUtils.loadURIString(gTestBrowser, url); +} + +function checkLoadEventForTest3() { + SpecialPowers.spawn(gTestBrowser, [], async function () { + var expected = "image blocked"; + await ContentTaskUtils.waitForCondition( + () => content.document.getElementById("mctestdiv").innerHTML == expected, + "OK: Expected result in innerHTML for Test3!" + ); + }).then(test4); +} + +// ------------------------ Test 4 ------------------------------ +// HTTP page loading insecure image +function test4() { + info("test4"); + var url = HTTP_TEST_ROOT + "test_mcb_redirect_image.html"; + BrowserTestUtils.browserLoaded(gTestBrowser, false, url).then( + checkLoadEventForTest4 + ); + BrowserTestUtils.loadURIString(gTestBrowser, url); +} + +function checkLoadEventForTest4() { + SpecialPowers.spawn(gTestBrowser, [], async function () { + var expected = "image loaded"; + await ContentTaskUtils.waitForCondition( + () => content.document.getElementById("mctestdiv").innerHTML == expected, + "OK: Expected result in innerHTML for Test4!" + ); + }).then(test5); +} + +// ------------------------ Test 5 ------------------------------ +// HTTP page laoding insecure cached image +// Assuming test 4 succeeded, the image has already been loaded once +// and hence should be cached per the sjs cache-control header +// Going into offline mode to ensure we are loading from the cache. +function test5() { + // Go into offline mode + info("test5"); + Services.io.offline = true; + var url = HTTP_TEST_ROOT + "test_mcb_redirect_image.html"; + BrowserTestUtils.browserLoaded(gTestBrowser, false, url).then( + checkLoadEventForTest5 + ); + BrowserTestUtils.loadURIString(gTestBrowser, url); +} + +function checkLoadEventForTest5() { + SpecialPowers.spawn(gTestBrowser, [], async function () { + var expected = "image loaded"; + await ContentTaskUtils.waitForCondition( + () => content.document.getElementById("mctestdiv").innerHTML == expected, + "OK: Expected result in innerHTML for Test5!" + ); + }).then(() => { + // Go back online + Services.io.offline = false; + test6(); + }); +} + +// ------------------------ Test 6 ------------------------------ +// HTTPS page loading insecure cached image +// Assuming test 4 succeeded, the image has already been loaded once +// and hence should be cached per the sjs cache-control header +// Going into offline mode to ensure we are loading from the cache. +function test6() { + // Go into offline mode + info("test6"); + Services.io.offline = true; + var url = HTTPS_TEST_ROOT + "test_mcb_redirect_image.html"; + BrowserTestUtils.browserLoaded(gTestBrowser, false, url).then( + checkLoadEventForTest6 + ); + BrowserTestUtils.loadURIString(gTestBrowser, url); +} + +function checkLoadEventForTest6() { + SpecialPowers.spawn(gTestBrowser, [], async function () { + var expected = "image blocked"; + await ContentTaskUtils.waitForCondition( + () => content.document.getElementById("mctestdiv").innerHTML == expected, + "OK: Expected result in innerHTML for Test6!" + ); + }).then(() => { + // Go back online + Services.io.offline = false; + test7(); + }); +} + +// ------------------------ Test 7 ------------------------------ +// HTTP page loading insecure image that went through a double redirect +function test7() { + var url = HTTP_TEST_ROOT + "test_mcb_double_redirect_image.html"; + BrowserTestUtils.browserLoaded(gTestBrowser, false, url).then( + checkLoadEventForTest7 + ); + BrowserTestUtils.loadURIString(gTestBrowser, url); +} + +function checkLoadEventForTest7() { + SpecialPowers.spawn(gTestBrowser, [], async function () { + var expected = "image loaded"; + await ContentTaskUtils.waitForCondition( + () => content.document.getElementById("mctestdiv").innerHTML == expected, + "OK: Expected result in innerHTML for Test7!" + ); + }).then(test8); +} + +// ------------------------ Test 8 ------------------------------ +// HTTP page loading insecure cached image that went through a double redirect +// Assuming test 7 succeeded, the image has already been loaded once +// and hence should be cached per the sjs cache-control header +// Going into offline mode to ensure we are loading from the cache. +function test8() { + // Go into offline mode + Services.io.offline = true; + var url = HTTP_TEST_ROOT + "test_mcb_double_redirect_image.html"; + BrowserTestUtils.browserLoaded(gTestBrowser, false, url).then( + checkLoadEventForTest8 + ); + BrowserTestUtils.loadURIString(gTestBrowser, url); +} + +function checkLoadEventForTest8() { + SpecialPowers.spawn(gTestBrowser, [], async function () { + var expected = "image loaded"; + await ContentTaskUtils.waitForCondition( + () => content.document.getElementById("mctestdiv").innerHTML == expected, + "OK: Expected result in innerHTML for Test8!" + ); + }).then(() => { + // Go back online + Services.io.offline = false; + test9(); + }); +} + +// ------------------------ Test 9 ------------------------------ +// HTTPS page loading insecure cached image that went through a double redirect +// Assuming test 7 succeeded, the image has already been loaded once +// and hence should be cached per the sjs cache-control header +// Going into offline mode to ensure we are loading from the cache. +function test9() { + // Go into offline mode + Services.io.offline = true; + var url = HTTPS_TEST_ROOT + "test_mcb_double_redirect_image.html"; + BrowserTestUtils.browserLoaded(gTestBrowser, false, url).then( + checkLoadEventForTest9 + ); + BrowserTestUtils.loadURIString(gTestBrowser, url); +} + +function checkLoadEventForTest9() { + SpecialPowers.spawn(gTestBrowser, [], async function () { + var expected = "image blocked"; + await ContentTaskUtils.waitForCondition( + () => content.document.getElementById("mctestdiv").innerHTML == expected, + "OK: Expected result in innerHTML for Test9!" + ); + }).then(() => { + // Go back online + Services.io.offline = false; + cleanUpAfterTests(); + }); +} + +// ------------------------ SETUP ------------------------------ + +function test() { + // Performing async calls, e.g. 'onload', we have to wait till all of them finished + waitForExplicitFinish(); + + // Store original preferences so we can restore settings after testing + origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE); + origBlockDisplay = Services.prefs.getBoolPref(PREF_DISPLAY); + origUpgradeDisplay = Services.prefs.getBoolPref(PREF_DISPLAY_UPGRADE); + origInsecurePref = Services.prefs.getBoolPref(PREF_INSECURE_ICON); + Services.prefs.setBoolPref(PREF_ACTIVE, true); + Services.prefs.setBoolPref(PREF_DISPLAY, true); + Services.prefs.setBoolPref(PREF_DISPLAY_UPGRADE, false); + + var newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + gTestBrowser = gBrowser.selectedBrowser; + newTab.linkedBrowser.stop(); + + executeSoon(testInsecure1); +} diff --git a/browser/base/content/test/siteIdentity/browser_mixedContentFramesOnHttp.js b/browser/base/content/test/siteIdentity/browser_mixedContentFramesOnHttp.js new file mode 100644 index 0000000000..c6096342cc --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_mixedContentFramesOnHttp.js @@ -0,0 +1,37 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * Test for Bug 1182551 - + * + * This test has a top level HTTP page with an HTTPS iframe. The HTTPS iframe + * includes an HTTP image. We check that the top level security state is + * STATE_IS_INSECURE. The mixed content from the iframe shouldn't "upgrade" + * the HTTP top level page to broken HTTPS. + */ + +const TEST_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com" + ) + "file_mixedContentFramesOnHttp.html"; + +add_task(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["security.mixed_content.block_active_content", true], + ["security.mixed_content.block_display_content", false], + ["security.mixed_content.upgrade_display_content", false], + ], + }); + + await BrowserTestUtils.withNewTab(TEST_URL, async function (browser) { + isSecurityState(browser, "insecure"); + await assertMixedContentBlockingState(browser, { + activeLoaded: false, + activeBlocked: false, + passiveLoaded: true, + }); + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_mixedContentFromOnunload.js b/browser/base/content/test/siteIdentity/browser_mixedContentFromOnunload.js new file mode 100644 index 0000000000..c9e11e54a7 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_mixedContentFromOnunload.js @@ -0,0 +1,68 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * Tests for Bug 947079 - Fix bug in nsSecureBrowserUIImpl that sets the wrong + * security state on a page because of a subresource load that is not on the + * same page. + */ + +// We use different domains for each test and for navigation within each test +const HTTP_TEST_ROOT_1 = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com" +); +const HTTPS_TEST_ROOT_1 = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://test1.example.com" +); +const HTTP_TEST_ROOT_2 = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.net" +); +const HTTPS_TEST_ROOT_2 = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://test2.example.com" +); + +add_task(async function () { + let url = HTTP_TEST_ROOT_1 + "file_mixedContentFromOnunload.html"; + await BrowserTestUtils.withNewTab(url, async function (browser) { + await SpecialPowers.pushPrefEnv({ + set: [ + ["security.mixed_content.block_active_content", true], + ["security.mixed_content.block_display_content", false], + ["security.mixed_content.upgrade_display_content", false], + ], + }); + // Navigation from an http page to a https page with no mixed content + // The http page loads an http image on unload + url = HTTPS_TEST_ROOT_1 + "file_mixedContentFromOnunload_test1.html"; + BrowserTestUtils.loadURIString(browser, url); + await BrowserTestUtils.browserLoaded(browser); + // check security state. Since current url is https and doesn't have any + // mixed content resources, we expect it to be secure. + isSecurityState(browser, "secure"); + await assertMixedContentBlockingState(browser, { + activeLoaded: false, + activeBlocked: false, + passiveLoaded: false, + }); + // Navigation from an http page to a https page that has mixed display content + // The https page loads an http image on unload + url = HTTP_TEST_ROOT_2 + "file_mixedContentFromOnunload.html"; + BrowserTestUtils.loadURIString(browser, url); + await BrowserTestUtils.browserLoaded(browser); + url = HTTPS_TEST_ROOT_2 + "file_mixedContentFromOnunload_test2.html"; + BrowserTestUtils.loadURIString(browser, url); + await BrowserTestUtils.browserLoaded(browser); + isSecurityState(browser, "broken"); + await assertMixedContentBlockingState(browser, { + activeLoaded: false, + activeBlocked: false, + passiveLoaded: true, + }); + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_mixed_content_cert_override.js b/browser/base/content/test/siteIdentity/browser_mixed_content_cert_override.js new file mode 100644 index 0000000000..6ca9655406 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_mixed_content_cert_override.js @@ -0,0 +1,69 @@ +/* + * Bug 1253771 - check mixed content blocking in combination with overriden certificates + */ + +"use strict"; + +const MIXED_CONTENT_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://self-signed.example.com" + ) + "test-mixedcontent-securityerrors.html"; + +function getConnectionState() { + return document.getElementById("identity-popup").getAttribute("connection"); +} + +function getPopupContentVerifier() { + return document.getElementById("identity-popup-content-verifier"); +} + +function getIdentityIcon() { + return window.getComputedStyle(document.getElementById("identity-icon")) + .listStyleImage; +} + +function checkIdentityPopup(icon) { + gIdentityHandler.refreshIdentityPopup(); + is(getIdentityIcon(), `url("chrome://global/skin/icons/${icon}")`); + is(getConnectionState(), "secure-cert-user-overridden"); + isnot( + getPopupContentVerifier().style.display, + "none", + "Overridden certificate warning is shown" + ); + ok( + getPopupContentVerifier().textContent.includes("security exception"), + "Text shows overridden certificate warning." + ); +} + +add_task(async function () { + await BrowserTestUtils.openNewForegroundTab(gBrowser); + + // check that a warning is shown when loading a page with mixed content and an overridden certificate + await loadBadCertPage(MIXED_CONTENT_URL); + checkIdentityPopup("security-warning.svg"); + + // check that the crossed out icon is shown when disabling mixed content protection + gIdentityHandler.disableMixedContentProtection(); + await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + + checkIdentityPopup("security-broken.svg"); + + // check that a warning is shown even without mixed content + BrowserTestUtils.loadURIString( + gBrowser.selectedBrowser, + "https://self-signed.example.com" + ); + await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + checkIdentityPopup("security-warning.svg"); + + // remove cert exception + let certOverrideService = Cc[ + "@mozilla.org/security/certoverride;1" + ].getService(Ci.nsICertOverrideService); + certOverrideService.clearValidityOverride("self-signed.example.com", -1, {}); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/base/content/test/siteIdentity/browser_mixed_content_with_navigation.js b/browser/base/content/test/siteIdentity/browser_mixed_content_with_navigation.js new file mode 100644 index 0000000000..48171ee876 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_mixed_content_with_navigation.js @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the site identity indicator is properly updated when loading from +// the BF cache. This is achieved by loading a page, navigating to another page, +// and then going "back" to the first page, as well as the reverse (loading to +// the other page, navigating to the page we're interested in, going back, and +// then going forward again). + +const kBaseURI = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); +const kSecureURI = kBaseURI + "dummy_page.html"; + +const kTestcases = [ + { + uri: kBaseURI + "file_mixedPassiveContent.html", + expectErrorPage: false, + expectedIdentityMode: "mixedDisplayContent", + }, + { + uri: kBaseURI + "file_bug1045809_1.html", + expectErrorPage: false, + expectedIdentityMode: "mixedActiveBlocked", + }, + { + uri: "https://expired.example.com", + expectErrorPage: true, + expectedIdentityMode: "certErrorPage", + }, +]; + +add_task(async function () { + for (let testcase of kTestcases) { + await run_testcase(testcase); + } +}); + +async function run_testcase(testcase) { + await SpecialPowers.pushPrefEnv({ + set: [["security.mixed_content.upgrade_display_content", false]], + }); + // Test the forward and back case. + // Start by loading an unrelated URI so that this generalizes well when the + // testcase would otherwise first navigate to an error page, which doesn't + // seem to work with withNewTab. + await BrowserTestUtils.withNewTab("about:blank", async browser => { + // Navigate to the test URI. + BrowserTestUtils.loadURIString(browser, testcase.uri); + if (!testcase.expectErrorPage) { + await BrowserTestUtils.browserLoaded(browser, false, testcase.uri); + } else { + await BrowserTestUtils.waitForErrorPage(browser); + } + let identityMode = window.document.getElementById("identity-box").classList; + ok( + identityMode.contains(testcase.expectedIdentityMode), + `identity should be ${testcase.expectedIdentityMode}` + ); + + // Navigate to a URI that should be secure. + BrowserTestUtils.loadURIString(browser, kSecureURI); + await BrowserTestUtils.browserLoaded(browser, false, kSecureURI); + let secureIdentityMode = + window.document.getElementById("identity-box").className; + is(secureIdentityMode, "verifiedDomain", "identity should be secure now"); + + // Go back to the test page. + browser.webNavigation.goBack(); + if (!testcase.expectErrorPage) { + await BrowserTestUtils.browserStopped(browser, testcase.uri); + } else { + await BrowserTestUtils.waitForErrorPage(browser); + } + let identityModeAgain = + window.document.getElementById("identity-box").classList; + ok( + identityModeAgain.contains(testcase.expectedIdentityMode), + `identity should again be ${testcase.expectedIdentityMode}` + ); + }); + + // Test the back and forward case. + // Start on a secure page. + await BrowserTestUtils.withNewTab(kSecureURI, async browser => { + let secureIdentityMode = + window.document.getElementById("identity-box").className; + is(secureIdentityMode, "verifiedDomain", "identity should start as secure"); + + // Navigate to the test URI. + BrowserTestUtils.loadURIString(browser, testcase.uri); + if (!testcase.expectErrorPage) { + await BrowserTestUtils.browserLoaded(browser, false, testcase.uri); + } else { + await BrowserTestUtils.waitForErrorPage(browser); + } + let identityMode = window.document.getElementById("identity-box").classList; + ok( + identityMode.contains(testcase.expectedIdentityMode), + `identity should be ${testcase.expectedIdentityMode}` + ); + + // Go back to the secure page. + browser.webNavigation.goBack(); + await BrowserTestUtils.browserStopped(browser, kSecureURI); + let secureIdentityModeAgain = + window.document.getElementById("identity-box").className; + is( + secureIdentityModeAgain, + "verifiedDomain", + "identity should be secure again" + ); + + // Go forward again to the test URI. + browser.webNavigation.goForward(); + if (!testcase.expectErrorPage) { + await BrowserTestUtils.browserStopped(browser, testcase.uri); + } else { + await BrowserTestUtils.waitForErrorPage(browser); + } + let identityModeAgain = + window.document.getElementById("identity-box").classList; + ok( + identityModeAgain.contains(testcase.expectedIdentityMode), + `identity should again be ${testcase.expectedIdentityMode}` + ); + }); +} diff --git a/browser/base/content/test/siteIdentity/browser_mixed_passive_content_indicator.js b/browser/base/content/test/siteIdentity/browser_mixed_passive_content_indicator.js new file mode 100644 index 0000000000..909764c597 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_mixed_passive_content_indicator.js @@ -0,0 +1,18 @@ +const TEST_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "simple_mixed_passive.html"; + +add_task(async function test_mixed_passive_content_indicator() { + await SpecialPowers.pushPrefEnv({ + set: [["security.mixed_content.upgrade_display_content", false]], + }); + await BrowserTestUtils.withNewTab(TEST_URL, function () { + is( + document.getElementById("identity-box").className, + "unknownIdentity mixedDisplayContent", + "identity box has class name for mixed content" + ); + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_mixedcontent_securityflags.js b/browser/base/content/test/siteIdentity/browser_mixedcontent_securityflags.js new file mode 100644 index 0000000000..3e39426c51 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_mixedcontent_securityflags.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// The test loads a web page with mixed active and mixed display content and +// makes sure that the mixed content flags on the docshell are set correctly. +// * Using default about:config prefs (mixed active blocked, mixed display +// loaded) we load the page and check the flags. +// * We change the about:config prefs (mixed active blocked, mixed display +// blocked), reload the page, and check the flags again. +// * We override protection so all mixed content can load and check the +// flags again. + +const TEST_URI = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "test-mixedcontent-securityerrors.html"; +const PREF_DISPLAY = "security.mixed_content.block_display_content"; +const PREF_DISPLAY_UPGRADE = "security.mixed_content.upgrade_display_content"; +const PREF_ACTIVE = "security.mixed_content.block_active_content"; +var gTestBrowser = null; + +registerCleanupFunction(function () { + // Set preferences back to their original values + Services.prefs.clearUserPref(PREF_DISPLAY); + Services.prefs.clearUserPref(PREF_DISPLAY_UPGRADE); + Services.prefs.clearUserPref(PREF_ACTIVE); + gBrowser.removeCurrentTab(); +}); + +add_task(async function blockMixedActiveContentTest() { + // Turn on mixed active blocking and mixed display loading and load the page. + Services.prefs.setBoolPref(PREF_DISPLAY, false); + Services.prefs.setBoolPref(PREF_DISPLAY_UPGRADE, false); + Services.prefs.setBoolPref(PREF_ACTIVE, true); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI); + gTestBrowser = gBrowser.getBrowserForTab(tab); + + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: true, + }); + + // Turn on mixed active and mixed display blocking and reload the page. + Services.prefs.setBoolPref(PREF_DISPLAY, true); + Services.prefs.setBoolPref(PREF_ACTIVE, true); + + gBrowser.reload(); + await BrowserTestUtils.browserLoaded(gTestBrowser); + + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: false, + activeBlocked: true, + passiveLoaded: false, + }); +}); + +add_task(async function overrideMCB() { + // Disable mixed content blocking (reloads page) and retest + let { gIdentityHandler } = gTestBrowser.ownerGlobal; + gIdentityHandler.disableMixedContentProtection(); + await BrowserTestUtils.browserLoaded(gTestBrowser); + + await assertMixedContentBlockingState(gTestBrowser, { + activeLoaded: true, + activeBlocked: false, + passiveLoaded: true, + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_navigation_failures.js b/browser/base/content/test/siteIdentity/browser_navigation_failures.js new file mode 100644 index 0000000000..ddb0d93fab --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_navigation_failures.js @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the site identity indicator is properly updated for navigations +// that fail for various reasons. In particular, we currently test TLS handshake +// failures, about: pages that don't actually exist, and situations where the +// TLS handshake completes but the server then closes the connection. +// See bug 1492424, bug 1493427, and bug 1391207. + +const kSecureURI = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "dummy_page.html"; +add_task(async function () { + await BrowserTestUtils.withNewTab(kSecureURI, async browser => { + let identityMode = window.document.getElementById("identity-box").className; + is(identityMode, "verifiedDomain", "identity should be secure before"); + + const TLS_HANDSHAKE_FAILURE_URI = "https://ssl3.example.com/"; + // Try to connect to a server where the TLS handshake will fail. + BrowserTestUtils.loadURIString(browser, TLS_HANDSHAKE_FAILURE_URI); + await BrowserTestUtils.browserLoaded( + browser, + false, + TLS_HANDSHAKE_FAILURE_URI, + true + ); + + let newIdentityMode = + window.document.getElementById("identity-box").className; + is( + newIdentityMode, + "certErrorPage notSecureText", + "identity should be unknown (not secure) after" + ); + }); +}); + +add_task(async function () { + await BrowserTestUtils.withNewTab(kSecureURI, async browser => { + let identityMode = window.document.getElementById("identity-box").className; + is(identityMode, "verifiedDomain", "identity should be secure before"); + + const BAD_ABOUT_PAGE_URI = "about:somethingthatdoesnotexist"; + // Try to load an about: page that doesn't exist + BrowserTestUtils.loadURIString(browser, BAD_ABOUT_PAGE_URI); + await BrowserTestUtils.browserLoaded( + browser, + false, + BAD_ABOUT_PAGE_URI, + true + ); + + let newIdentityMode = + window.document.getElementById("identity-box").className; + is( + newIdentityMode, + "unknownIdentity", + "identity should be unknown (not secure) after" + ); + }); +}); + +// Helper function to start a TLS server that will accept a connection, complete +// the TLS handshake, but then close the connection. +function startServer(cert) { + let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance( + Ci.nsITLSServerSocket + ); + tlsServer.init(-1, true, -1); + tlsServer.serverCert = cert; + + let input, output; + + let listener = { + onSocketAccepted(socket, transport) { + let connectionInfo = transport.securityCallbacks.getInterface( + Ci.nsITLSServerConnectionInfo + ); + connectionInfo.setSecurityObserver(listener); + input = transport.openInputStream(0, 0, 0); + output = transport.openOutputStream(0, 0, 0); + }, + + onHandshakeDone(socket, status) { + input.asyncWait( + { + onInputStreamReady(readyInput) { + try { + input.close(); + output.close(); + } catch (e) { + info(e); + } + }, + }, + 0, + 0, + Services.tm.currentThread + ); + }, + + onStopListening() {}, + }; + + tlsServer.setSessionTickets(false); + tlsServer.asyncListen(listener); + + return tlsServer; +} + +// Test that if we complete a TLS handshake but the server closes the connection +// just after doing so (resulting in a "connection reset" error page), the site +// identity information gets updated appropriately (it should indicate "not +// secure"). +add_task(async function () { + await SpecialPowers.pushPrefEnv({ + // This test fails on some platforms if we leave IPv6 enabled. + set: [["network.dns.disableIPv6", true]], + }); + + let certOverrideService = Cc[ + "@mozilla.org/security/certoverride;1" + ].getService(Ci.nsICertOverrideService); + + let cert = getTestServerCertificate(); + // Start a server and trust its certificate. + let server = startServer(cert); + certOverrideService.rememberValidityOverride( + "localhost", + server.port, + {}, + cert, + true + ); + + // Un-do configuration changes we've made when the test is done. + registerCleanupFunction(() => { + certOverrideService.clearValidityOverride("localhost", server.port, {}); + server.close(); + }); + + // Open up a new tab... + await BrowserTestUtils.withNewTab("about:blank", async browser => { + const TLS_HANDSHAKE_FAILURE_URI = `https://localhost:${server.port}/`; + // Try to connect to a server where the TLS handshake will succeed, but then + // the server closes the connection right after. + BrowserTestUtils.loadURIString(browser, TLS_HANDSHAKE_FAILURE_URI); + await BrowserTestUtils.browserLoaded( + browser, + false, + TLS_HANDSHAKE_FAILURE_URI, + true + ); + + let identityMode = window.document.getElementById("identity-box").className; + is( + identityMode, + "certErrorPage notSecureText", + "identity should be 'unknown'" + ); + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_no_mcb_for_loopback.js b/browser/base/content/test/siteIdentity/browser_no_mcb_for_loopback.js new file mode 100644 index 0000000000..1c854e2849 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_no_mcb_for_loopback.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// The test loads a HTTPS web page with active content from HTTP loopback URLs +// and makes sure that the mixed content flags on the docshell are not set. +// +// Note that the URLs referenced within the test page intentionally use the +// unassigned port 8 because we don't want to actually load anything, we just +// want to check that the URLs are not blocked. + +// The following rejections should not be left uncaught. This test has been +// whitelisted until the issue is fixed. +if (!gMultiProcessBrowser) { + const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" + ); + PromiseTestUtils.expectUncaughtRejection(/NetworkError/); + PromiseTestUtils.expectUncaughtRejection(/NetworkError/); +} + +const TEST_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "test_no_mcb_for_loopback.html"; + +const LOOPBACK_PNG_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://127.0.0.1:8888" + ) + "moz.png"; + +const PREF_BLOCK_DISPLAY = "security.mixed_content.block_display_content"; +const PREF_UPGRADE_DISPLAY = "security.mixed_content.upgrade_display_content"; +const PREF_BLOCK_ACTIVE = "security.mixed_content.block_active_content"; + +function clearAllImageCaches() { + let tools = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools); + let imageCache = tools.getImgCacheForDocument(window.document); + imageCache.clearCache(true); // true=chrome + imageCache.clearCache(false); // false=content +} + +registerCleanupFunction(function () { + clearAllImageCaches(); + Services.prefs.clearUserPref(PREF_BLOCK_DISPLAY); + Services.prefs.clearUserPref(PREF_UPGRADE_DISPLAY); + Services.prefs.clearUserPref(PREF_BLOCK_ACTIVE); + gBrowser.removeCurrentTab(); +}); + +add_task(async function allowLoopbackMixedContent() { + Services.prefs.setBoolPref(PREF_BLOCK_DISPLAY, true); + Services.prefs.setBoolPref(PREF_UPGRADE_DISPLAY, false); + Services.prefs.setBoolPref(PREF_BLOCK_ACTIVE, true); + + const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + const browser = gBrowser.getBrowserForTab(tab); + + // Check that loopback content served from the cache is not blocked. + await SpecialPowers.spawn( + browser, + [LOOPBACK_PNG_URL], + async function (loopbackPNGUrl) { + const doc = content.document; + const img = doc.createElement("img"); + const promiseImgLoaded = ContentTaskUtils.waitForEvent( + img, + "load", + false + ); + img.src = loopbackPNGUrl; + Assert.ok(!img.complete, "loopback image not yet loaded"); + doc.body.appendChild(img); + await promiseImgLoaded; + + const cachedImg = doc.createElement("img"); + cachedImg.src = img.src; + Assert.ok(cachedImg.complete, "loopback image loaded from cache"); + } + ); + + await assertMixedContentBlockingState(browser, { + activeBlocked: false, + activeLoaded: false, + passiveLoaded: false, + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_no_mcb_for_onions.js b/browser/base/content/test/siteIdentity/browser_no_mcb_for_onions.js new file mode 100644 index 0000000000..4a5c65b125 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_no_mcb_for_onions.js @@ -0,0 +1,41 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// The test loads a HTTPS web page with active content from HTTP .onion URLs +// and makes sure that the mixed content flags on the docshell are not set. +// +// Note that the URLs referenced within the test page intentionally use the +// unassigned port 8 because we don't want to actually load anything, we just +// want to check that the URLs are not blocked. + +const TEST_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "test_no_mcb_for_onions.html"; + +const PREF_BLOCK_DISPLAY = "security.mixed_content.block_display_content"; +const PREF_BLOCK_ACTIVE = "security.mixed_content.block_active_content"; +const PREF_ONION_ALLOWLIST = "dom.securecontext.allowlist_onions"; + +add_task(async function allowOnionMixedContent() { + registerCleanupFunction(function () { + gBrowser.removeCurrentTab(); + }); + + await SpecialPowers.pushPrefEnv({ set: [[PREF_BLOCK_DISPLAY, true]] }); + await SpecialPowers.pushPrefEnv({ set: [[PREF_BLOCK_ACTIVE, true]] }); + await SpecialPowers.pushPrefEnv({ set: [[PREF_ONION_ALLOWLIST, true]] }); + + const tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_URL + ).catch(console.error); + const browser = gBrowser.getBrowserForTab(tab); + + await assertMixedContentBlockingState(browser, { + activeBlocked: false, + activeLoaded: false, + passiveLoaded: false, + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_no_mcb_on_http_site.js b/browser/base/content/test/siteIdentity/browser_no_mcb_on_http_site.js new file mode 100644 index 0000000000..30caae4ea5 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_no_mcb_on_http_site.js @@ -0,0 +1,133 @@ +/* + * Description of the Tests for + * - Bug 909920 - Mixed content warning should not show on a HTTP site + * + * Description of the tests: + * Test 1: + * 1) Load an http page + * 2) The page includes a css file using https + * 3) The css file loads an |IMAGE| << over http + * + * Test 2: + * 1) Load an http page + * 2) The page includes a css file using https + * 3) The css file loads a |FONT| over http + * + * Test 3: + * 1) Load an http page + * 2) The page includes a css file using https + * 3) The css file imports (@import) another css file using http + * 3) The imported css file loads a |FONT| over http + * + * Since the top-domain is >> NOT << served using https, the MCB + * should >> NOT << trigger a warning. + */ + +const PREF_ACTIVE = "security.mixed_content.block_active_content"; +const PREF_DISPLAY = "security.mixed_content.block_display_content"; + +const HTTP_TEST_ROOT = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com" +); + +var gTestBrowser = null; + +function cleanUpAfterTests() { + gBrowser.removeCurrentTab(); + window.focus(); +} + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + [PREF_ACTIVE, true], + [PREF_DISPLAY, true], + ], + }); + let url = HTTP_TEST_ROOT + "test_no_mcb_on_http_site_img.html"; + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + gTestBrowser = tab.linkedBrowser; +}); + +// ------------- TEST 1 ----------------------------------------- + +add_task(async function test1() { + let expected = + "Verifying MCB does not trigger warning/error for an http page "; + expected += "with https css that includes http image"; + + await SpecialPowers.spawn( + gTestBrowser, + [expected], + async function (condition) { + await ContentTaskUtils.waitForCondition( + () => content.document.getElementById("testDiv").innerHTML == condition, + "Waited too long for status in Test 1!" + ); + } + ); + + // Explicit OKs needed because the harness requires at least one call to ok. + ok(true, "test 1 passed"); + + // set up test 2 + let url = HTTP_TEST_ROOT + "test_no_mcb_on_http_site_font.html"; + BrowserTestUtils.loadURIString(gTestBrowser, url); + await BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +// ------------- TEST 2 ----------------------------------------- + +add_task(async function test2() { + let expected = + "Verifying MCB does not trigger warning/error for an http page "; + expected += "with https css that includes http font"; + + await SpecialPowers.spawn( + gTestBrowser, + [expected], + async function (condition) { + await ContentTaskUtils.waitForCondition( + () => content.document.getElementById("testDiv").innerHTML == condition, + "Waited too long for status in Test 2!" + ); + } + ); + + ok(true, "test 2 passed"); + + // set up test 3 + let url = HTTP_TEST_ROOT + "test_no_mcb_on_http_site_font2.html"; + BrowserTestUtils.loadURIString(gTestBrowser, url); + await BrowserTestUtils.browserLoaded(gTestBrowser); +}); + +// ------------- TEST 3 ----------------------------------------- + +add_task(async function test3() { + let expected = + "Verifying MCB does not trigger warning/error for an http page "; + expected += + "with https css that imports another http css which includes http font"; + + await SpecialPowers.spawn( + gTestBrowser, + [expected], + async function (condition) { + await ContentTaskUtils.waitForCondition( + () => content.document.getElementById("testDiv").innerHTML == condition, + "Waited too long for status in Test 3!" + ); + } + ); + + ok(true, "test3 passed"); +}); + +// ------------------------------------------------------ + +add_task(async function cleanup() { + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/base/content/test/siteIdentity/browser_secure_transport_insecure_scheme.js b/browser/base/content/test/siteIdentity/browser_secure_transport_insecure_scheme.js new file mode 100644 index 0000000000..1d282ef6de --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_secure_transport_insecure_scheme.js @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that an insecure resource routed over a secure transport is considered +// insecure in terms of the site identity panel. We achieve this by running an +// HTTP-over-TLS "proxy" and having Firefox request an http:// URI over it. + +/** + * Tests that the page info dialog "security" section labels a + * connection as unencrypted and does not show certificate. + * @param {string} uri - URI of the page to test with. + */ +async function testPageInfoNotEncrypted(uri) { + let pageInfo = BrowserPageInfo(uri, "securityTab"); + await BrowserTestUtils.waitForEvent(pageInfo, "load"); + let pageInfoDoc = pageInfo.document; + let securityTab = pageInfoDoc.getElementById("securityTab"); + await TestUtils.waitForCondition( + () => BrowserTestUtils.is_visible(securityTab), + "Security tab should be visible." + ); + + let secLabel = pageInfoDoc.getElementById("security-technical-shortform"); + await TestUtils.waitForCondition( + () => secLabel.value == "Connection Not Encrypted", + "pageInfo 'Security Details' should show not encrypted" + ); + + let viewCertBtn = pageInfoDoc.getElementById("security-view-cert"); + ok( + viewCertBtn.collapsed, + "pageInfo 'View Cert' button should not be visible" + ); + pageInfo.close(); +} + +// But first, a quick test that we don't incorrectly treat a +// blob:https://example.com URI as secure. +add_task(async function () { + let uri = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "dummy_page.html"; + await BrowserTestUtils.withNewTab(uri, async browser => { + await SpecialPowers.spawn(browser, [], async () => { + let debug = { hello: "world" }; + let blob = new Blob([JSON.stringify(debug, null, 2)], { + type: "application/json", + }); + let blobUri = URL.createObjectURL(blob); + content.document.location = blobUri; + }); + await BrowserTestUtils.browserLoaded(browser); + let identityMode = window.document.getElementById("identity-box").className; + is(identityMode, "localResource", "identity should be 'localResource'"); + await testPageInfoNotEncrypted(uri); + }); +}); + +// This server pretends to be a HTTP over TLS proxy. It isn't really, but this +// is sufficient for the purposes of this test. +function startServer(cert) { + let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance( + Ci.nsITLSServerSocket + ); + tlsServer.init(-1, true, -1); + tlsServer.serverCert = cert; + + let input, output; + + let listener = { + onSocketAccepted(socket, transport) { + let connectionInfo = transport.securityCallbacks.getInterface( + Ci.nsITLSServerConnectionInfo + ); + connectionInfo.setSecurityObserver(listener); + input = transport.openInputStream(0, 0, 0); + output = transport.openOutputStream(0, 0, 0); + }, + + onHandshakeDone(socket, status) { + input.asyncWait( + { + onInputStreamReady(readyInput) { + try { + let request = NetUtil.readInputStreamToString( + readyInput, + readyInput.available() + ); + ok( + request.startsWith("GET ") && request.includes("HTTP/1.1"), + "expecting an HTTP/1.1 GET request" + ); + let response = + "HTTP/1.1 200 OK\r\nContent-Type:text/plain\r\n" + + "Connection:Close\r\nContent-Length:2\r\n\r\nOK"; + output.write(response, response.length); + } catch (e) { + info(e); + } + }, + }, + 0, + 0, + Services.tm.currentThread + ); + }, + + onStopListening() { + input.close(); + output.close(); + }, + }; + + tlsServer.setSessionTickets(false); + tlsServer.asyncListen(listener); + + return tlsServer; +} + +add_task(async function () { + await SpecialPowers.pushPrefEnv({ + // This test fails on some platforms if we leave IPv6 enabled. + set: [["network.dns.disableIPv6", true]], + }); + + let certOverrideService = Cc[ + "@mozilla.org/security/certoverride;1" + ].getService(Ci.nsICertOverrideService); + + let cert = getTestServerCertificate(); + // Start the proxy and configure Firefox to trust its certificate. + let server = startServer(cert); + certOverrideService.rememberValidityOverride( + "localhost", + server.port, + {}, + cert, + true + ); + // Configure Firefox to use the proxy. + let systemProxySettings = { + QueryInterface: ChromeUtils.generateQI(["nsISystemProxySettings"]), + mainThreadOnly: true, + PACURI: null, + getProxyForURI: (aSpec, aScheme, aHost, aPort) => { + return `HTTPS localhost:${server.port}`; + }, + }; + let oldProxyType = Services.prefs.getIntPref("network.proxy.type"); + Services.prefs.setIntPref( + "network.proxy.type", + Ci.nsIProtocolProxyService.PROXYCONFIG_SYSTEM + ); + let { MockRegistrar } = ChromeUtils.importESModule( + "resource://testing-common/MockRegistrar.sys.mjs" + ); + let mockProxy = MockRegistrar.register( + "@mozilla.org/system-proxy-settings;1", + systemProxySettings + ); + // Register cleanup to undo the configuration changes we've made. + registerCleanupFunction(() => { + certOverrideService.clearValidityOverride("localhost", server.port, {}); + Services.prefs.setIntPref("network.proxy.type", oldProxyType); + MockRegistrar.unregister(mockProxy); + server.close(); + }); + + // Navigate to 'http://example.com'. Our proxy settings will route this via + // the "proxy" we just started. Even though our connection to the proxy is + // secure, in a real situation the connection from the proxy to + // http://example.com won't be secure, so we treat it as not secure. + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + await BrowserTestUtils.withNewTab("http://example.com/", async browser => { + let identityMode = window.document.getElementById("identity-box").className; + is(identityMode, "notSecure", "identity should be 'not secure'"); + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + await testPageInfoNotEncrypted("http://example.com"); + }); +}); diff --git a/browser/base/content/test/siteIdentity/browser_session_store_pageproxystate.js b/browser/base/content/test/siteIdentity/browser_session_store_pageproxystate.js new file mode 100644 index 0000000000..5d8c011727 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_session_store_pageproxystate.js @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const triggeringPrincipal_base64 = E10SUtils.SERIALIZED_SYSTEMPRINCIPAL; + +let origBrowserState = SessionStore.getBrowserState(); + +add_setup(async function () { + registerCleanupFunction(() => { + SessionStore.setBrowserState(origBrowserState); + }); +}); + +// Test that when restoring tabs via SessionStore, we directly show the correct +// security state. +add_task(async function test_session_store_security_state() { + const state = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "https://example.net", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "https://example.org", triggeringPrincipal_base64 }, + ], + }, + ], + selected: 1, + }, + ], + }; + + // Create a promise that resolves when the tabs have finished restoring. + let promiseTabsRestored = Promise.all([ + TestUtils.topicObserved("sessionstore-browser-state-restored"), + BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "SSTabRestored"), + ]); + + SessionStore.setBrowserState(JSON.stringify(state)); + + await promiseTabsRestored; + + is(gBrowser.selectedTab, gBrowser.tabs[0], "First tab is selected initially"); + + info("Switch to second tab which has not been loaded yet."); + BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[1]); + is( + gURLBar.textbox.getAttribute("pageproxystate"), + "invalid", + "Page proxy state is invalid after tab switch" + ); + + // Wait for valid pageproxystate. As soon as we have a valid pageproxystate, + // showing the identity box, it should indicate a secure connection. + await BrowserTestUtils.waitForMutationCondition( + gURLBar.textbox, + { + attributeFilter: ["pageproxystate"], + }, + () => gURLBar.textbox.getAttribute("pageproxystate") == "valid" + ); + + // Wait for a tick for security state to apply. + await new Promise(resolve => setTimeout(resolve, 0)); + + is( + gBrowser.currentURI.spec, + "https://example.org/", + "Should have loaded example.org" + ); + is( + gIdentityHandler._identityBox.getAttribute("pageproxystate"), + "valid", + "identityBox pageproxystate is valid" + ); + + ok( + gIdentityHandler._isSecureConnection, + "gIdentityHandler._isSecureConnection is true" + ); + is( + gIdentityHandler._identityBox.className, + "verifiedDomain", + "identityBox class signals secure connection." + ); +}); diff --git a/browser/base/content/test/siteIdentity/browser_tab_sharing_state.js b/browser/base/content/test/siteIdentity/browser_tab_sharing_state.js new file mode 100644 index 0000000000..6c6ba57c55 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_tab_sharing_state.js @@ -0,0 +1,96 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests gBrowser#updateBrowserSharing + */ +add_task(async function testBrowserSharingStateSetter() { + const WEBRTC_TEST_STATE = { + camera: 0, + microphone: 1, + paused: false, + sharing: "microphone", + showMicrophoneIndicator: true, + showScreenSharingIndicator: "", + windowId: 0, + }; + + const WEBRTC_TEST_STATE2 = { + camera: 1, + microphone: 1, + paused: false, + sharing: "camera", + showCameraIndicator: true, + showMicrophoneIndicator: true, + showScreenSharingIndicator: "", + windowId: 1, + }; + + await BrowserTestUtils.withNewTab("https://example.com", async browser => { + let tab = gBrowser.selectedTab; + is(tab._sharingState, undefined, "No sharing state initially."); + ok(!tab.hasAttribute("sharing"), "No tab sharing attribute initially."); + + // Set an active sharing state for webrtc + gBrowser.updateBrowserSharing(browser, { webRTC: WEBRTC_TEST_STATE }); + Assert.deepEqual( + tab._sharingState, + { webRTC: WEBRTC_TEST_STATE }, + "Should have correct webRTC sharing state." + ); + is( + tab.getAttribute("sharing"), + WEBRTC_TEST_STATE.sharing, + "Tab sharing attribute reflects webRTC sharing state." + ); + + // Set sharing state for geolocation + gBrowser.updateBrowserSharing(browser, { geo: true }); + Assert.deepEqual( + tab._sharingState, + { + webRTC: WEBRTC_TEST_STATE, + geo: true, + }, + "Should have sharing state for both webRTC and geolocation." + ); + is( + tab.getAttribute("sharing"), + WEBRTC_TEST_STATE.sharing, + "Geolocation sharing doesn't update the tab sharing attribute." + ); + + // Update webRTC sharing state + gBrowser.updateBrowserSharing(browser, { webRTC: WEBRTC_TEST_STATE2 }); + Assert.deepEqual( + tab._sharingState, + { geo: true, webRTC: WEBRTC_TEST_STATE2 }, + "Should have updated webRTC sharing state while maintaining geolocation state." + ); + is( + tab.getAttribute("sharing"), + WEBRTC_TEST_STATE2.sharing, + "Tab sharing attribute reflects webRTC sharing state." + ); + + // Clear webRTC sharing state + gBrowser.updateBrowserSharing(browser, { webRTC: null }); + Assert.deepEqual( + tab._sharingState, + { geo: true, webRTC: null }, + "Should only have sharing state for geolocation." + ); + ok( + !tab.hasAttribute("sharing"), + "Ending webRTC sharing should remove tab sharing attribute." + ); + + // Clear geolocation sharing state + gBrowser.updateBrowserSharing(browser, { geo: null }); + Assert.deepEqual(tab._sharingState, { geo: null, webRTC: null }); + ok( + !tab.hasAttribute("sharing"), + "Tab sharing attribute should not be set." + ); + }); +}); diff --git a/browser/base/content/test/siteIdentity/dummy_iframe_page.html b/browser/base/content/test/siteIdentity/dummy_iframe_page.html new file mode 100644 index 0000000000..ea80367aa5 --- /dev/null +++ b/browser/base/content/test/siteIdentity/dummy_iframe_page.html @@ -0,0 +1,10 @@ +<html> +<head> +<title>Dummy iframe test page</title> +<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta> +</head> +<body> + <iframe src="https://example.org"></iframe> +<p>Dummy test page</p> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/dummy_page.html b/browser/base/content/test/siteIdentity/dummy_page.html new file mode 100644 index 0000000000..a7747a0bca --- /dev/null +++ b/browser/base/content/test/siteIdentity/dummy_page.html @@ -0,0 +1,10 @@ +<html> +<head> +<title>Dummy test page</title> +<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta> +</head> +<body> + <a href="https://nocert.example.com" id="no-cert">No Cert page</a> +<p>Dummy test page</p> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug1045809_1.html b/browser/base/content/test/siteIdentity/file_bug1045809_1.html new file mode 100644 index 0000000000..c4f281d670 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug1045809_1.html @@ -0,0 +1,7 @@ +<html> + <head> + </head> + <body> + <iframe src="http://test1.example.com/browser/browser/base/content/test/siteIdentity/file_bug1045809_2.html"></iframe> + </body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug1045809_2.html b/browser/base/content/test/siteIdentity/file_bug1045809_2.html new file mode 100644 index 0000000000..67a297dbc5 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug1045809_2.html @@ -0,0 +1,7 @@ +<html> + <head> + </head> + <body> + <div id="mixedContentContainer">Mixed Content is here</div> + </body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug822367_1.html b/browser/base/content/test/siteIdentity/file_bug822367_1.html new file mode 100644 index 0000000000..a6e3fafc23 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug822367_1.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 1 for Mixed Content Blocker User Override - Mixed Script +https://bugzilla.mozilla.org/show_bug.cgi?id=822367 +--> +<head> + <meta charset="utf-8"> + <title>Test 1 for Bug 822367</title> +</head> +<body> + <div id="testContent"> + <p id="p1"></p> + </div> + <script src="http://example.com/browser/browser/base/content/test/siteIdentity/file_bug822367_1.js"> + </script> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug822367_1.js b/browser/base/content/test/siteIdentity/file_bug822367_1.js new file mode 100644 index 0000000000..e4b5fb86c6 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug822367_1.js @@ -0,0 +1 @@ +document.getElementById("p1").innerHTML = "hello"; diff --git a/browser/base/content/test/siteIdentity/file_bug822367_2.html b/browser/base/content/test/siteIdentity/file_bug822367_2.html new file mode 100644 index 0000000000..fe56ee2130 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug822367_2.html @@ -0,0 +1,16 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 2 for Mixed Content Blocker User Override - Mixed Display +https://bugzilla.mozilla.org/show_bug.cgi?id=822367 +--> +<head> + <meta charset="utf-8"> + <title>Test 2 for Bug 822367 - Mixed Display</title> +</head> +<body> + <div id="testContent"> + <img src="http://example.com/tests/image/test/mochitest/blue.png"> + </div> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug822367_3.html b/browser/base/content/test/siteIdentity/file_bug822367_3.html new file mode 100644 index 0000000000..0cf5db7b20 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug822367_3.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 3 for Mixed Content Blocker User Override - Mixed Script and Display +https://bugzilla.mozilla.org/show_bug.cgi?id=822367 +--> +<head> + <meta charset="utf-8"> + <title>Test 3 for Bug 822367</title> + <script> + function foo() { + var x = document.createElement("p"); + x.setAttribute("id", "p2"); + x.innerHTML = "bye"; + document.getElementById("testContent").appendChild(x); + } + </script> +</head> +<body> + <div id="testContent"> + <p id="p1"></p> + <img src="http://example.com/tests/image/test/mochitest/blue.png" onload="foo()"> + </div> + <script src="http://example.com/browser/browser/base/content/test/siteIdentity/file_bug822367_1.js"> + </script> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug822367_4.html b/browser/base/content/test/siteIdentity/file_bug822367_4.html new file mode 100644 index 0000000000..8e5aeb67f2 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug822367_4.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 4 for Mixed Content Blocker User Override - Mixed Script and Display +https://bugzilla.mozilla.org/show_bug.cgi?id=822367 +--> +<head> + <meta charset="utf-8"> + <title>Test 4 for Bug 822367</title> +</head> +<body> + <div id="testContent"> + <p id="p1"></p> + </div> + <script src="http://example.com/browser/browser/base/content/test/siteIdentity/file_bug822367_4.js"> + </script> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug822367_4.js b/browser/base/content/test/siteIdentity/file_bug822367_4.js new file mode 100644 index 0000000000..8bdc791180 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug822367_4.js @@ -0,0 +1,2 @@ +document.location = + "https://example.com/browser/browser/base/content/test/siteIdentity/file_bug822367_4B.html"; diff --git a/browser/base/content/test/siteIdentity/file_bug822367_4B.html b/browser/base/content/test/siteIdentity/file_bug822367_4B.html new file mode 100644 index 0000000000..9af942525f --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug822367_4B.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 4B for Mixed Content Blocker User Override - Location Changed +https://bugzilla.mozilla.org/show_bug.cgi?id=822367 +--> +<head> + <meta charset="utf-8"> + <title>Test 4B Location Change for Bug 822367</title> +</head> +<body> + <div id="testContent"> + <p id="p1"></p> + </div> + <script src="http://example.com/browser/browser/base/content/test/siteIdentity/file_bug822367_1.js"> + </script> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug822367_5.html b/browser/base/content/test/siteIdentity/file_bug822367_5.html new file mode 100644 index 0000000000..6341539e83 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug822367_5.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 5 for Mixed Content Blocker User Override - Mixed Script in document.open() +https://bugzilla.mozilla.org/show_bug.cgi?id=822367 +--> +<head> + <meta charset="utf-8"> + <title>Test 5 for Bug 822367</title> + <script> + function createDoc() { + var doc = document.open("text/html", "replace"); + doc.write('<!DOCTYPE html><html><body><p id="p1">This is some content</p><script src="http://example.com/browser/browser/base/content/test/siteIdentity/file_bug822367_1.js">\<\/script\>\<\/body>\<\/html>'); + doc.close(); + } + </script> +</head> +<body> + <div id="testContent"> + <img src="https://example.com/tests/image/test/mochitest/blue.png" onload="createDoc()"> + </div> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug822367_6.html b/browser/base/content/test/siteIdentity/file_bug822367_6.html new file mode 100644 index 0000000000..2c071a785d --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug822367_6.html @@ -0,0 +1,16 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 6 for Mixed Content Blocker User Override - Mixed Script in document.open() within an iframe +https://bugzilla.mozilla.org/show_bug.cgi?id=822367 +--> +<head> + <meta charset="utf-8"> + <title>Test 6 for Bug 822367</title> +</head> +<body> + <div id="testContent"> + <iframe name="f1" id="f1" src="https://example.com/browser/browser/base/content/test/siteIdentity/file_bug822367_5.html"></iframe> + </div> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug902156.js b/browser/base/content/test/siteIdentity/file_bug902156.js new file mode 100644 index 0000000000..01ef4073fb --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug902156.js @@ -0,0 +1,6 @@ +/* + * Once the mixed content blocker is disabled for the page, this scripts loads + * and updates the text inside the div container. + */ +document.getElementById("mctestdiv").innerHTML = + "Mixed Content Blocker disabled"; diff --git a/browser/base/content/test/siteIdentity/file_bug902156_1.html b/browser/base/content/test/siteIdentity/file_bug902156_1.html new file mode 100644 index 0000000000..4cac7cfb93 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug902156_1.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 1 for Bug 902156 - See file browser_bug902156.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=902156 +--> +<head> + <meta charset="utf-8"> + <title>Test 1 for Bug 902156</title> +</head> +<body> + <div id="mctestdiv">Mixed Content Blocker enabled</div> + <script src="http://test1.example.com/browser/browser/base/content/test/siteIdentity/file_bug902156.js" ></script> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug902156_2.html b/browser/base/content/test/siteIdentity/file_bug902156_2.html new file mode 100644 index 0000000000..c815a09a93 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug902156_2.html @@ -0,0 +1,17 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 2 for Bug 902156 - See file browser_bug902156.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=902156 +--> +<head> + <meta charset="utf-8"> + <title>Test 2 for Bug 902156</title> +</head> +<body> + <div id="mctestdiv">Mixed Content Blocker enabled</div> + <a href="https://test2.example.com/browser/browser/base/content/test/siteIdentity/file_bug902156_1.html" + id="mctestlink" target="_top">Go to http site</a> + <script src="http://test2.example.com/browser/browser/base/content/test/siteIdentity/file_bug902156.js" ></script> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug902156_3.html b/browser/base/content/test/siteIdentity/file_bug902156_3.html new file mode 100644 index 0000000000..7a26f4b0f0 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug902156_3.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 3 for Bug 902156 - See file browser_bug902156.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=902156 +--> +<head> + <meta charset="utf-8"> + <title>Test 3 for Bug 902156</title> +</head> +<body> + <div id="mctestdiv">Mixed Content Blocker enabled</div> + <script src="http://test1.example.com/browser/browser/base/content/test/siteIdentity/file_bug902156.js" ></script> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug906190.js b/browser/base/content/test/siteIdentity/file_bug906190.js new file mode 100644 index 0000000000..01ef4073fb --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug906190.js @@ -0,0 +1,6 @@ +/* + * Once the mixed content blocker is disabled for the page, this scripts loads + * and updates the text inside the div container. + */ +document.getElementById("mctestdiv").innerHTML = + "Mixed Content Blocker disabled"; diff --git a/browser/base/content/test/siteIdentity/file_bug906190.sjs b/browser/base/content/test/siteIdentity/file_bug906190.sjs new file mode 100644 index 0000000000..088153d671 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug906190.sjs @@ -0,0 +1,18 @@ +function handleRequest(request, response) { + var page = "<!DOCTYPE html><html><body>bug 906190</body></html>"; + var path = + "https://test1.example.com/browser/browser/base/content/test/siteIdentity/"; + var url; + + if (request.queryString.includes("bad-redirection=1")) { + url = path + "this_page_does_not_exist.html"; + } else { + url = path + "file_bug906190_redirected.html"; + } + + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Type", "text/html", false); + response.setStatusLine(request.httpVersion, "302", "Found"); + response.setHeader("Location", url, false); + response.write(page); +} diff --git a/browser/base/content/test/siteIdentity/file_bug906190_1.html b/browser/base/content/test/siteIdentity/file_bug906190_1.html new file mode 100644 index 0000000000..031c229f0d --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug906190_1.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 1 for Bug 906190 - See file browser_bug902156.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=906190 +--> +<head> + <meta charset="utf-8"> + <title>Test 1 for Bug 906190</title> +</head> +<body> + <div id="mctestdiv">Mixed Content Blocker enabled</div> + <script src="http://test1.example.com/browser/browser/base/content/test/siteIdentity/file_bug906190.js" ></script> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug906190_2.html b/browser/base/content/test/siteIdentity/file_bug906190_2.html new file mode 100644 index 0000000000..2a7546dca4 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug906190_2.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 2 for Bug 906190 - See file browser_bug902156.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=906190 +--> +<head> + <meta charset="utf-8"> + <title>Test 2 for Bug 906190</title> +</head> +<body> + <div id="mctestdiv">Mixed Content Blocker enabled</div> + <script src="http://test2.example.com/browser/browser/base/content/test/siteIdentity/file_bug906190.js" ></script> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug906190_3_4.html b/browser/base/content/test/siteIdentity/file_bug906190_3_4.html new file mode 100644 index 0000000000..e78e271f85 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug906190_3_4.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 3 and 4 for Bug 906190 - See file browser_bug902156.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=906190 +--> +<head> + <meta charset="utf-8"> + <meta http-equiv="refresh" content="0; url=https://test1.example.com/browser/browser/base/content/test/siteIdentity/file_bug906190_redirected.html"> + <title>Test 3 and 4 for Bug 906190</title> +</head> +<body> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_bug906190_redirected.html b/browser/base/content/test/siteIdentity/file_bug906190_redirected.html new file mode 100644 index 0000000000..d0bc4a39f5 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_bug906190_redirected.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<!-- + Redirected Page of Test 3 to 6 for Bug 906190 - See file browser_bug902156.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=906190 +--> +<head> + <meta charset="utf-8"> + <title>Redirected Page for Bug 906190</title> +</head> +<body> + <div id="mctestdiv">Mixed Content Blocker enabled</div> + <script src="http://test1.example.com/browser/browser/base/content/test/siteIdentity/file_bug906190.js" ></script> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_csp_block_all_mixedcontent.html b/browser/base/content/test/siteIdentity/file_csp_block_all_mixedcontent.html new file mode 100644 index 0000000000..b5463d8d5b --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_csp_block_all_mixedcontent.html @@ -0,0 +1,11 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1122236 - CSP: Implement block-all-mixed-content</title> + <meta http-equiv="Content-Security-Policy" content="block-all-mixed-content"> +</head> +<body> + <script src="http://example.com/browser/browser/base/content/test/siteIdentity/file_csp_block_all_mixedcontent.js"></script> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_csp_block_all_mixedcontent.js b/browser/base/content/test/siteIdentity/file_csp_block_all_mixedcontent.js new file mode 100644 index 0000000000..dc6d6a64e4 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_csp_block_all_mixedcontent.js @@ -0,0 +1,3 @@ +// empty script file just used for testing Bug 1122236. +// Making sure the UI is not degraded when blocking +// mixed content using the CSP directive: block-all-mixed-content. diff --git a/browser/base/content/test/siteIdentity/file_mixedContentFramesOnHttp.html b/browser/base/content/test/siteIdentity/file_mixedContentFramesOnHttp.html new file mode 100644 index 0000000000..3ed5b82641 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_mixedContentFramesOnHttp.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test for https://bugzilla.mozilla.org/show_bug.cgi?id=1182551 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1182551</title> +</head> +<body> + <p>Test for Bug 1182551. This is an HTTP top level page. We include an HTTPS iframe that loads mixed passive content.</p> + <iframe src="https://example.org/browser/browser/base/content/test/siteIdentity/file_mixedPassiveContent.html"></iframe> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_mixedContentFromOnunload.html b/browser/base/content/test/siteIdentity/file_mixedContentFromOnunload.html new file mode 100644 index 0000000000..ae134f8cb0 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_mixedContentFromOnunload.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test for https://bugzilla.mozilla.org/show_bug.cgi?id=947079 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 947079</title> +</head> +<body> + <p>Test for Bug 947079</p> + <script> + window.addEventListener("unload", function() { + new Image().src = "http://mochi.test:8888/tests/image/test/mochitest/blue.png"; + }); + </script> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_mixedContentFromOnunload_test1.html b/browser/base/content/test/siteIdentity/file_mixedContentFromOnunload_test1.html new file mode 100644 index 0000000000..1d027b0362 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_mixedContentFromOnunload_test1.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 1 for https://bugzilla.mozilla.org/show_bug.cgi?id=947079 +Page with no insecure subresources +--> +<head> + <meta charset="utf-8"> + <title>Test 1 for Bug 947079</title> +</head> +<body> + <p>There are no insecure resource loads on this page</p> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_mixedContentFromOnunload_test2.html b/browser/base/content/test/siteIdentity/file_mixedContentFromOnunload_test2.html new file mode 100644 index 0000000000..4813337cc8 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_mixedContentFromOnunload_test2.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test 2 for https://bugzilla.mozilla.org/show_bug.cgi?id=947079 +Page with an insecure image load +--> +<head> + <meta charset="utf-8"> + <title>Test 2 for Bug 947079</title> +</head> +<body> + <p>Page with http image load</p> + <img src="http://test2.example.com/tests/image/test/mochitest/blue.png"> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_mixedPassiveContent.html b/browser/base/content/test/siteIdentity/file_mixedPassiveContent.html new file mode 100644 index 0000000000..a60ac94e8b --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_mixedPassiveContent.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test for https://bugzilla.mozilla.org/show_bug.cgi?id=1182551 +--> +<head> + <meta charset="utf-8"> + <title>HTTPS page with HTTP image</title> +</head> +<body> + <img src="http://mochi.test:8888/tests/image/test/mochitest/blue.png"> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/file_pdf.pdf b/browser/base/content/test/siteIdentity/file_pdf.pdf new file mode 100644 index 0000000000..593558f9a4 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_pdf.pdf @@ -0,0 +1,12 @@ +%PDF-1.0
+1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj 2 0 obj<</Type/Pages/Kids[3 0 R]/Count 1>>endobj 3 0 obj<</Type/Page/MediaBox[0 0 3 3]>>endobj
+xref
+0 4
+0000000000 65535 f
+0000000010 00000 n
+0000000053 00000 n
+0000000102 00000 n
+trailer<</Size 4/Root 1 0 R>>
+startxref
+149
+%EOF
\ No newline at end of file diff --git a/browser/base/content/test/siteIdentity/file_pdf_blob.html b/browser/base/content/test/siteIdentity/file_pdf_blob.html new file mode 100644 index 0000000000..ff6ed659a2 --- /dev/null +++ b/browser/base/content/test/siteIdentity/file_pdf_blob.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset='utf-8'> +</head> +<body> + <script> + let blob = new Blob(["x"], { type: "application/pdf" }); + let blobURL = URL.createObjectURL(blob); + + let link = document.createElement("a"); + link.innerText = "PDF blob"; + link.target = "_blank"; + link.href = blobURL; + document.body.appendChild(link); + </script> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/head.js b/browser/base/content/test/siteIdentity/head.js new file mode 100644 index 0000000000..d2a588a815 --- /dev/null +++ b/browser/base/content/test/siteIdentity/head.js @@ -0,0 +1,435 @@ +function openIdentityPopup() { + gIdentityHandler._initializePopup(); + let mainView = document.getElementById("identity-popup-mainView"); + let viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown"); + gIdentityHandler._identityIconBox.click(); + return viewShown; +} + +function openPermissionPopup() { + gPermissionPanel._initializePopup(); + let mainView = document.getElementById("permission-popup-mainView"); + let viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown"); + gPermissionPanel.openPopup(); + return viewShown; +} + +function getIdentityMode(aWindow = window) { + return aWindow.document.getElementById("identity-box").className; +} + +/** + * Waits for a load (or custom) event to finish in a given tab. If provided + * load an uri into the tab. + * + * @param tab + * The tab to load into. + * @param [optional] url + * The url to load, or the current url. + * @return {Promise} resolved when the event is handled. + * @resolves to the received event + * @rejects if a valid load event is not received within a meaningful interval + */ +function promiseTabLoadEvent(tab, url) { + info("Wait tab event: load"); + + function handle(loadedUrl) { + if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) { + info(`Skipping spurious load event for ${loadedUrl}`); + return false; + } + + info("Tab event received: load"); + return true; + } + + let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle); + + if (url) { + BrowserTestUtils.loadURIString(tab.linkedBrowser, url); + } + + return loaded; +} + +// Compares the security state of the page with what is expected +function isSecurityState(browser, expectedState) { + let ui = browser.securityUI; + if (!ui) { + ok(false, "No security UI to get the security state"); + return; + } + + const wpl = Ci.nsIWebProgressListener; + + // determine the security state + let isSecure = ui.state & wpl.STATE_IS_SECURE; + let isBroken = ui.state & wpl.STATE_IS_BROKEN; + let isInsecure = ui.state & wpl.STATE_IS_INSECURE; + + let actualState; + if (isSecure && !(isBroken || isInsecure)) { + actualState = "secure"; + } else if (isBroken && !(isSecure || isInsecure)) { + actualState = "broken"; + } else if (isInsecure && !(isSecure || isBroken)) { + actualState = "insecure"; + } else { + actualState = "unknown"; + } + + is( + expectedState, + actualState, + "Expected state " + + expectedState + + " and the actual state is " + + actualState + + "." + ); +} + +/** + * Test the state of the identity box and control center to make + * sure they are correctly showing the expected mixed content states. + * + * @note The checks are done synchronously, but new code should wait on the + * returned Promise object to ensure the identity panel has closed. + * Bug 1221114 is filed to fix the existing code. + * + * @param tabbrowser + * @param Object states + * MUST include the following properties: + * { + * activeLoaded: true|false, + * activeBlocked: true|false, + * passiveLoaded: true|false, + * } + * + * @return {Promise} + * @resolves When the operation has finished and the identity panel has closed. + */ +async function assertMixedContentBlockingState(tabbrowser, states = {}) { + if ( + !tabbrowser || + !("activeLoaded" in states) || + !("activeBlocked" in states) || + !("passiveLoaded" in states) + ) { + throw new Error( + "assertMixedContentBlockingState requires a browser and a states object" + ); + } + + let { passiveLoaded, activeLoaded, activeBlocked } = states; + let { gIdentityHandler } = tabbrowser.ownerGlobal; + let doc = tabbrowser.ownerDocument; + let identityBox = gIdentityHandler._identityBox; + let classList = identityBox.classList; + let identityIcon = doc.getElementById("identity-icon"); + let identityIconImage = tabbrowser.ownerGlobal + .getComputedStyle(identityIcon) + .getPropertyValue("list-style-image"); + + let stateSecure = + gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_SECURE; + let stateBroken = + gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN; + let stateInsecure = + gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_INSECURE; + let stateActiveBlocked = + gIdentityHandler._state & + Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT; + let stateActiveLoaded = + gIdentityHandler._state & + Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT; + let statePassiveLoaded = + gIdentityHandler._state & + Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT; + + is( + activeBlocked, + !!stateActiveBlocked, + "Expected state for activeBlocked matches UI state" + ); + is( + activeLoaded, + !!stateActiveLoaded, + "Expected state for activeLoaded matches UI state" + ); + is( + passiveLoaded, + !!statePassiveLoaded, + "Expected state for passiveLoaded matches UI state" + ); + + if (stateInsecure) { + const insecureConnectionIcon = Services.prefs.getBoolPref( + "security.insecure_connection_icon.enabled" + ); + if (!insecureConnectionIcon) { + // HTTP request, there should be no MCB classes for the identity box and the non secure icon + // should always be visible regardless of MCB state. + ok(classList.contains("unknownIdentity"), "unknownIdentity on HTTP page"); + ok( + BrowserTestUtils.is_visible(identityIcon), + "information icon should be still visible" + ); + } else { + // HTTP request, there should be a broken padlock shown always. + ok(classList.contains("notSecure"), "notSecure on HTTP page"); + ok( + !BrowserTestUtils.is_hidden(identityIcon), + "information icon should be visible" + ); + } + + ok(!classList.contains("mixedActiveContent"), "No MCB icon on HTTP page"); + ok(!classList.contains("mixedActiveBlocked"), "No MCB icon on HTTP page"); + ok(!classList.contains("mixedDisplayContent"), "No MCB icon on HTTP page"); + ok( + !classList.contains("mixedDisplayContentLoadedActiveBlocked"), + "No MCB icon on HTTP page" + ); + } else { + // Make sure the identity box UI has the correct mixedcontent states and icons + is( + classList.contains("mixedActiveContent"), + activeLoaded, + "identityBox has expected class for activeLoaded" + ); + is( + classList.contains("mixedActiveBlocked"), + activeBlocked && !passiveLoaded, + "identityBox has expected class for activeBlocked && !passiveLoaded" + ); + is( + classList.contains("mixedDisplayContent"), + passiveLoaded && !(activeLoaded || activeBlocked), + "identityBox has expected class for passiveLoaded && !(activeLoaded || activeBlocked)" + ); + is( + classList.contains("mixedDisplayContentLoadedActiveBlocked"), + passiveLoaded && activeBlocked, + "identityBox has expected class for passiveLoaded && activeBlocked" + ); + + ok( + !BrowserTestUtils.is_hidden(identityIcon), + "information icon should be visible" + ); + if (activeLoaded) { + is( + identityIconImage, + 'url("chrome://global/skin/icons/security-broken.svg")', + "Using active loaded icon" + ); + } + if (activeBlocked && !passiveLoaded) { + is( + identityIconImage, + 'url("chrome://global/skin/icons/security.svg")', + "Using active blocked icon" + ); + } + if (passiveLoaded && !(activeLoaded || activeBlocked)) { + is( + identityIconImage, + 'url("chrome://global/skin/icons/security-warning.svg")', + "Using passive loaded icon" + ); + } + if (passiveLoaded && activeBlocked) { + is( + identityIconImage, + 'url("chrome://global/skin/icons/security-warning.svg")', + "Using active blocked and passive loaded icon" + ); + } + } + + // Make sure the identity popup has the correct mixedcontent states + let promisePanelOpen = BrowserTestUtils.waitForEvent( + tabbrowser.ownerGlobal, + "popupshown", + true, + event => event.target == gIdentityHandler._identityPopup + ); + gIdentityHandler._identityIconBox.click(); + await promisePanelOpen; + let popupAttr = doc + .getElementById("identity-popup") + .getAttribute("mixedcontent"); + let bodyAttr = doc + .getElementById("identity-popup-securityView-extended-info") + .getAttribute("mixedcontent"); + + is( + popupAttr.includes("active-loaded"), + activeLoaded, + "identity-popup has expected attr for activeLoaded" + ); + is( + bodyAttr.includes("active-loaded"), + activeLoaded, + "securityView-body has expected attr for activeLoaded" + ); + + is( + popupAttr.includes("active-blocked"), + activeBlocked, + "identity-popup has expected attr for activeBlocked" + ); + is( + bodyAttr.includes("active-blocked"), + activeBlocked, + "securityView-body has expected attr for activeBlocked" + ); + + is( + popupAttr.includes("passive-loaded"), + passiveLoaded, + "identity-popup has expected attr for passiveLoaded" + ); + is( + bodyAttr.includes("passive-loaded"), + passiveLoaded, + "securityView-body has expected attr for passiveLoaded" + ); + + // Make sure the correct icon is visible in the Control Center. + // This logic is controlled with CSS, so this helps prevent regressions there. + let securityViewBG = tabbrowser.ownerGlobal + .getComputedStyle( + document + .getElementById("identity-popup-securityView") + .getElementsByClassName("identity-popup-security-connection")[0] + ) + .getPropertyValue("list-style-image"); + let securityContentBG = tabbrowser.ownerGlobal + .getComputedStyle( + document + .getElementById("identity-popup-mainView") + .getElementsByClassName("identity-popup-security-connection")[0] + ) + .getPropertyValue("list-style-image"); + + if (stateInsecure) { + is( + securityViewBG, + 'url("chrome://global/skin/icons/security-broken.svg")', + "CC using 'not secure' icon" + ); + is( + securityContentBG, + 'url("chrome://global/skin/icons/security-broken.svg")', + "CC using 'not secure' icon" + ); + } + + if (stateSecure) { + is( + securityViewBG, + 'url("chrome://global/skin/icons/security.svg")', + "CC using secure icon" + ); + is( + securityContentBG, + 'url("chrome://global/skin/icons/security.svg")', + "CC using secure icon" + ); + } + + if (stateBroken) { + if (activeLoaded) { + is( + securityViewBG, + 'url("chrome://browser/skin/controlcenter/mcb-disabled.svg")', + "CC using active loaded icon" + ); + is( + securityContentBG, + 'url("chrome://browser/skin/controlcenter/mcb-disabled.svg")', + "CC using active loaded icon" + ); + } else if (activeBlocked || passiveLoaded) { + is( + securityViewBG, + 'url("chrome://global/skin/icons/security-warning.svg")', + "CC using degraded icon" + ); + is( + securityContentBG, + 'url("chrome://global/skin/icons/security-warning.svg")', + "CC using degraded icon" + ); + } else { + // There is a case here with weak ciphers, but no bc tests are handling this yet. + is( + securityViewBG, + 'url("chrome://global/skin/icons/security.svg")', + "CC using degraded icon" + ); + is( + securityContentBG, + 'url("chrome://global/skin/icons/security.svg")', + "CC using degraded icon" + ); + } + } + + if (activeLoaded || activeBlocked || passiveLoaded) { + let promiseViewShown = BrowserTestUtils.waitForEvent( + gIdentityHandler._identityPopup, + "ViewShown" + ); + doc.getElementById("identity-popup-security-button").click(); + await promiseViewShown; + is( + Array.prototype.filter.call( + doc + .getElementById("identity-popup-securityView") + .querySelectorAll(".identity-popup-mcb-learn-more"), + element => !BrowserTestUtils.is_hidden(element) + ).length, + 1, + "The 'Learn more' link should be visible once." + ); + } + + if (gIdentityHandler._identityPopup.state != "closed") { + let hideEvent = BrowserTestUtils.waitForEvent( + gIdentityHandler._identityPopup, + "popuphidden" + ); + info("Hiding identity popup"); + gIdentityHandler._identityPopup.hidePopup(); + await hideEvent; + } +} + +async function loadBadCertPage(url) { + let loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser); + BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, url); + await loaded; + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { + content.document.getElementById("exceptionDialogButton").click(); + }); + await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); +} + +// nsITLSServerSocket needs a certificate with a corresponding private key +// available. In mochitests, the certificate with the common name "Mochitest +// client" has such a key. +function getTestServerCertificate() { + const certDB = Cc["@mozilla.org/security/x509certdb;1"].getService( + Ci.nsIX509CertDB + ); + for (const cert of certDB.getCerts()) { + if (cert.commonName == "Mochitest client") { + return cert; + } + } + return null; +} diff --git a/browser/base/content/test/siteIdentity/iframe_navigation.html b/browser/base/content/test/siteIdentity/iframe_navigation.html new file mode 100644 index 0000000000..d4564569e7 --- /dev/null +++ b/browser/base/content/test/siteIdentity/iframe_navigation.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> +<meta charset="UTF-8"> +</head> +<body class="running"> + <script> + window.addEventListener("message", doNavigation); + + function doNavigation() { + let destination; + let destinationIdentifier = window.location.hash.substring(1); + switch (destinationIdentifier) { + case "blank": + destination = "about:blank"; + break; + case "secure": + destination = + "https://example.com/browser/browser/base/content/test/siteIdentity/dummy_page.html"; + break; + case "insecure": + destination = + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com/browser/browser/base/content/test/siteIdentity/dummy_page.html"; + break; + } + setTimeout(() => { + let frame = document.getElementById("navigateMe"); + frame.onload = done; + frame.onerror = done; + frame.src = destination; + }, 0); + } + + function done() { + document.body.classList.toggle("running"); + } + </script> + <iframe id="navigateMe" src="dummy_page.html"> + </iframe> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/insecure_opener.html b/browser/base/content/test/siteIdentity/insecure_opener.html new file mode 100644 index 0000000000..26ed014f63 --- /dev/null +++ b/browser/base/content/test/siteIdentity/insecure_opener.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML> +<html dir="ltr" xml:lang="en-US" lang="en-US"> + <head> + <meta charset="utf8"> + </head> + <body> + <a id="link" target="_blank" href="https://example.com/browser/toolkit/components/passwordmgr/test/browser/form_basic.html">Click me, I'm "secure".</a> + </body> +</html> diff --git a/browser/base/content/test/siteIdentity/open-self-from-frame.html b/browser/base/content/test/siteIdentity/open-self-from-frame.html new file mode 100644 index 0000000000..17d0cf56ef --- /dev/null +++ b/browser/base/content/test/siteIdentity/open-self-from-frame.html @@ -0,0 +1,6 @@ +<iframe src="about:blank"></iframe> +<script> + document.querySelector("iframe").contentDocument.write( + `<button onclick="window.open().document.write('Hi')">click me!</button>` + ); +</script> diff --git a/browser/base/content/test/siteIdentity/simple_mixed_passive.html b/browser/base/content/test/siteIdentity/simple_mixed_passive.html new file mode 100644 index 0000000000..2e4cda790a --- /dev/null +++ b/browser/base/content/test/siteIdentity/simple_mixed_passive.html @@ -0,0 +1 @@ +<img src="http://example.com/browser/browser/base/content/test/siteIdentity/moz.png"> diff --git a/browser/base/content/test/siteIdentity/test-mixedcontent-securityerrors.html b/browser/base/content/test/siteIdentity/test-mixedcontent-securityerrors.html new file mode 100644 index 0000000000..cb8cfdaaf5 --- /dev/null +++ b/browser/base/content/test/siteIdentity/test-mixedcontent-securityerrors.html @@ -0,0 +1,21 @@ +<!-- + Bug 875456 - Log mixed content messages from the Mixed Content Blocker to the + Security Pane in the Web Console +--> + +<!DOCTYPE HTML> +<html dir="ltr" xml:lang="en-US" lang="en-US"> + <head> + <meta charset="utf8"> + <title>Mixed Content test - http on https</title> + <script src="testscript.js"></script> + <!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ + --> + </head> + <body> + <iframe src="http://example.com"></iframe> + <img src="http://example.com/tests/image/test/mochitest/blue.png"></img> + </body> +</html> diff --git a/browser/base/content/test/siteIdentity/test_mcb_double_redirect_image.html b/browser/base/content/test/siteIdentity/test_mcb_double_redirect_image.html new file mode 100644 index 0000000000..adadf01944 --- /dev/null +++ b/browser/base/content/test/siteIdentity/test_mcb_double_redirect_image.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 7-9 for Bug 1082837 - See file browser_mcb_redirect.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=1082837 +--> +<head> + <meta charset="utf-8"> + <title>Bug 1082837</title> + <script> + function image_loaded() { + document.getElementById("mctestdiv").innerHTML = "image loaded"; + } + function image_blocked() { + document.getElementById("mctestdiv").innerHTML = "image blocked"; + } + </script> +</head> +<body> + <div id="mctestdiv"></div> + <img src="https://example.com/browser/browser/base/content/test/siteIdentity/test_mcb_redirect.sjs?image_redirect_http_sjs" onload="image_loaded()" onerror="image_blocked()" ></image> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/test_mcb_redirect.html b/browser/base/content/test/siteIdentity/test_mcb_redirect.html new file mode 100644 index 0000000000..fc7ccc2764 --- /dev/null +++ b/browser/base/content/test/siteIdentity/test_mcb_redirect.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 1 for Bug 418354 - See file browser_mcb_redirect.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=418354 +--> +<head> + <meta charset="utf-8"> + <title>Bug 418354</title> +</head> +<body> + <div id="mctestdiv">script blocked</div> + <script src="https://example.com/browser/browser/base/content/test/siteIdentity/test_mcb_redirect.sjs?script" ></script> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/test_mcb_redirect.js b/browser/base/content/test/siteIdentity/test_mcb_redirect.js new file mode 100644 index 0000000000..48538c9409 --- /dev/null +++ b/browser/base/content/test/siteIdentity/test_mcb_redirect.js @@ -0,0 +1,5 @@ +/* + * Once the mixed content blocker is disabled for the page, this scripts loads + * and updates the text inside the div container. + */ +document.getElementById("mctestdiv").innerHTML = "script executed"; diff --git a/browser/base/content/test/siteIdentity/test_mcb_redirect.sjs b/browser/base/content/test/siteIdentity/test_mcb_redirect.sjs new file mode 100644 index 0000000000..53b8cf2b08 --- /dev/null +++ b/browser/base/content/test/siteIdentity/test_mcb_redirect.sjs @@ -0,0 +1,29 @@ +function handleRequest(request, response) { + var page = + "<!DOCTYPE html><html><body>bug 418354 and bug 1082837</body></html>"; + + let redirect; + if (request.queryString === "script") { + redirect = + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com/browser/browser/base/content/test/siteIdentity/test_mcb_redirect.js"; + response.setHeader("Cache-Control", "no-cache", false); + } else if (request.queryString === "image_http") { + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + redirect = "http://example.com/tests/image/test/mochitest/blue.png"; + response.setHeader("Cache-Control", "max-age=3600", false); + } else if (request.queryString === "image_redirect_http_sjs") { + redirect = + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com/browser/browser/base/content/test/siteIdentity/test_mcb_redirect.sjs?image_redirect_https"; + response.setHeader("Cache-Control", "max-age=3600", false); + } else if (request.queryString === "image_redirect_https") { + redirect = "https://example.com/tests/image/test/mochitest/blue.png"; + response.setHeader("Cache-Control", "max-age=3600", false); + } + + response.setHeader("Content-Type", "text/html", false); + response.setStatusLine(request.httpVersion, "302", "Found"); + response.setHeader("Location", redirect, false); + response.write(page); +} diff --git a/browser/base/content/test/siteIdentity/test_mcb_redirect_image.html b/browser/base/content/test/siteIdentity/test_mcb_redirect_image.html new file mode 100644 index 0000000000..42da0d7c13 --- /dev/null +++ b/browser/base/content/test/siteIdentity/test_mcb_redirect_image.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 3-6 for Bug 1082837 - See file browser_mcb_redirect.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=1082837 +--> +<head> + <meta charset="utf-8"> + <title>Bug 1082837</title> + <script> + function image_loaded() { + document.getElementById("mctestdiv").innerHTML = "image loaded"; + } + function image_blocked() { + document.getElementById("mctestdiv").innerHTML = "image blocked"; + } + </script> +</head> +<body> + <div id="mctestdiv"></div> + <img src="https://example.com/browser/browser/base/content/test/siteIdentity/test_mcb_redirect.sjs?image_http" onload="image_loaded()" onerror="image_blocked()" ></image> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/test_no_mcb_for_loopback.html b/browser/base/content/test/siteIdentity/test_no_mcb_for_loopback.html new file mode 100644 index 0000000000..34193d370b --- /dev/null +++ b/browser/base/content/test/siteIdentity/test_no_mcb_for_loopback.html @@ -0,0 +1,56 @@ +<!-- See browser_no_mcb_for_localhost.js --> +<!DOCTYPE HTML> +<html> + <head> + <meta charset="utf8"> + <title>Bug 903966, Bug 1402530</title> + </head> + + <style> + @font-face { + font-family: "Font-IPv4"; + src: url("http://127.0.0.1:8/test.ttf"); + } + + @font-face { + font-family: "Font-IPv6"; + src: url("http://[::1]:8/test.ttf"); + } + + #ip-v4 { + font-family: "Font-IPv4" + } + + #ip-v6 { + font-family: "Font-IPv6" + } + </style> + + <body> + <div id="ip-v4">test</div> + <div id="ip-v6">test</div> + + <img src="http://127.0.0.1:8/test.png"> + <img src="http://[::1]:8/test.png"> + <img src="http://localhost:8/test.png"> + + <iframe src="http://127.0.0.1:8/test.html"></iframe> + <iframe src="http://[::1]:8/test.html"></iframe> + <iframe src="http://localhost:8/test.html"></iframe> + </body> + + <script src="http://127.0.0.1:8/test.js"></script> + <script src="http://[::1]:8/test.js"></script> + <script src="http://localhost:8/test.js"></script> + + <link href="http://127.0.0.1:8/test.css" rel="stylesheet"></link> + <link href="http://[::1]:8/test.css" rel="stylesheet"></link> + <link href="http://localhost:8/test.css" rel="stylesheet"></link> + + <script> + fetch("http://127.0.0.1:8"); + fetch("http://localhost:8"); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + fetch("http://[::1]:8"); + </script> +</html> diff --git a/browser/base/content/test/siteIdentity/test_no_mcb_for_onions.html b/browser/base/content/test/siteIdentity/test_no_mcb_for_onions.html new file mode 100644 index 0000000000..c73c3681a3 --- /dev/null +++ b/browser/base/content/test/siteIdentity/test_no_mcb_for_onions.html @@ -0,0 +1,29 @@ +<!-- See browser_no_mcb_for_onions.js --> +<!DOCTYPE HTML> +<html> + <head> + <meta charset="utf8"> + <title>Bug 1382359</title> + </head> + + <style> + @font-face { + src: url("http://123456789abcdef.onion:8/test.ttf"); + } + </style> + + <body> + <img src="http://123456789abcdef.onion:8/test.png"> + + <iframe src="http://123456789abcdef.onion:8/test.html"></iframe> + </body> + + <script src="http://123456789abcdef.onion:8/test.js"></script> + + <link href="http://123456789abcdef.onion:8/test.css" rel="stylesheet"></link> + + <script> + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + fetch("http://123456789abcdef.onion:8"); + </script> +</html> diff --git a/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font.css b/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font.css new file mode 100644 index 0000000000..0587ef7939 --- /dev/null +++ b/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font.css @@ -0,0 +1,11 @@ +@font-face { + font-family: testFont; + src: url(http://example.com/browser/devtools/client/fontinspector/test/browser_font.woff); +} +/* stylelint-disable font-family-no-missing-generic-family-keyword */ +body { + font-family: Arial; +} +div { + font-family: testFont; +} diff --git a/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font.html b/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font.html new file mode 100644 index 0000000000..7b39be064c --- /dev/null +++ b/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 2 for Bug 909920 - See file browser_no_mcb_on_http_site.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=909920 +--> +<head> + <meta charset="utf-8"> + <title>Test 2 for Bug 909920</title> + <link rel="stylesheet" type="text/css" href="https://example.com/browser/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font.css" /> +<script src="/tests/SimpleTest/SimpleTest.js"></script> + +<script type="text/javascript"> + async function checkLoadStates() { + let state = await SpecialPowers.getSecurityState(window); + + var loadedMixedActive = + !!(state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT); + is(loadedMixedActive, false, "OK: Should not load mixed active content!"); + + var blockedMixedActive = + !!(state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT); + is(blockedMixedActive, false, "OK: Should not block mixed active content!"); + + var loadedMixedDisplay = + !!(state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT); + is(loadedMixedDisplay, false, "OK: Should not load mixed display content!"); + + var blockedMixedDisplay = + !!(state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT); + is(blockedMixedDisplay, false, "OK: Should not block mixed display content!"); + + var newValue = "Verifying MCB does not trigger warning/error for an http page with https css that includes http font"; + // eslint-disable-next-line no-unsanitized/property + document.getElementById("testDiv").innerHTML = newValue; + } +</script> +</head> +<body onload="checkLoadStates()"> + <div class="testDiv" id="testDiv"> + Testing MCB does not trigger warning/error for an http page with https css that includes http font + </div> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font2.css b/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font2.css new file mode 100644 index 0000000000..3ac6c87a6b --- /dev/null +++ b/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font2.css @@ -0,0 +1 @@ +@import url(http://example.com/browser/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font.css); diff --git a/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font2.html b/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font2.html new file mode 100644 index 0000000000..3da31592dd --- /dev/null +++ b/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font2.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 3 for Bug 909920 - See file browser_no_mcb_on_http_site.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=909920 +--> +<head> + <meta charset="utf-8"> + <title>Test 3 for Bug 909920</title> + <link rel="stylesheet" type="text/css" href="https://example.com/browser/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font2.css" /> +<script src="/tests/SimpleTest/SimpleTest.js"></script> + +<script type="text/javascript"> + async function checkLoadStates() { + let state = await SpecialPowers.getSecurityState(window); + + var loadedMixedActive = + !!(state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT); + is(loadedMixedActive, false, "OK: Should not load mixed active content!"); + + var blockedMixedActive = + !!(state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT); + is(blockedMixedActive, false, "OK: Should not block mixed active content!"); + + var loadedMixedDisplay = + !!(state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT); + is(loadedMixedDisplay, false, "OK: Should not load mixed display content!"); + + var blockedMixedDisplay = + !!(state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT); + is(blockedMixedDisplay, false, "OK: Should not block mixed display content!"); + + var newValue = "Verifying MCB does not trigger warning/error for an http page "; + newValue += "with https css that imports another http css which includes http font"; + // eslint-disable-next-line no-unsanitized/property + document.getElementById("testDiv").innerHTML = newValue; + } +</script> +</head> +<body onload="checkLoadStates()"> + <div class="testDiv" id="testDiv"> + Testing MCB does not trigger warning/error for an http page with https css that imports another http css which includes http font + </div> +</body> +</html> diff --git a/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_img.css b/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_img.css new file mode 100644 index 0000000000..d045e21ba0 --- /dev/null +++ b/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_img.css @@ -0,0 +1,3 @@ +#testDiv { + background: url(http://example.com/tests/image/test/mochitest/blue.png) +} diff --git a/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_img.html b/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_img.html new file mode 100644 index 0000000000..10aa281959 --- /dev/null +++ b/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_img.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test 1 for Bug 909920 - See file browser_no_mcb_on_http_site.js for description. + https://bugzilla.mozilla.org/show_bug.cgi?id=909920 +--> +<head> + <meta charset="utf-8"> + <title>Test 1 for Bug 909920</title> + <link rel="stylesheet" type="text/css" href="https://example.com/browser/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_img.css" /> +<script src="/tests/SimpleTest/SimpleTest.js"></script> + +<script type="text/javascript"> + async function checkLoadStates() { + let state = await SpecialPowers.getSecurityState(window); + + var loadedMixedActive = + !!(state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT); + is(loadedMixedActive, false, "OK: Should not load mixed active content!"); + + var blockedMixedActive = + !!(state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT); + is(blockedMixedActive, false, "OK: Should not block mixed active content!"); + + var loadedMixedDisplay = + !!(state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT); + is(loadedMixedDisplay, false, "OK: Should not load mixed display content!"); + + var blockedMixedDisplay = + !!(state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT); + is(blockedMixedDisplay, false, "OK: Should not block mixed display content!"); + + var newValue = "Verifying MCB does not trigger warning/error for an http page with https css that includes http image"; + // eslint-disable-next-line no-unsanitized/property + document.getElementById("testDiv").innerHTML = newValue; + } +</script> +</head> +<body onload="checkLoadStates()"> + <div class="testDiv" id="testDiv"> + Testing MCB does not trigger warning/error for an http page with https css that includes http image + </div> +</body> +</html> |