summaryrefslogtreecommitdiffstats
path: root/dom/security/test/https-first
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/security/test/https-first
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/security/test/https-first')
-rw-r--r--dom/security/test/https-first/browser.ini35
-rw-r--r--dom/security/test/https-first/browser_beforeunload_permit_http.js208
-rw-r--r--dom/security/test/https-first/browser_downgrade_mixed_content_auto_upgrade_console.js80
-rw-r--r--dom/security/test/https-first/browser_downgrade_view_source.js81
-rw-r--r--dom/security/test/https-first/browser_download_attribute.js125
-rw-r--r--dom/security/test/https-first/browser_httpsfirst.js74
-rw-r--r--dom/security/test/https-first/browser_httpsfirst_console_logging.js71
-rw-r--r--dom/security/test/https-first/browser_httpsfirst_speculative_connect.js69
-rw-r--r--dom/security/test/https-first/browser_mixed_content_console.js104
-rw-r--r--dom/security/test/https-first/browser_mixed_content_download.js156
-rw-r--r--dom/security/test/https-first/browser_navigation.js97
-rw-r--r--dom/security/test/https-first/browser_slow_download.js158
-rw-r--r--dom/security/test/https-first/browser_upgrade_onion.js60
-rw-r--r--dom/security/test/https-first/download_page.html22
-rw-r--r--dom/security/test/https-first/download_server.sjs16
-rw-r--r--dom/security/test/https-first/file_bad_cert.sjs34
-rw-r--r--dom/security/test/https-first/file_beforeunload_permit_http.html9
-rw-r--r--dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs61
-rw-r--r--dom/security/test/https-first/file_data_uri.html16
-rw-r--r--dom/security/test/https-first/file_downgrade_500_responses.sjs70
-rw-r--r--dom/security/test/https-first/file_downgrade_bad_responses.sjs73
-rw-r--r--dom/security/test/https-first/file_downgrade_request_upgrade_request.sjs52
-rw-r--r--dom/security/test/https-first/file_downgrade_view_source.sjs30
-rw-r--r--dom/security/test/https-first/file_downgrade_with_different_path.sjs27
-rw-r--r--dom/security/test/https-first/file_download_attribute.html14
-rw-r--r--dom/security/test/https-first/file_download_attribute.sjs12
-rw-r--r--dom/security/test/https-first/file_form_submission.sjs84
-rw-r--r--dom/security/test/https-first/file_fragment.html43
-rw-r--r--dom/security/test/https-first/file_httpsfirst_speculative_connect.html1
-rw-r--r--dom/security/test/https-first/file_httpsfirst_timeout_server.sjs13
-rw-r--r--dom/security/test/https-first/file_mixed_content_auto_upgrade.html12
-rw-r--r--dom/security/test/https-first/file_mixed_content_console.html13
-rw-r--r--dom/security/test/https-first/file_multiple_redirection.sjs87
-rw-r--r--dom/security/test/https-first/file_navigation.html6
-rw-r--r--dom/security/test/https-first/file_redirect.sjs58
-rw-r--r--dom/security/test/https-first/file_redirect_downgrade.sjs87
-rw-r--r--dom/security/test/https-first/file_referrer_policy.sjs102
-rw-r--r--dom/security/test/https-first/file_slow_download.html14
-rw-r--r--dom/security/test/https-first/file_slow_download.sjs26
-rw-r--r--dom/security/test/https-first/file_toplevel_cookies.sjs233
-rw-r--r--dom/security/test/https-first/file_upgrade_insecure.html72
-rw-r--r--dom/security/test/https-first/file_upgrade_insecure_server.sjs114
-rw-r--r--dom/security/test/https-first/mochitest.ini44
-rw-r--r--dom/security/test/https-first/pass.pngbin0 -> 1689 bytes
-rw-r--r--dom/security/test/https-first/test.ogvbin0 -> 2344665 bytes
-rw-r--r--dom/security/test/https-first/test.wavbin0 -> 353022 bytes
-rw-r--r--dom/security/test/https-first/test_bad_cert.html67
-rw-r--r--dom/security/test/https-first/test_break_endless_upgrade_downgrade_loop.html80
-rw-r--r--dom/security/test/https-first/test_data_uri.html51
-rw-r--r--dom/security/test/https-first/test_downgrade_500_responses.html63
-rw-r--r--dom/security/test/https-first/test_downgrade_bad_responses.html63
-rw-r--r--dom/security/test/https-first/test_downgrade_request_upgrade_request.html52
-rw-r--r--dom/security/test/https-first/test_form_submission.html122
-rw-r--r--dom/security/test/https-first/test_fragment.html59
-rw-r--r--dom/security/test/https-first/test_multiple_redirection.html76
-rw-r--r--dom/security/test/https-first/test_redirect_downgrade.html59
-rw-r--r--dom/security/test/https-first/test_redirect_upgrade.html58
-rw-r--r--dom/security/test/https-first/test_referrer_policy.html237
-rw-r--r--dom/security/test/https-first/test_resource_upgrade.html118
-rw-r--r--dom/security/test/https-first/test_toplevel_cookies.html116
60 files changed, 3984 insertions, 0 deletions
diff --git a/dom/security/test/https-first/browser.ini b/dom/security/test/https-first/browser.ini
new file mode 100644
index 0000000000..9e355423dc
--- /dev/null
+++ b/dom/security/test/https-first/browser.ini
@@ -0,0 +1,35 @@
+[browser_httpsfirst.js]
+support-files =
+ file_httpsfirst_timeout_server.sjs
+[browser_httpsfirst_console_logging.js]
+[browser_upgrade_onion.js]
+[browser_mixed_content_console.js]
+support-files =
+ file_mixed_content_console.html
+[browser_downgrade_view_source.js]
+support-files = file_downgrade_view_source.sjs
+[browser_navigation.js]
+support-files = file_navigation.html
+[browser_httpsfirst_speculative_connect.js]
+support-files = file_httpsfirst_speculative_connect.html
+[browser_download_attribute.js]
+support-files = file_download_attribute.html
+ file_download_attribute.sjs
+
+[browser_mixed_content_download.js]
+skip-if = win10_2004 && debug # Bug 1723573
+support-files =
+ download_page.html
+ download_server.sjs
+[browser_slow_download.js]
+support-files =
+ file_slow_download.html
+ file_slow_download.sjs
+[browser_downgrade_mixed_content_auto_upgrade_console.js]
+support-files =
+ file_mixed_content_auto_upgrade.html
+ pass.png
+ test.ogv
+ test.wav
+[browser_beforeunload_permit_http.js]
+support-files = file_beforeunload_permit_http.html
diff --git a/dom/security/test/https-first/browser_beforeunload_permit_http.js b/dom/security/test/https-first/browser_beforeunload_permit_http.js
new file mode 100644
index 0000000000..dda5fbbba9
--- /dev/null
+++ b/dom/security/test/https-first/browser_beforeunload_permit_http.js
@@ -0,0 +1,208 @@
+"use strict";
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+const TEST_PATH_HTTP = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://nocert.example.com/"
+);
+/*
+ * Description of Tests:
+ *
+ * Test load page and reload:
+ * 1. Enable HTTPS-First and the pref to trigger beforeunload by user interaction
+ * 2. Open an HTTP site. HTTPS-First will try to upgrade it to https - but since it has no cert that try will fail
+ * 3. Then simulated user interaction and reload the page with a reload flag.
+ * 4. That should lead to a beforeUnload prompt that asks for users permission to perform reload. HTTPS-First should not try to upgrade the reload again
+ *
+ * Test Navigation:
+ * 1. Enable HTTPS-First and the pref to trigger beforeunload by user interaction
+ * 2. Open an http site. HTTPS-First will try to upgrade it to https - but since it has no cert for https that try will fail
+ * 3. Then simulated user interaction and navigate to another http page. Again HTTPS-First will try to upgrade to HTTPS
+ * 4. This attempted navigation leads to a prompt which askes for permission to leave page - accept it
+ * 5. Since the site is not using a valid HTTPS cert HTTPS-First will downgrade the request back to HTTP
+ * 6. User should NOT get asked again for permission to unload
+ *
+ * Test Session History Navigation:
+ * 1. Enable HTTPS-First and the pref to trigger beforeunload by user interaction
+ * 2. Open an http site. HTTPS-First will try to upgrade it to https - but since it has no cert for https that try will fail
+ * 3. Then navigate to another http page and simulated a user interaction.
+ * 4. Trigger a session history navigation by clicking the "back button".
+ * 5. This attempted navigation leads to a prompt which askes for permission to leave page - accept it
+ */
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.security.https_first", true],
+ ["dom.require_user_interaction_for_beforeunload", true],
+ ],
+ });
+});
+const TESTS = [
+ {
+ name: "Normal Reload (No flag)",
+ reloadFlag: Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
+ },
+ {
+ name: "Bypass Cache Reload",
+ reloadFlag: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE,
+ },
+ {
+ name: "Bypass Proxy Reload",
+ reloadFlag: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY,
+ },
+ {
+ name: "Bypass Cache and Proxy Reload",
+ reloadFlag:
+ Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE |
+ Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY,
+ },
+];
+
+add_task(async function testReloadFlags() {
+ for (let index = 0; index < TESTS.length; index++) {
+ const testCase = TESTS[index];
+ // The onbeforeunload dialog should appear
+ let dialogPromise = PromptTestUtils.waitForPrompt(null, {
+ modalType: Services.prompt.MODAL_TYPE_CONTENT,
+ promptType: "confirmEx",
+ });
+ let reloadPromise = loadPageAndReload(testCase);
+ let dialog = await dialogPromise;
+ Assert.ok(true, "Showed the beforeunload dialog.");
+ await PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 0 });
+ await reloadPromise;
+ }
+});
+
+add_task(async function testNavigation() {
+ // The onbeforeunload dialog should appear
+ let dialogPromise = PromptTestUtils.waitForPrompt(null, {
+ modalType: Services.prompt.MODAL_TYPE_CONTENT,
+ promptType: "confirmEx",
+ });
+
+ let openPagePromise = openPage();
+ let dialog = await dialogPromise;
+ Assert.ok(true, "Showed the beforeunload dialog.");
+ await PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 0 });
+ await openPagePromise;
+});
+
+add_task(async function testSessionHistoryNavigation() {
+ // The onbeforeunload dialog should appear
+ let dialogPromise = PromptTestUtils.waitForPrompt(null, {
+ modalType: Services.prompt.MODAL_TYPE_CONTENT,
+ promptType: "confirmEx",
+ });
+
+ let openPagePromise = loadPagesAndUseBackButton();
+ let dialog = await dialogPromise;
+ Assert.ok(true, "Showed the beforeunload dialog.");
+ await PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 0 });
+ await openPagePromise;
+});
+
+async function openPage() {
+ // Open about:blank in a new tab
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (browser) {
+ // Load http page
+ BrowserTestUtils.loadURIString(
+ browser,
+ `${TEST_PATH_HTTP}file_beforeunload_permit_http.html`
+ );
+ await BrowserTestUtils.browserLoaded(browser);
+ // Interact with page such that unload permit will be necessary
+ await BrowserTestUtils.synthesizeMouse("body", 2, 2, {}, browser);
+ let hasInteractedWith = await SpecialPowers.spawn(
+ browser,
+ [""],
+ function () {
+ return content.document.userHasInteracted;
+ }
+ );
+
+ is(true, hasInteractedWith, "Simulated successfully user interaction");
+ // And then navigate away to another site which proves that user won't be asked twice to permit a reload (otherwise the test get timed out)
+ BrowserTestUtils.loadURIString(
+ browser,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://self-signed.example.com/"
+ );
+ await BrowserTestUtils.browserLoaded(browser);
+ Assert.ok(true, "Navigated successfully.");
+ }
+ );
+}
+
+async function loadPageAndReload(testCase) {
+ // Load initial site
+ // Open about:blank in a new tab
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (browser) {
+ BrowserTestUtils.loadURIString(
+ browser,
+ `${TEST_PATH_HTTP}file_beforeunload_permit_http.html`
+ );
+ await BrowserTestUtils.browserLoaded(browser);
+ // Interact with page such that unload permit will be necessary
+ await BrowserTestUtils.synthesizeMouse("body", 2, 2, {}, browser);
+
+ let hasInteractedWith = await SpecialPowers.spawn(
+ browser,
+ [""],
+ function () {
+ return content.document.userHasInteracted;
+ }
+ );
+ is(true, hasInteractedWith, "Simulated successfully user interaction");
+ BrowserReloadWithFlags(testCase.reloadFlag);
+ await BrowserTestUtils.browserLoaded(browser);
+ is(true, true, `reload with flag ${testCase.name} was successful`);
+ }
+ );
+}
+
+async function loadPagesAndUseBackButton() {
+ // Load initial site
+ // Open about:blank in a new tab
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (browser) {
+ BrowserTestUtils.loadURIString(
+ browser,
+ `${TEST_PATH_HTTP}file_beforeunload_permit_http.html`
+ );
+ await BrowserTestUtils.browserLoaded(browser);
+
+ BrowserTestUtils.loadURIString(
+ browser,
+ `${TEST_PATH_HTTP}file_beforeunload_permit_http.html?getASessionHistoryEntry`
+ );
+ await BrowserTestUtils.browserLoaded(browser);
+ // Interact with page such that unload permit will be necessary
+ await BrowserTestUtils.synthesizeMouse("body", 2, 2, {}, browser);
+
+ let hasInteractedWith = await SpecialPowers.spawn(
+ browser,
+ [""],
+ function () {
+ return content.document.userHasInteracted;
+ }
+ );
+ is(true, hasInteractedWith, "Simulated successfully user interaction");
+ // Go back one site by clicking the back button
+ info("Clicking back button");
+ let backButton = document.getElementById("back-button");
+ backButton.click();
+ await BrowserTestUtils.browserLoaded(browser);
+ is(true, true, `Got back successful`);
+ }
+ );
+}
diff --git a/dom/security/test/https-first/browser_downgrade_mixed_content_auto_upgrade_console.js b/dom/security/test/https-first/browser_downgrade_mixed_content_auto_upgrade_console.js
new file mode 100644
index 0000000000..2235d7392c
--- /dev/null
+++ b/dom/security/test/https-first/browser_downgrade_mixed_content_auto_upgrade_console.js
@@ -0,0 +1,80 @@
+// Bug 1673574 - Improve Console logging for mixed content auto upgrading
+"use strict";
+
+const testPath = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://httpsfirst.com"
+);
+
+let tests = [
+ {
+ description: "Top-Level upgrade should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.warn,
+ expectIncludes: ["Upgrading insecure request", "to use", "httpsfirst.com"],
+ },
+ {
+ description: "Top-Level upgrade failure should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.warn,
+ expectIncludes: [
+ "Upgrading insecure request",
+ "failed",
+ "httpsfirst.com",
+ "Downgrading to",
+ ],
+ },
+];
+
+const kTestURI = testPath + "file_mixed_content_auto_upgrade.html";
+
+add_task(async function () {
+ // A longer timeout is necessary for this test than the plain mochitests
+ // due to opening a new tab with the web console.
+ requestLongerTimeout(4);
+
+ // Enable ML2 and HTTPS-First Mode and register console-listener
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["security.mixed_content.upgrade_display_content", true],
+ ["dom.security.https_first", true],
+ ],
+ });
+ Services.console.registerListener(on_new_message);
+ // 1. Upgrade page to https://
+ await BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, kTestURI);
+
+ await BrowserTestUtils.waitForCondition(() => tests.length === 0);
+
+ // Clean up
+ Services.console.unregisterListener(on_new_message);
+});
+
+function on_new_message(msgObj) {
+ const message = msgObj.message;
+ const logLevel = msgObj.logLevel;
+
+ // The console message is:
+ // Should only show HTTPS-First messages
+
+ if (message.includes("Mixed Content:")) {
+ ok(
+ !message.includes("Upgrading insecure display request"),
+ "msg included a mixed content upgrade"
+ );
+ }
+ if (message.includes("HTTPS-First Mode:")) {
+ for (let i = 0; i < tests.length; i++) {
+ const testCase = tests[i];
+ // Check if log-level matches
+ if (logLevel !== testCase.expectLogLevel) {
+ continue;
+ }
+ // Check if all substrings are included
+ if (testCase.expectIncludes.some(str => !message.includes(str))) {
+ continue;
+ }
+ ok(true, testCase.description);
+ tests.splice(i, 1);
+ break;
+ }
+ }
+}
diff --git a/dom/security/test/https-first/browser_downgrade_view_source.js b/dom/security/test/https-first/browser_downgrade_view_source.js
new file mode 100644
index 0000000000..38ff468490
--- /dev/null
+++ b/dom/security/test/https-first/browser_downgrade_view_source.js
@@ -0,0 +1,81 @@
+// This test ensures that view-source:https falls back to view-source:http
+"use strict";
+
+const TEST_PATH_HTTP = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+
+const TEST_PATH_HTTPS = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+async function runTest(desc, url, expectedURI, excpectedContent) {
+ await BrowserTestUtils.withNewTab("about:blank", async function (browser) {
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, null, true);
+ BrowserTestUtils.loadURIString(browser, url);
+ await loaded;
+
+ await SpecialPowers.spawn(
+ browser,
+ [desc, expectedURI, excpectedContent],
+ async function (desc, expectedURI, excpectedContent) {
+ let loadedURI = content.document.documentURI;
+ is(loadedURI, expectedURI, desc);
+ let loadedContent = content.document.body.textContent;
+ is(loadedContent, excpectedContent, desc);
+ }
+ );
+ });
+}
+
+add_task(async function () {
+ requestLongerTimeout(2);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first", true]],
+ });
+
+ await runTest(
+ "URL with query 'downgrade' should be http:",
+ `view-source:${TEST_PATH_HTTP}/file_downgrade_view_source.sjs?downgrade`,
+ `view-source:${TEST_PATH_HTTP}/file_downgrade_view_source.sjs?downgrade`,
+ "view-source:http://"
+ );
+
+ await runTest(
+ "URL with query 'downgrade' should be http and leave query params untouched:",
+ `view-source:${TEST_PATH_HTTP}/file_downgrade_view_source.sjs?downgrade&https://httpsfirst.com`,
+ `view-source:${TEST_PATH_HTTP}/file_downgrade_view_source.sjs?downgrade&https://httpsfirst.com`,
+ "view-source:http://"
+ );
+
+ await runTest(
+ "URL with query 'upgrade' should be https:",
+ `view-source:${TEST_PATH_HTTP}/file_downgrade_view_source.sjs?upgrade`,
+ `view-source:${TEST_PATH_HTTPS}/file_downgrade_view_source.sjs?upgrade`,
+ "view-source:https://"
+ );
+
+ await runTest(
+ "URL with query 'upgrade' should be https:",
+ `view-source:${TEST_PATH_HTTPS}/file_downgrade_view_source.sjs?upgrade`,
+ `view-source:${TEST_PATH_HTTPS}/file_downgrade_view_source.sjs?upgrade`,
+ "view-source:https://"
+ );
+
+ await runTest(
+ "URL with query 'upgrade' should be https and leave query params untouched:",
+ `view-source:${TEST_PATH_HTTP}/file_downgrade_view_source.sjs?upgrade&https://httpsfirst.com`,
+ `view-source:${TEST_PATH_HTTPS}/file_downgrade_view_source.sjs?upgrade&https://httpsfirst.com`,
+ "view-source:https://"
+ );
+
+ await runTest(
+ "URL with query 'upgrade' should be https and leave query params untouched:",
+ `view-source:${TEST_PATH_HTTPS}/file_downgrade_view_source.sjs?upgrade&https://httpsfirst.com`,
+ `view-source:${TEST_PATH_HTTPS}/file_downgrade_view_source.sjs?upgrade&https://httpsfirst.com`,
+ "view-source:https://"
+ );
+});
diff --git a/dom/security/test/https-first/browser_download_attribute.js b/dom/security/test/https-first/browser_download_attribute.js
new file mode 100644
index 0000000000..81bd0d799a
--- /dev/null
+++ b/dom/security/test/https-first/browser_download_attribute.js
@@ -0,0 +1,125 @@
+"use strict";
+
+// Create a uri for an http site
+//(in that case a site without cert such that https-first isn't upgrading it)
+const insecureTestPath = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://nocert.example.com"
+);
+const insecureTestURI = insecureTestPath + "file_download_attribute.html";
+
+function promisePanelOpened() {
+ if (DownloadsPanel.panel && DownloadsPanel.panel.state == "open") {
+ return Promise.resolve();
+ }
+ return BrowserTestUtils.waitForEvent(DownloadsPanel.panel, "popupshown");
+}
+
+const CONSOLE_UPGRADE_TRY_MESSAGE = "Upgrading insecure request";
+const CONSOLE_ERROR_MESSAGE = "Downgrading to “http” again";
+const DOWNLOAD_PAGE_URL =
+ "nocert.example.com/browser/dom/security/test/https-first/file_download_attribute.html";
+const DOWNLOAD_LINK_URL =
+ "nocert.example.com/browser/dom/security/test/https-first/file_download_attribute.sjs";
+
+// Verifys that https-first tried to upgrade the download
+// - and that the upgrade attempt failed.
+// We will receive 4 messages. Two for upgrading and downgrading
+// the download page and another two for upgrading and downgrading
+// the download.
+let msgCounter = 0;
+function shouldConsoleTryUpgradeAndError() {
+ // Waits until CONSOLE_ERROR_MESSAGE was logged.
+ // Checks if download was tried via http://
+ return new Promise((resolve, reject) => {
+ function listener(msgObj) {
+ let text = msgObj.message;
+ // Verify upgrade messages
+ if (
+ text.includes(CONSOLE_UPGRADE_TRY_MESSAGE) &&
+ text.includes("http://")
+ ) {
+ if (msgCounter == 0) {
+ ok(
+ text.includes(DOWNLOAD_PAGE_URL),
+ "Tries to upgrade nocert example to https"
+ );
+ } else {
+ ok(
+ text.includes(DOWNLOAD_LINK_URL),
+ "Tries to upgrade download to https"
+ );
+ }
+ msgCounter++;
+ }
+ // Verify downgrade messages
+ if (text.includes(CONSOLE_ERROR_MESSAGE) && msgCounter > 0) {
+ if (msgCounter == 1) {
+ ok(
+ text.includes("https://" + DOWNLOAD_PAGE_URL),
+ "Downgrades nocert example to http"
+ );
+ msgCounter++;
+ } else {
+ ok(
+ text.includes("https://" + DOWNLOAD_LINK_URL),
+ "Downgrades download to http"
+ );
+ Services.console.unregisterListener(listener);
+ resolve();
+ }
+ }
+ }
+ Services.console.registerListener(listener);
+ });
+}
+
+// Test https-first download of an html file from an http site.
+// Test description:
+// 1. https-first tries to upgrade site to https
+// 2. upgrade fails because site has no certificate
+// 3. https-first downgrades to http and starts download via http
+// 4. Successfully completes download
+add_task(async function test_with_downloads_pref_enabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first", true]],
+ });
+ let checkPromise = shouldConsoleTryUpgradeAndError();
+ let downloadsPanelPromise = promisePanelOpened();
+ let downloadsPromise = Downloads.getList(Downloads.PUBLIC);
+
+ BrowserTestUtils.loadURIString(gBrowser, insecureTestURI);
+ // wait for downloadsPanel to open before continuing with test
+ await downloadsPanelPromise;
+ let downloadList = await downloadsPromise;
+ await checkPromise;
+ is(DownloadsPanel.isPanelShowing, true, "DownloadsPanel should be open.");
+ is(
+ downloadList._downloads.length,
+ 1,
+ "File should be successfully downloaded."
+ );
+
+ let [download] = downloadList._downloads;
+ is(download.contentType, "text/html", "File contentType should be correct.");
+ // ensure https-first didn't upgrade the scheme.
+ is(
+ download.source.url,
+ insecureTestPath + "file_download_attribute.sjs",
+ "Scheme should be http."
+ );
+
+ info("cleaning up downloads");
+ try {
+ if (Services.appinfo.OS === "WINNT") {
+ // We need to make the file writable to delete it on Windows.
+ await IOUtils.setPermissions(download.target.path, 0o600);
+ }
+ await IOUtils.remove(download.target.path);
+ } catch (error) {
+ info("The file " + download.target.path + " is not removed, " + error);
+ }
+
+ await downloadList.remove(download);
+ await download.finalize();
+});
diff --git a/dom/security/test/https-first/browser_httpsfirst.js b/dom/security/test/https-first/browser_httpsfirst.js
new file mode 100644
index 0000000000..a2f916a2f0
--- /dev/null
+++ b/dom/security/test/https-first/browser_httpsfirst.js
@@ -0,0 +1,74 @@
+"use strict";
+
+const TEST_PATH_HTTP = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+
+const TIMEOUT_PAGE_URI_HTTP =
+ TEST_PATH_HTTP + "file_httpsfirst_timeout_server.sjs";
+
+async function runPrefTest(aURI, aDesc, aAssertURLStartsWith) {
+ await BrowserTestUtils.withNewTab("about:blank", async function (browser) {
+ const loaded = BrowserTestUtils.browserLoaded(browser, false, null, true);
+ BrowserTestUtils.loadURIString(browser, aURI);
+ await loaded;
+
+ await ContentTask.spawn(
+ browser,
+ { aDesc, aAssertURLStartsWith },
+ function ({ aDesc, aAssertURLStartsWith }) {
+ ok(
+ content.document.location.href.startsWith(aAssertURLStartsWith),
+ aDesc
+ );
+ }
+ );
+ });
+}
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first", false]],
+ });
+
+ await runPrefTest(
+ "http://example.com",
+ "HTTPS-First disabled; Should not upgrade",
+ "http://"
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first", true]],
+ });
+
+ await runPrefTest(
+ "http://example.com",
+ "Should upgrade upgradeable website",
+ "https://"
+ );
+
+ await runPrefTest(
+ "http://httpsfirst.com",
+ "Should downgrade after error.",
+ "http://"
+ );
+
+ await runPrefTest(
+ "http://httpsfirst.com/?https://httpsfirst.com",
+ "Should downgrade after error and leave query params untouched.",
+ "http://httpsfirst.com/?https://httpsfirst.com"
+ );
+
+ await runPrefTest(
+ "http://domain.does.not.exist",
+ "Should not downgrade on dnsNotFound error.",
+ "https://"
+ );
+
+ await runPrefTest(
+ TIMEOUT_PAGE_URI_HTTP,
+ "Should downgrade after timeout.",
+ "http://"
+ );
+});
diff --git a/dom/security/test/https-first/browser_httpsfirst_console_logging.js b/dom/security/test/https-first/browser_httpsfirst_console_logging.js
new file mode 100644
index 0000000000..55f114ced1
--- /dev/null
+++ b/dom/security/test/https-first/browser_httpsfirst_console_logging.js
@@ -0,0 +1,71 @@
+// Bug 1658924 - HTTPS-First Mode - Tests for console logging
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1658924
+// This test makes sure that the various console messages from the HTTPS-First
+// mode get logged to the console.
+"use strict";
+
+// Test Cases
+// description: Description of what the subtests expects.
+// expectLogLevel: Expected log-level of a message.
+// expectIncludes: Expected substrings the message should contain.
+let tests = [
+ {
+ description: "Top-Level upgrade should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.warn,
+ expectIncludes: ["Upgrading insecure request", "to use", "httpsfirst.com"],
+ },
+ {
+ description: "Top-Level upgrade failure should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.warn,
+ expectIncludes: [
+ "Upgrading insecure request",
+ "failed",
+ "httpsfirst.com",
+ "Downgrading to",
+ ],
+ },
+];
+
+add_task(async function () {
+ // A longer timeout is necessary for this test than the plain mochitests
+ // due to opening a new tab with the web console.
+ requestLongerTimeout(4);
+
+ // Enable HTTPS-First Mode and register console-listener
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first", true]],
+ });
+ Services.console.registerListener(on_new_message);
+ // 1. Upgrade page to https://
+ await BrowserTestUtils.loadURIString(
+ gBrowser.selectedBrowser,
+ "http://httpsfirst.com"
+ );
+
+ await BrowserTestUtils.waitForCondition(() => tests.length === 0);
+
+ // Clean up
+ Services.console.unregisterListener(on_new_message);
+});
+
+function on_new_message(msgObj) {
+ const message = msgObj.message;
+ const logLevel = msgObj.logLevel;
+
+ if (message.includes("HTTPS-First Mode:")) {
+ for (let i = 0; i < tests.length; i++) {
+ const testCase = tests[i];
+ // Check if log-level matches
+ if (logLevel !== testCase.expectLogLevel) {
+ continue;
+ }
+ // Check if all substrings are included
+ if (testCase.expectIncludes.some(str => !message.includes(str))) {
+ continue;
+ }
+ ok(true, testCase.description);
+ tests.splice(i, 1);
+ break;
+ }
+ }
+}
diff --git a/dom/security/test/https-first/browser_httpsfirst_speculative_connect.js b/dom/security/test/https-first/browser_httpsfirst_speculative_connect.js
new file mode 100644
index 0000000000..9bf02e797d
--- /dev/null
+++ b/dom/security/test/https-first/browser_httpsfirst_speculative_connect.js
@@ -0,0 +1,69 @@
+"use strict";
+
+const TEST_PATH_HTTP = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+
+let console_messages = [
+ {
+ description: "Speculative Connection should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.warn,
+ expectIncludes: [
+ "Upgrading insecure speculative TCP connection",
+ "to use",
+ "example.com",
+ "file_httpsfirst_speculative_connect.html",
+ ],
+ },
+ {
+ description: "Upgrade should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.warn,
+ expectIncludes: [
+ "Upgrading insecure request",
+ "to use",
+ "example.com",
+ "file_httpsfirst_speculative_connect.html",
+ ],
+ },
+];
+
+function on_new_console_messages(msgObj) {
+ const message = msgObj.message;
+ const logLevel = msgObj.logLevel;
+
+ if (message.includes("HTTPS-First Mode:")) {
+ for (let i = 0; i < console_messages.length; i++) {
+ const testCase = console_messages[i];
+ // Check if log-level matches
+ if (logLevel !== testCase.expectLogLevel) {
+ continue;
+ }
+ // Check if all substrings are included
+ if (testCase.expectIncludes.some(str => !message.includes(str))) {
+ continue;
+ }
+ ok(true, testCase.description);
+ console_messages.splice(i, 1);
+ break;
+ }
+ }
+}
+
+add_task(async function () {
+ requestLongerTimeout(4);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first", true]],
+ });
+ Services.console.registerListener(on_new_console_messages);
+
+ await BrowserTestUtils.loadURIString(
+ gBrowser.selectedBrowser,
+ `${TEST_PATH_HTTP}file_httpsfirst_speculative_connect.html`
+ );
+
+ await BrowserTestUtils.waitForCondition(() => console_messages.length === 0);
+
+ Services.console.unregisterListener(on_new_console_messages);
+});
diff --git a/dom/security/test/https-first/browser_mixed_content_console.js b/dom/security/test/https-first/browser_mixed_content_console.js
new file mode 100644
index 0000000000..0b93850ff7
--- /dev/null
+++ b/dom/security/test/https-first/browser_mixed_content_console.js
@@ -0,0 +1,104 @@
+// Bug 1713593: HTTPS-First: Add test for mixed content blocker.
+"use strict";
+
+const testPath = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+
+const UPGRADE_DISPLAY_CONTENT =
+ "security.mixed_content.upgrade_display_content";
+
+let threeMessagesArrived = 0;
+let messageImageSeen = false;
+
+const kTestURI = testPath + "file_mixed_content_console.html";
+
+add_task(async function () {
+ // A longer timeout is necessary for this test than the plain mochitests
+ // due to opening a new tab with the web console.
+ requestLongerTimeout(4);
+
+ // Enable HTTPS-First Mode and register console-listener
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first", true]],
+ });
+ Services.console.registerListener(on_console_message);
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, kTestURI);
+
+ await BrowserTestUtils.waitForCondition(() => threeMessagesArrived === 3);
+
+ Services.console.unregisterListener(on_console_message);
+});
+
+function on_console_message(msgObj) {
+ const message = msgObj.message;
+
+ // The first console message is:
+ // "HTTPS-First Mode: Upgrading insecure request
+ // ‘http://example.com/browser/dom/security/test/https-first/file_mixed_content_console.html’ to use ‘https’"
+ if (message.includes("HTTPS-First Mode: Upgrading insecure request")) {
+ ok(message.includes("Upgrading insecure request"), "request got upgraded");
+ ok(
+ message.includes(
+ "“http://example.com/browser/dom/security/test/https-first/file_mixed_content_console.html” to use “https”."
+ ),
+ "correct top-level request"
+ );
+ threeMessagesArrived++;
+ }
+ // If security.mixed_content.upgrade_display_content is enabled:
+ // The second console message is about upgrading the insecure image
+ else if (
+ Services.prefs.getBoolPref(UPGRADE_DISPLAY_CONTENT) &&
+ message.includes("Mixed Content: Upgrading")
+ ) {
+ ok(
+ message.includes("insecure display request"),
+ "display content got load"
+ );
+ ok(
+ message.includes(
+ "‘http://example.com/browser/dom/security/test/https-first/auto_upgrading_identity.png’ to use ‘https’"
+ ),
+ "img loaded secure"
+ );
+ threeMessagesArrived++;
+ messageImageSeen = true;
+ }
+ // Else:
+ // The second console message is about blocking the image:
+ // Message: "Loading mixed (insecure) display content
+ // “http://example.com/browser/dom/security/test/https-first/auto_upgrading_identity.png” on a secure page".
+ // Since the message is send twice, prevent reading the image message two times
+ else if (message.includes("Loading mixed") && !messageImageSeen) {
+ ok(
+ message.includes("Loading mixed (insecure) display content"),
+ "display content got load"
+ );
+ ok(
+ message.includes(
+ "“http://example.com/browser/dom/security/test/https-first/auto_upgrading_identity.png” on a secure page"
+ ),
+ "img loaded insecure"
+ );
+ threeMessagesArrived++;
+ messageImageSeen = true;
+ }
+ // The third message is:
+ // "Blocked loading mixed active content
+ // "http://example.com/browser/dom/security/test/https-first/barfoo""
+ else if (message.includes("Blocked loading")) {
+ ok(
+ message.includes("Blocked loading mixed active content"),
+ "script got blocked"
+ );
+ ok(
+ message.includes(
+ "http://example.com/browser/dom/security/test/https-first/barfoo"
+ ),
+ "the right script got blocked"
+ );
+ threeMessagesArrived++;
+ }
+}
diff --git a/dom/security/test/https-first/browser_mixed_content_download.js b/dom/security/test/https-first/browser_mixed_content_download.js
new file mode 100644
index 0000000000..09ea64cea8
--- /dev/null
+++ b/dom/security/test/https-first/browser_mixed_content_download.js
@@ -0,0 +1,156 @@
+ChromeUtils.defineESModuleGetters(this, {
+ Downloads: "resource://gre/modules/Downloads.sys.mjs",
+ DownloadsCommon: "resource:///modules/DownloadsCommon.sys.mjs",
+});
+
+const HandlerService = Cc[
+ "@mozilla.org/uriloader/handler-service;1"
+].getService(Ci.nsIHandlerService);
+
+const MIMEService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+
+let SECURE_BASE_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "https://example.com/"
+ ) + "download_page.html";
+
+/**
+ * Waits until a download is triggered.
+ * It waits until a prompt is shown,
+ * saves and then accepts the dialog.
+ * @returns {Promise} Resolved once done.
+ */
+
+function shouldTriggerDownload() {
+ return new Promise((resolve, reject) => {
+ Services.wm.addListener({
+ onOpenWindow(xulWin) {
+ Services.wm.removeListener(this);
+ let win = xulWin.docShell.domWindow;
+ waitForFocus(() => {
+ if (
+ win.location ==
+ "chrome://mozapps/content/downloads/unknownContentType.xhtml"
+ ) {
+ let dialog = win.document.getElementById("unknownContentType");
+ let button = dialog.getButton("accept");
+ let actionRadio = win.document.getElementById("save");
+ actionRadio.click();
+ button.disabled = false;
+ dialog.acceptDialog();
+ resolve();
+ } else {
+ reject();
+ }
+ }, win);
+ },
+ });
+ });
+}
+
+const CONSOLE_UPGRADE_MESSAGE = "Upgrading insecure request";
+const CONSOLE_DOWNGRADE_MESSAGE = "Downgrading to “http” again.";
+const DOWNLOAD_URL =
+ "example.com/browser/dom/security/test/https-first/download_server.sjs";
+// Verifies that https-first tries to upgrade download,
+// falls back since download is not available via https
+let msgCounter = 0;
+function shouldConsoleError() {
+ return new Promise((resolve, reject) => {
+ function listener(msgObj) {
+ let text = msgObj.message;
+ if (text.includes(CONSOLE_UPGRADE_MESSAGE) && msgCounter == 0) {
+ ok(
+ text.includes("http://" + DOWNLOAD_URL),
+ "Https-first tries to upgrade download to https"
+ );
+ msgCounter++;
+ }
+ if (text.includes(CONSOLE_DOWNGRADE_MESSAGE) && msgCounter == 1) {
+ ok(
+ text.includes("https://" + DOWNLOAD_URL),
+ "Https-first downgrades download to http."
+ );
+ resolve();
+ Services.console.unregisterListener(listener);
+ }
+ }
+ Services.console.registerListener(listener);
+ });
+}
+
+async function resetDownloads() {
+ // Removes all downloads from the download List
+ const types = new Set();
+ let publicList = await Downloads.getList(Downloads.PUBLIC);
+ let downloads = await publicList.getAll();
+ for (let download of downloads) {
+ if (download.contentType) {
+ types.add(download.contentType);
+ }
+ publicList.remove(download);
+ await download.finalize(true);
+ }
+
+ if (types.size) {
+ // reset handlers for the contentTypes of any files previously downloaded
+ for (let type of types) {
+ const mimeInfo = MIMEService.getFromTypeAndExtension(type, "");
+ info("resetting handler for type: " + type);
+ HandlerService.remove(mimeInfo);
+ }
+ }
+}
+
+async function runTest(url, link, checkFunction, description) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.security.https_first", true],
+ ["browser.download.always_ask_before_handling_new_types", true],
+ ],
+ });
+ requestLongerTimeout(2);
+ await resetDownloads();
+
+ let tab = BrowserTestUtils.addTab(gBrowser, url);
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+ is(
+ gBrowser.currentURI.schemeIs("https"),
+ true,
+ "Scheme of opened tab should be https"
+ );
+ info("Checking: " + description);
+
+ let checkPromise = checkFunction();
+ // Click the Link to trigger the download
+ SpecialPowers.spawn(gBrowser.selectedBrowser, [link], contentLink => {
+ content.document.getElementById(contentLink).click();
+ });
+ await checkPromise;
+ ok(true, description);
+ BrowserTestUtils.removeTab(tab);
+}
+
+//Test description:
+// 1. Open "https://example.com"
+// 2. From "https://example.com" download something, but that download is only available via http.
+// 3. Https-first tries to upgrade the download.
+// 4. Upgrading fails - so http-first downgrade download to http.
+
+add_task(async function test_mixed_download() {
+ await runTest(
+ SECURE_BASE_URL,
+ "insecure",
+ () => Promise.all([shouldTriggerDownload(), shouldConsoleError()]),
+ "Secure -> Insecure should Error"
+ );
+ // remove downloaded file
+ let downloadsPromise = Downloads.getList(Downloads.PUBLIC);
+ let downloadList = await downloadsPromise;
+ let [download] = downloadList._downloads;
+ await downloadList.remove(download);
+});
diff --git a/dom/security/test/https-first/browser_navigation.js b/dom/security/test/https-first/browser_navigation.js
new file mode 100644
index 0000000000..b8e81f76bb
--- /dev/null
+++ b/dom/security/test/https-first/browser_navigation.js
@@ -0,0 +1,97 @@
+"use strict";
+
+const REQUEST_URL =
+ "http://httpsfirst.com/browser/dom/security/test/https-first/";
+
+async function promiseGetHistoryIndex(browser) {
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ return SpecialPowers.spawn(browser, [], function () {
+ let shistory =
+ docShell.browsingContext.childSessionHistory.legacySHistory;
+ return shistory.index;
+ });
+ }
+
+ let shistory = browser.browsingContext.sessionHistory;
+ return shistory.index;
+}
+
+async function testNavigations() {
+ // Load initial site
+
+ let url1 = REQUEST_URL + "file_navigation.html?foo1";
+ let url2 = REQUEST_URL + "file_navigation.html?foo2";
+ let url3 = REQUEST_URL + "file_navigation.html?foo3";
+
+ let loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ BrowserTestUtils.loadURIString(gBrowser, url1);
+ await loaded;
+
+ // Load another site
+ loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [url2],
+ async url => (content.location.href = url)
+ );
+ await loaded;
+
+ // Load another site
+ loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [url3],
+ async url => (content.location.href = url)
+ );
+ await loaded;
+ is(
+ await promiseGetHistoryIndex(gBrowser.selectedBrowser),
+ 2,
+ "correct session history index after load 3"
+ );
+
+ // Go back one site by clicking the back button
+ info("Clicking back button");
+ loaded = BrowserTestUtils.waitForLocationChange(gBrowser, url2);
+ let backButton = document.getElementById("back-button");
+ backButton.click();
+ await loaded;
+ is(
+ await promiseGetHistoryIndex(gBrowser.selectedBrowser),
+ 1,
+ "correct session history index after going back for the first time"
+ );
+
+ // Go back again
+ info("Clicking back button again");
+ loaded = BrowserTestUtils.waitForLocationChange(gBrowser, url1);
+ backButton.click();
+ await loaded;
+ is(
+ await promiseGetHistoryIndex(gBrowser.selectedBrowser),
+ 0,
+ "correct session history index after going back for the second time"
+ );
+}
+
+add_task(async function () {
+ waitForExplicitFinish();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first", true]],
+ });
+
+ await testNavigations();
+
+ // If fission is enabled we also want to test the navigations with the bfcache
+ // in the parent.
+ if (SpecialPowers.getBoolPref("fission.autostart")) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["fission.bfcacheInParent", true]],
+ });
+
+ await testNavigations();
+ }
+
+ finish();
+});
diff --git a/dom/security/test/https-first/browser_slow_download.js b/dom/security/test/https-first/browser_slow_download.js
new file mode 100644
index 0000000000..82d7a99b07
--- /dev/null
+++ b/dom/security/test/https-first/browser_slow_download.js
@@ -0,0 +1,158 @@
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+});
+// Create a uri for an https site
+const testPath = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+const TEST_URI = testPath + "file_slow_download.html";
+const EXPECTED_DOWNLOAD_URL =
+ "example.com/browser/dom/security/test/https-first/file_slow_download.sjs";
+
+// Since the server send the complete download file after 3 seconds we need an longer timeout
+requestLongerTimeout(4);
+
+function promisePanelOpened() {
+ if (DownloadsPanel.panel && DownloadsPanel.panel.state == "open") {
+ return Promise.resolve();
+ }
+ return BrowserTestUtils.waitForEvent(DownloadsPanel.panel, "popupshown");
+}
+
+/**
+ * Waits for a download to finish, in case it has not finished already.
+ *
+ * @param aDownload
+ * The Download object to wait upon.
+ *
+ * @return {Promise}
+ * @resolves When the download has finished successfully.
+ * @rejects JavaScript exception if the download failed.
+ */
+function promiseDownloadStopped(aDownload) {
+ if (!aDownload.stopped) {
+ // The download is in progress, wait for the current attempt to finish and
+ // report any errors that may occur.
+ return aDownload.start();
+ }
+
+ if (aDownload.succeeded) {
+ return Promise.resolve();
+ }
+
+ // The download failed or was canceled.
+ return Promise.reject(aDownload.error || new Error("Download canceled."));
+}
+
+// Verifys that no background request was send
+let requestCounter = 0;
+function examiner() {
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request");
+}
+
+examiner.prototype = {
+ observe(subject, topic, data) {
+ if (topic !== "specialpowers-http-notify-request") {
+ return;
+ }
+ // On Android we have other requests appear here as well. Let's make
+ // sure we only evaluate requests triggered by the test.
+ if (
+ !data.startsWith("http://example.com") &&
+ !data.startsWith("https://example.com")
+ ) {
+ return;
+ }
+ ++requestCounter;
+ if (requestCounter == 1) {
+ is(data, TEST_URI, "Download start page is https");
+ return;
+ }
+ if (requestCounter == 2) {
+ // The specialpowers-http-notify-request fires before the internal redirect( /upgrade) to
+ // https happens.
+ is(
+ data,
+ "http://" + EXPECTED_DOWNLOAD_URL,
+ "First download request is http (internal)"
+ );
+ return;
+ }
+ if (requestCounter == 3) {
+ is(
+ data,
+ "https://" + EXPECTED_DOWNLOAD_URL,
+ "Download got upgraded to https"
+ );
+ return;
+ }
+ ok(false, "we should never get here, but just in case");
+ },
+ remove() {
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ },
+};
+
+// Test description:
+// 1. Open https://example.com
+// 2. Start download - location of download is http
+// 3. https-first upgrades to https
+// 4. Server send first part of download and after 3 seconds the rest
+// 5. Complete download of text file
+add_task(async function test_slow_download() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first", true]],
+ });
+
+ // remove all previous downloads
+ let downloadsList = await Downloads.getList(Downloads.PUBLIC);
+ await downloadsList.removeFinished();
+
+ // add observer to ensure that the background request gets canceled for the upgraded Download
+ this.examiner = new examiner();
+
+ let downloadsPanelPromise = promisePanelOpened();
+ let downloadsPromise = Downloads.getList(Downloads.PUBLIC);
+ BrowserTestUtils.loadURIString(gBrowser, TEST_URI);
+ // wait for downloadsPanel to open before continuing with test
+ await downloadsPanelPromise;
+ let downloadList = await downloadsPromise;
+ is(DownloadsPanel.isPanelShowing, true, "DownloadsPanel should be open.");
+ is(downloadList._downloads.length, 1, "File should be downloaded.");
+ let [download] = downloadList._downloads;
+ // wait for download to finish (with success or error)
+ await promiseDownloadStopped(download);
+ is(download.contentType, "text/plain", "File contentType should be correct.");
+ // ensure https-first did upgrade the scheme.
+ is(
+ download.source.url,
+ "https://" + EXPECTED_DOWNLOAD_URL,
+ "Scheme should be https."
+ );
+ // ensure that no background request was send
+ is(
+ requestCounter,
+ 3,
+ "three requests total (download page, download http, download https/ upgraded)"
+ );
+ // ensure that downloaded is complete
+ is(download.target.size, 25, "Download size is correct");
+ //clean up
+ this.examiner.remove();
+ info("cleaning up downloads");
+ try {
+ if (Services.appinfo.OS === "WINNT") {
+ // We need to make the file writable to delete it on Windows.
+ await IOUtils.setPermissions(download.target.path, 0o600);
+ }
+ await IOUtils.remove(download.target.path);
+ } catch (error) {
+ info("The file " + download.target.path + " is not removed, " + error);
+ }
+
+ await downloadList.remove(download);
+ await download.finalize();
+});
diff --git a/dom/security/test/https-first/browser_upgrade_onion.js b/dom/security/test/https-first/browser_upgrade_onion.js
new file mode 100644
index 0000000000..a6a6a85412
--- /dev/null
+++ b/dom/security/test/https-first/browser_upgrade_onion.js
@@ -0,0 +1,60 @@
+// This test ensures that various configurable upgrade exceptions work
+"use strict";
+
+async function runTest(desc, url, expectedURI) {
+ await BrowserTestUtils.withNewTab("about:blank", async function (browser) {
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, null, true);
+ BrowserTestUtils.loadURIString(browser, url);
+ await loaded;
+
+ await SpecialPowers.spawn(
+ browser,
+ [desc, expectedURI],
+ async function (desc, expectedURI) {
+ // XXX ckerschb: generally we use the documentURI, but our test infra
+ // can not handle .onion, hence we use the URI of the failed channel
+ // stored on the docshell to see if the scheme was upgraded to https.
+ let loadedURI = content.document.documentURI;
+ if (loadedURI.startsWith("about:neterror")) {
+ loadedURI = content.docShell.failedChannel.URI.spec;
+ }
+ is(loadedURI, expectedURI, desc);
+ }
+ );
+ });
+}
+
+// by default local addresses and .onion should *not* get upgraded
+add_task(async function () {
+ requestLongerTimeout(2);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.security.https_first", true],
+ ["dom.security.https_only_mode", false],
+ ["dom.security.https_only_mode.upgrade_local", false],
+ ["dom.security.https_only_mode.upgrade_onion", false],
+ ],
+ });
+
+ await runTest(
+ "Hosts ending with .onion should be be exempt from HTTPS-First upgrades by default",
+ "http://grocery.shopping.for.one.onion/",
+ "http://grocery.shopping.for.one.onion/"
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.security.https_first", true],
+ ["dom.security.https_only_mode", false],
+ ["dom.security.https_only_mode.upgrade_local", false],
+ ["dom.security.https_only_mode.upgrade_onion", true],
+ ],
+ });
+
+ await runTest(
+ "Hosts ending with .onion should get upgraded when 'dom.security.https_only_mode.upgrade_onion' is set to true",
+ "http://grocery.shopping.for.one.onion/",
+ "https://grocery.shopping.for.one.onion/"
+ );
+});
diff --git a/dom/security/test/https-first/download_page.html b/dom/security/test/https-first/download_page.html
new file mode 100644
index 0000000000..a828ee07db
--- /dev/null
+++ b/dom/security/test/https-first/download_page.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test mixed content download by https-first</title>
+ </head>
+ <body>
+ hi
+
+ <script>
+ const host = window.location.host;
+ const path = location.pathname.replace("download_page.html","download_server.sjs");
+
+ const insecureLink = document.createElement("a");
+ insecureLink.href=`http://${host}${path}`;
+ insecureLink.download="true";
+ insecureLink.id="insecure";
+ insecureLink.textContent="Not secure Link";
+
+ document.body.append(insecureLink);
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/https-first/download_server.sjs b/dom/security/test/https-first/download_server.sjs
new file mode 100644
index 0000000000..7af5722e7b
--- /dev/null
+++ b/dom/security/test/https-first/download_server.sjs
@@ -0,0 +1,16 @@
+function handleRequest(request, response) {
+ // Only answer to http, in case request is in https let the reply time out.
+ if (request.scheme === "https") {
+ response.processAsync();
+ return;
+ }
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ // Send some file, e.g. an image
+ response.setHeader(
+ "Content-Disposition",
+ "attachment; filename=some-file.png"
+ );
+ response.setHeader("Content-Type", "image/png");
+ response.write("Success!");
+}
diff --git a/dom/security/test/https-first/file_bad_cert.sjs b/dom/security/test/https-first/file_bad_cert.sjs
new file mode 100644
index 0000000000..1a8ae08a86
--- /dev/null
+++ b/dom/security/test/https-first/file_bad_cert.sjs
@@ -0,0 +1,34 @@
+const RESPONSE_SUCCESS = `
+ <html>
+ <body>
+ send message, downgraded
+ <script type="application/javascript">
+ let scheme = document.location.protocol;
+ window.opener.postMessage({result: 'downgraded', scheme: scheme}, '*');
+ </script>
+ </body>
+ </html>`;
+
+const RESPONSE_UNEXPECTED = `
+ <html>
+ <body>
+ send message, error
+ <script type="application/javascript">
+ let scheme = document.location.protocol;
+ window.opener.postMessage({result: 'Error', scheme: scheme}, '*');
+ </script>
+ </body>
+ </html>`;
+
+function handleRequest(request, response) {
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // if the received request is not http send an error
+ if (request.scheme === "http") {
+ response.write(RESPONSE_SUCCESS);
+ return;
+ }
+ // we should never get here; just in case, return something unexpected
+ response.write(RESPONSE_UNEXPECTED);
+}
diff --git a/dom/security/test/https-first/file_beforeunload_permit_http.html b/dom/security/test/https-first/file_beforeunload_permit_http.html
new file mode 100644
index 0000000000..50459d6006
--- /dev/null
+++ b/dom/security/test/https-first/file_beforeunload_permit_http.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html><html>
+<body>
+ <script>
+ window.onbeforeunload = function() {
+ return "stop";
+ }
+ </script>
+</body>
+</html>
diff --git a/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs b/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs
new file mode 100644
index 0000000000..eb64c59e97
--- /dev/null
+++ b/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs
@@ -0,0 +1,61 @@
+"use strict";
+
+const REDIRECT_URI =
+ "http://example.com/tests/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs?verify";
+const DOWNGRADE_URI =
+ "http://example.com/tests/dom/security/test/https-first/file_downgrade_with_different_path.sjs";
+const RESPONSE_ERROR = "unexpected-query";
+
+// An onload postmessage to window opener
+const RESPONSE_HTTPS_SCHEME = `
+ <html>
+ <body>
+ <script type="application/javascript">
+ window.opener.postMessage({result: 'scheme-https'}, '*');
+ </script>
+ </body>
+ </html>`;
+
+const RESPONSE_HTTP_SCHEME = `
+ <html>
+ <body>
+ <script type="application/javascript">
+ window.opener.postMessage({result: 'scheme-http'}, '*');
+ </script>
+ </body>
+ </html>`;
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ const query = request.queryString;
+
+ if (query == "downgrade") {
+ // send same-origin downgrade from https: to http: with a different path.
+ // we don't consider it's an endless upgrade downgrade loop in this case.
+ response.setStatusLine(request.httpVersion, 302, "Found");
+ response.setHeader("Location", DOWNGRADE_URI, false);
+ return;
+ }
+
+ // handle the redirect case
+ if ((query >= 301 && query <= 303) || query == 307) {
+ // send same-origin downgrade from https: to http: again simluating
+ // and endless upgrade downgrade loop.
+ response.setStatusLine(request.httpVersion, query, "Found");
+ response.setHeader("Location", REDIRECT_URI, false);
+ return;
+ }
+
+ // Check if scheme is http:// or https://
+ if (query == "verify") {
+ let response_content =
+ request.scheme === "https" ? RESPONSE_HTTPS_SCHEME : RESPONSE_HTTP_SCHEME;
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(response_content);
+ return;
+ }
+
+ // We should never get here, but just in case ...
+ response.setStatusLine(request.httpVersion, 500, "OK");
+ response.write("unexepcted query");
+}
diff --git a/dom/security/test/https-first/file_data_uri.html b/dom/security/test/https-first/file_data_uri.html
new file mode 100644
index 0000000000..69133e5079
--- /dev/null
+++ b/dom/security/test/https-first/file_data_uri.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1709069: Test that Data URI which makes a top-level request gets updated in https-first</title>
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+ window.onload = (event) => {
+ let myLoc = window.location.href;
+ window.opener.parent.postMessage({location: myLoc}, "*");
+ window.close();
+};
+</script>
+</body>
+</html>
diff --git a/dom/security/test/https-first/file_downgrade_500_responses.sjs b/dom/security/test/https-first/file_downgrade_500_responses.sjs
new file mode 100644
index 0000000000..b3cfbd79dd
--- /dev/null
+++ b/dom/security/test/https-first/file_downgrade_500_responses.sjs
@@ -0,0 +1,70 @@
+// Custom *.sjs file specifically for the needs of Bug 1709552
+"use strict";
+
+const RESPONSE_SUCCESS = `
+ <html>
+ <body>
+ send message, downgraded
+ <script type="application/javascript">
+ let scheme = document.location.protocol;
+ window.opener.postMessage({result: 'downgraded', scheme: scheme}, '*');
+ </script>
+ </body>
+ </html>`;
+
+const RESPONSE_UNEXPECTED = `
+ <html>
+ <body>
+ send message, error
+ <script type="application/javascript">
+ let scheme = document.location.protocol;
+ window.opener.postMessage({result: 'Error', scheme: scheme}, '*');
+ </script>
+ </body>
+ </html>`;
+
+function handleRequest(request, response) {
+ // avoid confusing cache behaviour
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+
+ let query = request.queryString;
+ // if the scheme is not https and it is the initial request
+ // then we rather fall through and display unexpected content
+ if (request.scheme === "https") {
+ if (query === "test1a") {
+ response.setStatusLine("1.1", 501, "Not Implemented");
+ response.write("Not Implemented\n");
+ return;
+ }
+
+ if (query === "test2a") {
+ response.setStatusLine("1.1", 504, "Gateway Timeout");
+ response.write("Gateway Timeout\n");
+ return;
+ }
+
+ if (query === "test3a") {
+ response.setStatusLine("1.1", 521, "Web Server Is Down");
+ response.write("Web Server Is Down\n");
+ return;
+ }
+ if (query === "test4a") {
+ response.setStatusLine("1.1", 530, "Railgun Error");
+ response.write("Railgun Error\n");
+ return;
+ }
+ if (query === "test5a") {
+ response.setStatusLine("1.1", 560, "Unauthorized");
+ response.write("Unauthorized\n");
+ return;
+ }
+
+ // We should never arrive here, just in case send something unexpected
+ response.write(RESPONSE_UNEXPECTED);
+ return;
+ }
+
+ // We should arrive here when the redirection was downraded successful
+ response.write(RESPONSE_SUCCESS);
+}
diff --git a/dom/security/test/https-first/file_downgrade_bad_responses.sjs b/dom/security/test/https-first/file_downgrade_bad_responses.sjs
new file mode 100644
index 0000000000..1a6eea2dd1
--- /dev/null
+++ b/dom/security/test/https-first/file_downgrade_bad_responses.sjs
@@ -0,0 +1,73 @@
+// Custom *.sjs file specifically for the needs of Bug 1709552
+"use strict";
+
+const RESPONSE_SUCCESS = `
+ <html>
+ <body>
+ send message, downgraded
+ <script type="application/javascript">
+ window.opener.postMessage({result: 'downgraded', scheme: 'http'}, '*');
+ </script>
+ </body>
+ </html>`;
+
+const RESPONSE_UNEXPECTED = `
+ <html>
+ <body>
+ send message, error
+ <script type="application/javascript">
+ window.opener.postMessage({result: 'Error', scheme: 'http'}, '*');
+ </script>
+ </body>
+ </html>`;
+
+function handleRequest(request, response) {
+ // avoid confusing cache behaviour
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+
+ let query = request.queryString;
+ // if the scheme is not https and it is the initial request
+ // then we rather fall through and display unexpected content
+ if (request.scheme === "https") {
+ if (query === "test1a") {
+ response.setStatusLine("1.1", 400, "Bad Request");
+ response.write("Bad Request\n");
+ return;
+ }
+
+ if (query === "test2a") {
+ response.setStatusLine("1.1", 403, "Forbidden");
+ response.write("Forbidden\n");
+ return;
+ }
+
+ if (query === "test3a") {
+ response.setStatusLine("1.1", 404, "Not Found");
+ response.write("Not Found\n");
+ return;
+ }
+ if (query === "test4a") {
+ response.setStatusLine("1.1", 416, "Requested Range Not Satisfiable");
+ response.write("Requested Range Not Satisfiable\n");
+ return;
+ }
+ if (query === "test5a") {
+ response.setStatusLine("1.1", 418, "I'm a teapot");
+ response.write("I'm a teapot\n");
+ return;
+ }
+ if (query == "test6a") {
+ // Simulating a timeout by processing the https request
+ response.processAsync();
+ return;
+ }
+
+ // We should never arrive here, just in case send something unexpected
+ response.write(RESPONSE_UNEXPECTED);
+ return;
+ }
+
+ // We should arrive here when the redirection was downraded successful
+ response.write(RESPONSE_SUCCESS);
+}
diff --git a/dom/security/test/https-first/file_downgrade_request_upgrade_request.sjs b/dom/security/test/https-first/file_downgrade_request_upgrade_request.sjs
new file mode 100644
index 0000000000..6004d57eaf
--- /dev/null
+++ b/dom/security/test/https-first/file_downgrade_request_upgrade_request.sjs
@@ -0,0 +1,52 @@
+// Custom *.sjs file specifically for the needs of Bug 1706126
+"use strict";
+// subdomain of example.com
+const REDIRECT_302 =
+ "http://www.redirect-example.com/tests/dom/security/test/https-first/file_downgrade_request_upgrade_request.sjs";
+
+const RESPONSE_SUCCESS = `
+ <html>
+ <body>
+ send message, upgraded
+ <script type="application/javascript">
+ let scheme = document.location.protocol;
+ window.opener.postMessage({result: 'upgraded', scheme: scheme}, '*');
+ </script>
+ </body>
+ </html>`;
+
+const RESPONSE_UNEXPECTED = `
+ <html>
+ <body>
+ send message, error
+ <script type="application/javascript">
+ let scheme = document.location.protocol;
+ window.opener.postMessage({result: 'error', scheme: scheme}, '*');
+ </script>
+ </body>
+ </html>`;
+
+function handleRequest(request, response) {
+ // avoid confusing cache behaviour
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+
+ // if the scheme is https and it is the initial request time it out
+ if (request.scheme === "https" && request.host === "redirect-example.com") {
+ // Simulating a timeout by processing the https request
+ response.processAsync();
+ return;
+ }
+ if (request.scheme === "http" && request.host === "redirect-example.com") {
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", REDIRECT_302, false);
+ return;
+ }
+ // if the request was sent to subdomain
+ if (request.host.startsWith("www.")) {
+ response.write(RESPONSE_SUCCESS);
+ return;
+ }
+ // We should never arrive here, just in case send 'error'
+ response.write(RESPONSE_UNEXPECTED);
+}
diff --git a/dom/security/test/https-first/file_downgrade_view_source.sjs b/dom/security/test/https-first/file_downgrade_view_source.sjs
new file mode 100644
index 0000000000..c57dd0deb8
--- /dev/null
+++ b/dom/security/test/https-first/file_downgrade_view_source.sjs
@@ -0,0 +1,30 @@
+"use strict";
+
+function handleRequest(request, response) {
+ // avoid confusing cache behaviour
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+
+ let query = request.queryString.split("&");
+ let scheme = request.scheme;
+
+ if (scheme === "https") {
+ if (query.includes("downgrade")) {
+ response.setStatusLine("1.1", 400, "Bad Request");
+ response.write("Bad Request\n");
+ return;
+ }
+ if (query.includes("upgrade")) {
+ response.write("view-source:https://");
+ return;
+ }
+ }
+
+ if (scheme === "http" && query.includes("downgrade")) {
+ response.write("view-source:http://");
+ return;
+ }
+
+ // We should arrive here when the redirection was downraded successful
+ response.write("unexpected scheme and query given");
+}
diff --git a/dom/security/test/https-first/file_downgrade_with_different_path.sjs b/dom/security/test/https-first/file_downgrade_with_different_path.sjs
new file mode 100644
index 0000000000..7450313d98
--- /dev/null
+++ b/dom/security/test/https-first/file_downgrade_with_different_path.sjs
@@ -0,0 +1,27 @@
+"use strict";
+
+// An onload postmessage to window opener
+const RESPONSE_HTTPS_SCHEME = `
+ <html>
+ <body>
+ <script type="application/javascript">
+ window.opener.postMessage({result: 'scheme-https'}, '*');
+ </script>
+ </body>
+ </html>`;
+
+const RESPONSE_HTTP_SCHEME = `
+ <html>
+ <body>
+ <script type="application/javascript">
+ window.opener.postMessage({result: 'scheme-http'}, '*');
+ </script>
+ </body>
+ </html>`;
+
+function handleRequest(request, response) {
+ let response_content =
+ request.scheme === "https" ? RESPONSE_HTTPS_SCHEME : RESPONSE_HTTP_SCHEME;
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(response_content);
+}
diff --git a/dom/security/test/https-first/file_download_attribute.html b/dom/security/test/https-first/file_download_attribute.html
new file mode 100644
index 0000000000..453bf408b3
--- /dev/null
+++ b/dom/security/test/https-first/file_download_attribute.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test download attribute for http site</title>
+</head>
+<body>
+ <a href="http://nocert.example.com/browser/dom/security/test/https-first/file_download_attribute.sjs" download="some.html" id="testlink">download by attribute</a>
+ <script>
+ // click the link to start download
+ let testlink = document.getElementById("testlink");
+ testlink.click();
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/https-first/file_download_attribute.sjs b/dom/security/test/https-first/file_download_attribute.sjs
new file mode 100644
index 0000000000..8941da1a41
--- /dev/null
+++ b/dom/security/test/https-first/file_download_attribute.sjs
@@ -0,0 +1,12 @@
+function handleRequest(request, response) {
+ // Only answer to http, in case request is in https let the reply time out.
+ if (request.scheme === "https") {
+ response.processAsync();
+ return;
+ }
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Disposition", "attachment; filename=some.html");
+ response.setHeader("Content-Type", "text/html");
+ response.write("success!");
+}
diff --git a/dom/security/test/https-first/file_form_submission.sjs b/dom/security/test/https-first/file_form_submission.sjs
new file mode 100644
index 0000000000..63b248d773
--- /dev/null
+++ b/dom/security/test/https-first/file_form_submission.sjs
@@ -0,0 +1,84 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+const RESPONSE_SUCCESS = `
+ <html>
+ <body>
+ send message, downgraded
+ <script type="application/javascript">
+ let scheme = document.location.protocol;
+ const loc = document.location.href;
+ window.opener.postMessage({location: loc, scheme: scheme, form:"test=success" }, '*');
+ </script>
+ </body>
+ </html>`;
+
+const POST_FORMULAR = `
+<html>
+ <body>
+ <form action="http://example.com/tests/dom/security/test/https-first/file_form_submission.sjs?" method="POST" id="POSTForm">
+ <div>
+ <label id="submit">Submit</label>
+ <input name="test" id="form" value="success">
+ </div>
+ </form>
+ <script class="testbody" type="text/javascript">
+ document.getElementById("POSTForm").submit();
+ </script>
+ </body>
+</html>
+`;
+
+function handleRequest(request, response) {
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ let queryString = request.queryString;
+ if (request.scheme === "https" && queryString === "test=1") {
+ response.write(RESPONSE_SUCCESS);
+ return;
+ }
+ if (
+ request.scheme === "https" &&
+ (queryString === "test=2" || queryString === "test=4")
+ ) {
+ // time out request
+ response.processAsync();
+ return;
+ }
+ if (request.scheme === "http" && queryString === "test=2") {
+ response.write(RESPONSE_SUCCESS);
+ return;
+ }
+ if (queryString === "test=3" || queryString === "test=4") {
+ // send post form
+ response.write(POST_FORMULAR);
+ return;
+ }
+ if (request.method == "POST") {
+ // extract form parameters
+ let body = new BinaryInputStream(request.bodyInputStream);
+ let avail;
+ let bytes = [];
+ while ((avail = body.available()) > 0) {
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+ }
+ let requestBodyContents = String.fromCharCode.apply(null, bytes);
+
+ response.write(`
+ <html>
+ <script type="application/javascript">
+ let scheme = document.location.protocol;
+ const loc = document.location.href;
+ window.opener.postMessage({location: loc, scheme: scheme, form: '${requestBodyContents}'}, '*');
+ </script>
+ </html>`);
+
+ return;
+ }
+ // we should never get here; just in case, return something unexpected
+ response.write("do'h");
+}
diff --git a/dom/security/test/https-first/file_fragment.html b/dom/security/test/https-first/file_fragment.html
new file mode 100644
index 0000000000..5846d6d977
--- /dev/null
+++ b/dom/security/test/https-first/file_fragment.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<script>
+
+function beforeunload(){
+ window.opener.postMessage({
+ info: "before-unload",
+ result: window.location.hash,
+ button: false,
+ }, "*");
+}
+
+window.onload = function (){
+ let button = window.document.getElementById("clickMeButton");
+ let buttonExist = button !== null;
+ window.opener.postMessage({
+ info: "onload",
+ result: window.location.href,
+ button: buttonExist,
+ }, "*");
+ button.click();
+
+}
+
+// after button clicked and paged scrolled sends URL of current window
+window.onscroll = function(){
+ window.opener.postMessage({
+ info: "scrolled-to-foo",
+ result: window.location.href,
+ button: true,
+ documentURI: document.documentURI,
+ }, "*");
+ }
+
+
+</script>
+<body onbeforeunload="/*just to notify if we load a new page*/ beforeunload()";>
+ <a id="clickMeButton" href="http://example.com/tests/dom/security/test/https-first/file_fragment.html#foo">Click me</a>
+ <div style="height: 1000px; border: 1px solid black;"> space</div>
+ <a name="foo" href="http://example.com/tests/dom/security/test/https-first/file_fragment.html">foo</a>
+ <div style="height: 1000px; border: 1px solid black;">space</div>
+</body>
+</html>
diff --git a/dom/security/test/https-first/file_httpsfirst_speculative_connect.html b/dom/security/test/https-first/file_httpsfirst_speculative_connect.html
new file mode 100644
index 0000000000..6542884191
--- /dev/null
+++ b/dom/security/test/https-first/file_httpsfirst_speculative_connect.html
@@ -0,0 +1 @@
+<html><body>dummy file for speculative https-first upgrade test</body></html>
diff --git a/dom/security/test/https-first/file_httpsfirst_timeout_server.sjs b/dom/security/test/https-first/file_httpsfirst_timeout_server.sjs
new file mode 100644
index 0000000000..81c4c0328b
--- /dev/null
+++ b/dom/security/test/https-first/file_httpsfirst_timeout_server.sjs
@@ -0,0 +1,13 @@
+function handleRequest(request, response) {
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ if (request.scheme === "https") {
+ // Simulating a timeout by processing the https request
+ // async and *never* return anything!
+ response.processAsync();
+ return;
+ }
+ // we should never get here; just in case, return something unexpected
+ response.write("do'h");
+}
diff --git a/dom/security/test/https-first/file_mixed_content_auto_upgrade.html b/dom/security/test/https-first/file_mixed_content_auto_upgrade.html
new file mode 100644
index 0000000000..7dda8909a5
--- /dev/null
+++ b/dom/security/test/https-first/file_mixed_content_auto_upgrade.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1673574 - Improve Console logging for mixed content auto upgrading</title>
+</head>
+<body>
+ <!--upgradeable resources--->
+ <img src="http://example.com/browser/dom/security/test/https-first/pass.png">
+ <video src="http://example.com/browser/dom/security/test/https-first/test.ogv">
+ <audio src="http://example.com/browser/dom/security/test/https-first/test.wav">
+</body>
+</html>
diff --git a/dom/security/test/https-first/file_mixed_content_console.html b/dom/security/test/https-first/file_mixed_content_console.html
new file mode 100644
index 0000000000..631ac0b40f
--- /dev/null
+++ b/dom/security/test/https-first/file_mixed_content_console.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1713593: HTTPS-First: Add test for mixed content blocker.</title>
+</head>
+<body>
+ <!-- Test that image gets loaded (insecure) - the *.png does not actually exist because we only care for the console message to appear-->
+ <img type="image/png" id="testimage" src="http://example.com/browser/dom/security/test/https-first/auto_upgrading_identity.png" />
+ <!-- Test that the script gets blocked. The script does not actually exist because we only care for the console message to appear-->
+ <script type="text/javascript" src="http://example.com/browser/dom/security/test/https-first/barfoo" >
+</script>
+</body>
+</html>
diff --git a/dom/security/test/https-first/file_multiple_redirection.sjs b/dom/security/test/https-first/file_multiple_redirection.sjs
new file mode 100644
index 0000000000..49098ccdb7
--- /dev/null
+++ b/dom/security/test/https-first/file_multiple_redirection.sjs
@@ -0,0 +1,87 @@
+"use strict";
+
+// redirection uri
+const REDIRECT_URI =
+ "https://example.com/tests/dom/security/test/https-first/file_multiple_redirection.sjs?redirect";
+const REDIRECT_URI_HTTP =
+ "http://example.com/tests/dom/security/test/https-first/file_multiple_redirection.sjs?verify";
+const REDIRECT_URI_HTTPS =
+ "https://example.com/tests/dom/security/test/https-first/file_multiple_redirection.sjs?verify";
+
+const RESPONSE_ERROR = "unexpected-query";
+
+// An onload postmessage to window opener
+const RESPONSE_HTTPS_SCHEME = `
+ <html>
+ <body>
+ <script type="application/javascript">
+ window.opener.postMessage({result: 'scheme-https'}, '*');
+ </script>
+ </body>
+ </html>`;
+
+const RESPONSE_HTTP_SCHEME = `
+ <html>
+ <body>
+ <script type="application/javascript">
+ window.opener.postMessage({result: 'scheme-http'}, '*');
+ </script>
+ </body>
+ </html>`;
+
+function sendRedirection(query, response) {
+ // send a redirection to an http uri
+ if (query.includes("test1")) {
+ response.setHeader("Location", REDIRECT_URI_HTTP, false);
+ return;
+ }
+ // send a redirection to an https uri
+ if (query.includes("test2")) {
+ response.setHeader("Location", REDIRECT_URI_HTTPS, false);
+ return;
+ }
+ // send a redirection to an http uri with hsts header
+ if (query.includes("test3")) {
+ response.setHeader("Strict-Transport-Security", "max-age=60");
+ response.setHeader("Location", REDIRECT_URI_HTTP, false);
+ }
+}
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ const query = request.queryString;
+
+ // if the query contains a test query start first test
+ if (query.startsWith("test")) {
+ // send a 302 redirection
+ response.setStatusLine(request.httpVersion, 302, "Found");
+ response.setHeader("Location", REDIRECT_URI + query, false);
+ return;
+ }
+ // Send a redirection
+ if (query.includes("redirect")) {
+ response.setStatusLine(request.httpVersion, 302, "Found");
+ sendRedirection(query, response);
+ return;
+ }
+ // Reset the HSTS policy, prevent influencing other tests
+ if (request.queryString === "reset") {
+ response.setHeader("Strict-Transport-Security", "max-age=0");
+ let response_content =
+ request.scheme === "https" ? RESPONSE_HTTPS_SCHEME : RESPONSE_HTTP_SCHEME;
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(response_content);
+ }
+ // Check if scheme is http:// or https://
+ if (query == "verify") {
+ let response_content =
+ request.scheme === "https" ? RESPONSE_HTTPS_SCHEME : RESPONSE_HTTP_SCHEME;
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(response_content);
+ return;
+ }
+
+ // We should never get here, but just in case ...
+ response.setStatusLine(request.httpVersion, 500, "OK");
+ response.write("unexepcted query");
+}
diff --git a/dom/security/test/https-first/file_navigation.html b/dom/security/test/https-first/file_navigation.html
new file mode 100644
index 0000000000..02d366291b
--- /dev/null
+++ b/dom/security/test/https-first/file_navigation.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <p>Blank page</p>
+ <body>
+</html>
diff --git a/dom/security/test/https-first/file_redirect.sjs b/dom/security/test/https-first/file_redirect.sjs
new file mode 100644
index 0000000000..2042bcbc88
--- /dev/null
+++ b/dom/security/test/https-first/file_redirect.sjs
@@ -0,0 +1,58 @@
+//https://bugzilla.mozilla.org/show_bug.cgi?id=1706351
+
+// Step 1. Send request with redirect queryString (eg. file_redirect.sjs?302)
+// Step 2. Server responds with corresponding redirect code to http://example.com/../file_redirect.sjs?check
+// Step 3. Response from ?check indicates whether the redirected request was secure or not.
+
+const RESPONSE_ERROR = "unexpected-query";
+
+// An onload postmessage to window opener
+const RESPONSE_SECURE = `
+ <html>
+ <body>
+ send onload message...
+ <script type="application/javascript">
+ window.opener.postMessage({result: 'secure'}, '*');
+ </script>
+ </body>
+ </html>`;
+
+const RESPONSE_INSECURE = `
+ <html>
+ <body>
+ send onload message...
+ <script type="application/javascript">
+ window.opener.postMessage({result: 'insecure'}, '*');
+ </script>
+ </body>
+ </html>`;
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ const query = request.queryString;
+
+ // Send redirect header
+ if ((query >= 301 && query <= 303) || query == 307) {
+ // needs to be a cross site redirect to http://example.com otherwise
+ // our upgrade downgrade endless loop break mechanism kicks in
+ const loc =
+ "http://test1.example.com/tests/dom/security/test/https-first/file_redirect.sjs?check";
+ response.setStatusLine(request.httpVersion, query, "Found");
+ response.setHeader("Location", loc, false);
+ return;
+ }
+
+ // Check if scheme is http:// or https://
+ if (query == "check") {
+ const secure =
+ request.scheme == "https" ? RESPONSE_SECURE : RESPONSE_INSECURE;
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(secure);
+ return;
+ }
+
+ // This should not happen
+ response.setStatusLine(request.httpVersion, 500, "OK");
+ response.write(RESPONSE_ERROR);
+}
diff --git a/dom/security/test/https-first/file_redirect_downgrade.sjs b/dom/security/test/https-first/file_redirect_downgrade.sjs
new file mode 100644
index 0000000000..a31c8cb99b
--- /dev/null
+++ b/dom/security/test/https-first/file_redirect_downgrade.sjs
@@ -0,0 +1,87 @@
+// Custom *.sjs file specifically for the needs of Bug 1707856
+"use strict";
+
+const REDIRECT_META = `
+ <html>
+ <head>
+ <meta http-equiv="refresh" content="0; url='http://example.com/tests/dom/security/test/https-first/file_redirect_downgrade.sjs?testEnd'">
+ </head>
+ <body>
+ META REDIRECT
+ </body>
+ </html>`;
+
+const REDIRECT_JS = `
+ <html>
+ <body>
+ JS REDIRECT
+ <script>
+ let url= "http://example.com/tests/dom/security/test/https-first/file_redirect_downgrade.sjs?testEnd";
+ window.location = url;
+ </script>
+ </body>
+ </html>`;
+
+const REDIRECT_302 =
+ "http://example.com/tests/dom/security/test/https-first/file_redirect_downgrade.sjs?testEnd";
+
+const RESPONSE_SUCCESS = `
+ <html>
+ <body>
+ send message, downgraded
+ <script type="application/javascript">
+ let scheme = document.location.protocol;
+ window.opener.postMessage({result: 'downgraded', scheme: scheme}, '*');
+ </script>
+ </body>
+ </html>`;
+
+const RESPONSE_UNEXPECTED = `
+ <html>
+ <body>
+ send message, error
+ <script type="application/javascript">
+ let scheme = document.location.protocol;
+ window.opener.postMessage({result: 'error', scheme: scheme}, '*');
+ </script>
+ </body>
+ </html>`;
+
+function handleRequest(request, response) {
+ // avoid confusing cache behaviour
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+
+ let query = request.queryString;
+ // if the scheme is not https and it is the initial request
+ // then we rather fall through and display unexpected content
+ if (request.scheme === "https") {
+ if (query === "test1a") {
+ response.write(REDIRECT_META);
+ return;
+ }
+
+ if (query === "test2a") {
+ response.write(REDIRECT_JS);
+ return;
+ }
+
+ if (query === "test3a") {
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", REDIRECT_302, false);
+ return;
+ }
+
+ // Simulating a timeout by processing the https request
+ response.processAsync();
+ return;
+ }
+
+ // We should arrive here when the redirection was downraded successful
+ if (query == "testEnd") {
+ response.write(RESPONSE_SUCCESS);
+ return;
+ }
+ // We should never arrive here, just in case send 'error'
+ response.write(RESPONSE_UNEXPECTED);
+}
diff --git a/dom/security/test/https-first/file_referrer_policy.sjs b/dom/security/test/https-first/file_referrer_policy.sjs
new file mode 100644
index 0000000000..282f7ba09f
--- /dev/null
+++ b/dom/security/test/https-first/file_referrer_policy.sjs
@@ -0,0 +1,102 @@
+const RESPONSE_ERROR = `
+ <html>
+ <body>
+ Error occurred...
+ <script type="application/javascript">
+ window.opener.postMessage({result: 'ERROR'}, '*');
+ </script>
+ </body>
+ </html>`;
+const RESPONSE_POLICY = `
+<html>
+<body>
+Send policy onload...
+<script type="application/javascript">
+ const loc = document.location.href;
+ window.opener.postMessage({result: document.referrer, location: loc}, "*");
+</script>
+</body>
+</html>`;
+
+const expectedQueries = [
+ "no-referrer",
+ "no-referrer-when-downgrade",
+ "origin",
+ "origin-when-cross-origin",
+ "same-origin",
+ "strict-origin",
+ "strict-origin-when-cross-origin",
+ "unsafe-url",
+];
+function readQuery(testCase) {
+ let twoValues = testCase.split("-");
+ let upgradeRequest = twoValues[0] === "https" ? 1 : 0;
+ let httpsResponse = twoValues[1] === "https" ? 1 : 0;
+ return [upgradeRequest, httpsResponse];
+}
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ Cu.importGlobalProperties(["URLSearchParams"]);
+ let query = new URLSearchParams(request.queryString);
+ // Downgrade to test http/https -> HTTP referrer policy
+ if (query.has("sendMe2") && request.scheme === "https") {
+ // Simulating a timeout by processing the https request
+ response.processAsync();
+ return;
+ }
+ if (query.has("sendMe") || query.has("sendMe2")) {
+ response.write(RESPONSE_POLICY);
+ return;
+ }
+ // Get the referrer policy that we want to set
+ let referrerPolicy = query.get("rp");
+ //If the query contained one of the expected referrer policies send a request with the given policy,
+ // else send error
+ if (expectedQueries.includes(referrerPolicy)) {
+ // Determine the test case, e.g. don't upgrade request but send response in https
+ let testCase = readQuery(query.get("upgrade"));
+ let httpsRequest = testCase[0];
+ let httpsResponse = testCase[1];
+ // Downgrade to http if upgrade equals 0
+ if (httpsRequest === 0 && request.scheme === "https") {
+ // Simulating a timeout by processing the https request
+ response.processAsync();
+ return;
+ }
+ // create js redirection that request with the given (related to the query) referrer policy
+ const SEND_REQUEST_HTTPS = `
+ <html>
+ <head>
+ <meta name="referrer" content=${referrerPolicy}>
+ </head>
+ <body>
+ JS REDIRECT
+ <script>
+ let url = 'https://example.com/tests/dom/security/test/https-first/file_referrer_policy.sjs?sendMe';
+ window.location = url;
+ </script>
+ </body>
+ </html>`;
+ const SEND_REQUEST_HTTP = `
+ <html>
+ <head>
+ <meta name="referrer" content=${referrerPolicy}>
+ </head>
+ <body>
+ JS REDIRECT
+ <script>
+ let url = 'http://example.com/tests/dom/security/test/https-first/file_referrer_policy.sjs?sendMe2';
+ window.location = url;
+ </script>
+ </body>
+ </html>`;
+ let respond = httpsResponse === 1 ? SEND_REQUEST_HTTPS : SEND_REQUEST_HTTP;
+ response.write(respond);
+ return;
+ }
+
+ // We should never get here but in case we send an error
+ response.setStatusLine(request.httpVersion, 500, "OK");
+ response.write(RESPONSE_ERROR);
+}
diff --git a/dom/security/test/https-first/file_slow_download.html b/dom/security/test/https-first/file_slow_download.html
new file mode 100644
index 0000000000..084977607d
--- /dev/null
+++ b/dom/security/test/https-first/file_slow_download.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test slow download from an http site that gets upgraded to https</title>
+</head>
+<body>
+ <a href="http://example.com/browser/dom/security/test/https-first/file_slow_download.sjs" download="large-dummy-file.txt" id="testlink">download by attribute</a>
+ <script>
+ // click the link to start download
+ let testlink = document.getElementById("testlink");
+ testlink.click();
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/https-first/file_slow_download.sjs b/dom/security/test/https-first/file_slow_download.sjs
new file mode 100644
index 0000000000..6e4f109068
--- /dev/null
+++ b/dom/security/test/https-first/file_slow_download.sjs
@@ -0,0 +1,26 @@
+"use strict";
+let timer;
+
+// Send a part of the file then wait for 3 second before sending the rest.
+// If download isn't exempt from background timer of https-only/-first then the download
+// gets cancelled before it completed.
+const DELAY_MS = 3500;
+function handleRequest(request, response) {
+ response.processAsync();
+ timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader(
+ "Content-Disposition",
+ "attachment; filename=large-dummy-file.txt"
+ );
+ response.setHeader("Content-Type", "text/plain");
+ response.write("Begin the file");
+ timer.init(
+ () => {
+ response.write("End of file");
+ response.finish();
+ },
+ DELAY_MS,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/dom/security/test/https-first/file_toplevel_cookies.sjs b/dom/security/test/https-first/file_toplevel_cookies.sjs
new file mode 100644
index 0000000000..dd9f7c0909
--- /dev/null
+++ b/dom/security/test/https-first/file_toplevel_cookies.sjs
@@ -0,0 +1,233 @@
+// Custom *.sjs file specifically for the needs of Bug 1711453
+"use strict";
+
+// small red image
+const IMG_BYTES = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+ "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
+);
+
+const IFRAME_INC = `<iframe id="testframeinc"></iframe>`;
+
+// Sets an image sends cookie and location after loading
+const SET_COOKIE_IMG = `
+<html>
+<body>
+<img id="cookieImage">
+<script class="testbody" type="text/javascript">
+ var cookieImage = document.getElementById("cookieImage");
+ cookieImage.onload = function() {
+ let myLocation = window.location.href;
+ let myCookie = document.cookie;
+ window.opener.postMessage({result: 'upgraded', loc: myLocation, cookie: myCookie}, '*');
+ }
+ cookieImage.onerror = function() {
+ window.opener.postMessage({result: 'error'}, '*');
+ }
+ // Add the last number of the old query to the new query to set cookie properly
+ cookieImage.src = window.location.origin + "/tests/dom/security/test/https-first/file_toplevel_cookies.sjs?setSameSiteCookie"
+ + window.location.href.charAt(window.location.href.length -1);
+</script>
+</body>
+</html>
+`;
+
+// Load blank frame navigation sends cookie and location after loading
+const LOAD_BLANK_FRAME_NAV = `
+<html>
+<body>
+<iframe id="testframe"></iframe>
+<script>
+ let testframe = document.getElementById("testframe");
+ testframe.onload = function() {
+ let myLocation = window.location.href;
+ let myCookie = document.cookie;
+ window.opener.postMessage({result: 'upgraded', loc: myLocation, cookie: myCookie}, '*');
+ }
+ testframe.onerror = function() {
+ window.opener.postMessage({result: 'error', loc: 'error', cookie: ''}, '*');
+ }
+ testframe.src = window.location.origin + "/tests/dom/security/test/https-first/file_toplevel_cookies.sjs?loadblankframeNav";
+</script>
+</body>
+</html>
+`;
+
+// Load frame navigation sends cookie and location after loading
+const LOAD_FRAME_NAV = `
+<html>
+<body>
+<iframe id="testframe"></iframe>
+<script>
+ let testframe = document.getElementById("testframe");
+ testframe.onload = function() {
+ let myLocation = window.location.href;
+ let myCookie = document.cookie;
+ window.opener.postMessage({result: 'upgraded', loc: myLocation, cookie: myCookie}, '*');
+ }
+ testframe.onerror = function() {
+ window.opener.postMessage({result: 'error', loc: 'error', cookie: ''}, '*');
+ }
+ testframe.src = window.location.origin + "/tests/dom/security/test/https-first/file_toplevel_cookies.sjs?loadsrcdocframeNav";
+</script>
+</body>
+</html>
+
+`;
+// blank frame sends cookie and location after loading
+const LOAD_BLANK_FRAME = `
+<html>
+<body>
+<iframe id="testframe"></iframe>
+<script>
+ let testframe = document.getElementById("testframe");
+ testframe.onload = function() {
+ let myLocation = window.location.href;
+ let myCookie = document.cookie;
+ window.opener.postMessage({result: 'upgraded', loc: myLocation, cookie: myCookie}, '*');
+ }
+ testframe.onerror = function() {
+ window.opener.postMessage({result: 'error', loc: 'error', cookie: ''}, '*');
+ }
+ testframe.src = window.location.origin + "/tests/dom/security/test/https-first/file_toplevel_cookies.sjs?loadblankframeInc";
+</script>
+</body>
+</html>
+`;
+// frame sends cookie and location after loading
+const LOAD_FRAME = `
+<html>
+<body>
+<iframe id="testframe"></iframe>
+<script>
+ let testframe = document.getElementById("testframe");
+ testframe.onload = function() {
+ let myLocation = window.location.href;
+ let myCookie = document.cookie;
+ window.opener.postMessage({result: 'upgraded', loc: myLocation, cookie: myCookie}, '*');
+ }
+ testframe.onerror = function() {
+ window.opener.postMessage({result: 'error', loc: 'error', cookie: ''}, '*');
+ }
+ testframe.src = window.location.origin + "/tests/dom/security/test/https-first/file_toplevel_cookies.sjs?loadsrcdocframeInc";
+</script>
+</body>
+</html>
+`;
+
+const RESPONSE_UNEXPECTED = `
+ <html>
+ <body>
+ send message, error
+ <script type="application/javascript">
+ let myLocation = document.location.href;
+ window.opener.postMessage({result: 'error', loc: myLocation}, '*');
+ </script>
+ </body>
+ </html>`;
+
+function setCookie(name, query) {
+ let cookie = name + "=";
+ if (query.includes("0")) {
+ cookie += "0;Domain=.example.com;sameSite=none";
+ return cookie;
+ }
+ if (query.includes("1")) {
+ cookie += "1;Domain=.example.com;sameSite=strict";
+ return cookie;
+ }
+ if (query.includes("2")) {
+ cookie += "2;Domain=.example.com;sameSite=none;secure";
+ return cookie;
+ }
+ if (query.includes("3")) {
+ cookie += "3;Domain=.example.com;sameSite=strict;secure";
+ return cookie;
+ }
+ return cookie + "error";
+}
+
+function handleRequest(request, response) {
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ let query = request.queryString;
+ if (query.includes("setImage")) {
+ response.write(SET_COOKIE_IMG);
+ return;
+ }
+ // using startsWith and discard the math random
+ if (query.includes("setSameSiteCookie")) {
+ response.setHeader("Set-Cookie", setCookie("setImage", query), true);
+ response.setHeader("Content-Type", "image/png");
+ response.write(IMG_BYTES);
+ return;
+ }
+
+ // navigation tests
+ if (query.includes("loadNavBlank")) {
+ response.setHeader("Set-Cookie", setCookie("loadNavBlank", query), true);
+ response.write(LOAD_BLANK_FRAME_NAV);
+ return;
+ }
+
+ if (request.queryString === "loadblankframeNav") {
+ let FRAME = `
+ <iframe src="about:blank"
+ // nothing happens here
+ </iframe>`;
+ response.write(FRAME);
+ return;
+ }
+
+ if (query.includes("loadNav")) {
+ response.setHeader("Set-Cookie", setCookie("loadNav", query), true);
+ response.write(LOAD_FRAME_NAV);
+ return;
+ }
+
+ if (query === "loadsrcdocframeNav") {
+ let FRAME = `
+ <iframe srcdoc="foo"
+ // nothing happens here
+ </iframe>`;
+ response.write(FRAME);
+ return;
+ }
+
+ // inclusion tests
+ if (query.includes("loadframeIncBlank")) {
+ response.setHeader(
+ "Set-Cookie",
+ setCookie("loadframeIncBlank", query),
+ true
+ );
+ response.write(LOAD_BLANK_FRAME);
+ return;
+ }
+
+ if (request.queryString === "loadblankframeInc") {
+ let FRAME =
+ ` <iframe id="blankframe" src="about:blank"></iframe>
+ <script>
+ document.getElementById("blankframe").contentDocument.write("` +
+ IFRAME_INC +
+ `");
+ <\script>`;
+ response.write(FRAME);
+ return;
+ }
+
+ if (query.includes("loadframeInc")) {
+ response.setHeader("Set-Cookie", setCookie("loadframeInc", query), true);
+ response.write(LOAD_FRAME);
+ return;
+ }
+
+ if (request.queryString === "loadsrcdocframeInc") {
+ response.write('<iframe srcdoc="' + IFRAME_INC + '"></iframe>');
+ return;
+ }
+
+ // We should never arrive here, just in case send 'error'
+ response.write(RESPONSE_UNEXPECTED);
+}
diff --git a/dom/security/test/https-first/file_upgrade_insecure.html b/dom/security/test/https-first/file_upgrade_insecure.html
new file mode 100644
index 0000000000..af306d2a16
--- /dev/null
+++ b/dom/security/test/https-first/file_upgrade_insecure.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1704454 - HTTPS FIRST Mode</title>
+ <!-- style -->
+ <link rel='stylesheet' type='text/css' href='http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?style' media='screen' />
+
+ <!-- font -->
+ <style>
+ @font-face {
+ font-family: "foofont";
+ src: url('http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?font');
+ }
+ .div_foo { font-family: "foofont"; }
+ </style>
+</head>
+<body>
+
+ <!-- images: -->
+ <img src="http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?img"></img>
+
+ <!-- redirects: upgrade http:// to https:// redirect to http:// and then upgrade to https:// again -->
+ <img src="http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?redirect-image"></img>
+
+ <!-- script: -->
+ <script src="http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?script"></script>
+
+ <!-- media: -->
+ <audio src="http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?media"></audio>
+
+ <!-- objects: -->
+ <object width="10" height="10" data="http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?object"></object>
+
+ <!-- font: (apply font loaded in header to div) -->
+ <div class="div_foo">foo</div>
+
+ <!-- iframe: (same origin) -->
+ <iframe src="http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?iframe">
+ <!-- within that iframe we load an image over http and make sure the requested gets upgraded to https -->
+ </iframe>
+
+ <!-- toplevel: -->
+ <script type="application/javascript">
+ let myWin = window.open("http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?top-level");
+ //close right after opening
+ myWin.onunload = function(){
+ myWin.close();
+ }
+ </script>
+
+ <!-- xhr: -->
+ <script type="application/javascript">
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?xhr");
+ myXHR.send(null);
+ </script>
+
+
+ <!-- form action: (upgrade POST from http:// to https://) -->
+ <iframe name='formFrame' id='formFrame'></iframe>
+ <form target="formFrame" action="http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?form" method="POST">
+ <input name="foo" value="foo">
+ <input type="submit" id="submitButton" formenctype='multipart/form-data' value="Submit form">
+ </form>
+ <script type="text/javascript">
+ var submitButton = document.getElementById('submitButton');
+ submitButton.click();
+ </script>
+
+</body>
+</html>
diff --git a/dom/security/test/https-first/file_upgrade_insecure_server.sjs b/dom/security/test/https-first/file_upgrade_insecure_server.sjs
new file mode 100644
index 0000000000..a8f4d66659
--- /dev/null
+++ b/dom/security/test/https-first/file_upgrade_insecure_server.sjs
@@ -0,0 +1,114 @@
+// SJS file for https-first Mode mochitests
+// Bug 1704454 - HTTPS First Mode
+
+const TOTAL_EXPECTED_REQUESTS = 12;
+
+const IFRAME_CONTENT =
+ "<!DOCTYPE HTML>" +
+ "<html>" +
+ "<head><meta charset='utf-8'>" +
+ "<title>Bug 1704454 - Test HTTPS First Mode</title>" +
+ "</head>" +
+ "<body>" +
+ "<img src='http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?nested-img'></img>" +
+ "</body>" +
+ "</html>";
+
+const expectedQueries = [
+ "script",
+ "style",
+ "img",
+ "iframe",
+ "form",
+ "xhr",
+ "media",
+ "object",
+ "font",
+ "img-redir",
+ "nested-img",
+ "top-level",
+];
+
+function handleRequest(request, response) {
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ var queryString = request.queryString;
+
+ // initialize server variables and save the object state
+ // of the initial request, which returns async once the
+ // server has processed all requests.
+ if (queryString == "queryresult") {
+ setState("totaltests", TOTAL_EXPECTED_REQUESTS.toString());
+ setState("receivedQueries", "");
+ response.processAsync();
+ setObjectState("queryResult", response);
+ return;
+ }
+
+ // handle img redirect (https->http)
+ if (queryString == "redirect-image") {
+ var newLocation =
+ "http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?img-redir";
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", newLocation, false);
+ return;
+ }
+
+ // just in case error handling for unexpected queries
+ if (!expectedQueries.includes(queryString)) {
+ response.write("unexpected-response");
+ return;
+ }
+
+ // make sure all the requested queries aren't upgraded to https
+ // except of toplevel requests
+ if (queryString === "top-level") {
+ queryString += request.scheme === "https" ? "-ok" : "-error";
+ } else {
+ queryString += request.scheme === "http" ? "-ok" : "-error";
+ }
+ var receivedQueries = getState("receivedQueries");
+
+ // images, scripts, etc. get queried twice, do not
+ // confuse the server by storing the preload as
+ // well as the actual load. If either the preload
+ // or the actual load is not https, then we would
+ // append "-error" in the array and the test would
+ // fail at the end.
+
+ // append the result to the total query string array
+ if (receivedQueries != "") {
+ receivedQueries += ",";
+ }
+ receivedQueries += queryString;
+ setState("receivedQueries", receivedQueries);
+
+ // keep track of how many more requests the server
+ // is expecting
+ var totaltests = parseInt(getState("totaltests"));
+ totaltests -= 1;
+ setState("totaltests", totaltests.toString());
+
+ // return content (img) for the nested iframe to test
+ // that subresource requests within nested contexts
+ // get upgraded as well. We also have to return
+ // the iframe context in case of an error so we
+ // can test both, using upgrade-insecure as well
+ // as the base case of not using upgrade-insecure.
+ if (queryString == "iframe-ok" || queryString == "iframe-error") {
+ response.write(IFRAME_CONTENT);
+ }
+
+ // if we have received all the requests, we return
+ // the result back.
+ if (totaltests == 0) {
+ getObjectState("queryResult", function (queryResponse) {
+ if (!queryResponse) {
+ return;
+ }
+ var receivedQueries = getState("receivedQueries");
+ queryResponse.write(receivedQueries);
+ queryResponse.finish();
+ });
+ }
+}
diff --git a/dom/security/test/https-first/mochitest.ini b/dom/security/test/https-first/mochitest.ini
new file mode 100644
index 0000000000..5a0ff62f21
--- /dev/null
+++ b/dom/security/test/https-first/mochitest.ini
@@ -0,0 +1,44 @@
+[DEFAULT]
+skip-if = http3
+
+[test_fragment.html]
+support-files = file_fragment.html
+[test_resource_upgrade.html]
+scheme=https
+support-files =
+ file_upgrade_insecure.html
+ file_upgrade_insecure_server.sjs
+skip-if = true # Bug 1727101, Bug 1727925
+[test_redirect_upgrade.html]
+scheme=https
+support-files =
+ file_redirect.sjs
+[test_redirect_downgrade.html]
+support-files = file_redirect_downgrade.sjs
+[test_data_uri.html]
+support-files =
+ file_data_uri.html
+ [test_toplevel_cookies.html]
+support-files =
+ file_toplevel_cookies.sjs
+[test_downgrade_bad_responses.html]
+support-files= file_downgrade_bad_responses.sjs
+[test_referrer_policy.html]
+support-files= file_referrer_policy.sjs
+[test_break_endless_upgrade_downgrade_loop.html]
+support-files =
+ file_break_endless_upgrade_downgrade_loop.sjs
+ file_downgrade_with_different_path.sjs
+[test_multiple_redirection.html]
+support-files =
+ file_multiple_redirection.sjs
+[test_form_submission.html]
+support-files =
+ file_form_submission.sjs
+[test_bad_cert.html]
+support-files =
+ file_bad_cert.sjs
+[test_downgrade_request_upgrade_request.html]
+support-files = file_downgrade_request_upgrade_request.sjs
+[test_downgrade_500_responses.html]
+support-files = file_downgrade_500_responses.sjs
diff --git a/dom/security/test/https-first/pass.png b/dom/security/test/https-first/pass.png
new file mode 100644
index 0000000000..2fa1e0ac06
--- /dev/null
+++ b/dom/security/test/https-first/pass.png
Binary files differ
diff --git a/dom/security/test/https-first/test.ogv b/dom/security/test/https-first/test.ogv
new file mode 100644
index 0000000000..0f83996e5d
--- /dev/null
+++ b/dom/security/test/https-first/test.ogv
Binary files differ
diff --git a/dom/security/test/https-first/test.wav b/dom/security/test/https-first/test.wav
new file mode 100644
index 0000000000..85dc1ea904
--- /dev/null
+++ b/dom/security/test/https-first/test.wav
Binary files differ
diff --git a/dom/security/test/https-first/test_bad_cert.html b/dom/security/test/https-first/test_bad_cert.html
new file mode 100644
index 0000000000..d7e9296d97
--- /dev/null
+++ b/dom/security/test/https-first/test_bad_cert.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1719309
+Test that bad cert sites won't get upgraded by https-first
+-->
+
+<head>
+ <title>HTTPS-FirstMode - Bad Certificates</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+ <h1>HTTPS-First Mode</h1>
+ <p>Test: Downgrade bad certificates without warning page </p>
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1706351">Bug 1719309</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+ /*
+ * We perform the following tests:
+ * 1. Request nocert.example.com which is a site without a certificate
+ * 2. Request a site with self-signed cert (self-signed.example.com)
+ * 3. Request a site with an untrusted cert (untrusted.example.com)
+ * 4. Request a site with an expired cert
+ * 5. Request a site with an untrusted and expired cert
+ * 6. Request a site with no subject alternative dns name matching
+ *
+ * Expected result: Https-first tries to upgrade each request. Receives for each one an SSL_ERROR_*
+ * and downgrades back to http.
+ */
+ const badCertificates = ["nocert","self-signed", "untrusted","expired","untrusted-expired", "no-subject-alt-name"];
+ let currentTest = 0;
+ let testWin;
+ window.addEventListener("message", receiveMessage);
+
+ // Receive message and verify that it is from an http site.
+ // Verify that we got the correct message and an http scheme
+ async function receiveMessage(event) {
+ let data = event.data;
+ let currentBadCert = badCertificates[currentTest];
+ ok(data.result === "downgraded", "Downgraded request " + currentBadCert);
+ ok(data.scheme === "http:", "Received 'http' for " + currentBadCert);
+ testWin.close();
+ if (++currentTest < badCertificates.length) {
+ startTest();
+ return;
+ }
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+ }
+
+ async function startTest() {
+ const currentCode = badCertificates[currentTest];
+ // make a request to a subdomain of example.com with a bad certificate
+ testWin = window.open(`http://${currentCode}.example.com/tests/dom/security/test/https-first/file_bad_cert.sjs`);
+ }
+
+ // Set preference and start test
+ SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_first", true],
+ ]}, startTest);
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</body>
+</html>
diff --git a/dom/security/test/https-first/test_break_endless_upgrade_downgrade_loop.html b/dom/security/test/https-first/test_break_endless_upgrade_downgrade_loop.html
new file mode 100644
index 0000000000..7d239350a1
--- /dev/null
+++ b/dom/security/test/https-first/test_break_endless_upgrade_downgrade_loop.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1715253
+Test that same origin redirect does not cause endless loop with https-first enabled
+-->
+
+<head>
+ <title>HTTPS-First-Mode - Break endless upgrade downgrade redirect loop</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+ <h1>HTTPS-First Mode</h1>
+ <p>Upgrade Test for insecure redirects.</p>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ const redirectCodes = ["301", "302","303","307"];
+ let currentTest = 0;
+ let testWin;
+ window.addEventListener("message", receiveMessage);
+
+ // receive message from loaded site verifying the scheme of
+ // the loaded document.
+ async function receiveMessage(event) {
+ let currentRedirectCode = redirectCodes[currentTest];
+ is(event.data.result,
+ "scheme-http",
+ "same-origin redirect results in 'http' for " + currentRedirectCode
+ );
+ testWin.close();
+ if (++currentTest < redirectCodes.length) {
+ startTest();
+ return;
+ }
+ window.removeEventListener("message", receiveMessage);
+ window.addEventListener("message", receiveMessageForDifferentPathTest);
+ testDifferentPath();
+ }
+
+ async function receiveMessageForDifferentPathTest(event) {
+ is(event.data.result,
+ "scheme-https",
+ "scheme should be https when the path is different"
+ );
+ testWin.close();
+ window.removeEventListener("message", receiveMessageForDifferentPathTest);
+ SimpleTest.finish();
+ }
+
+ async function startTest() {
+ const currentCode = redirectCodes[currentTest];
+ // Load an http:// window which gets upgraded to https://
+ let uri =
+ `http://example.com/tests/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs?${currentCode}`;
+ testWin = window.open(uri);
+ }
+
+ async function testDifferentPath() {
+ // Load an https:// window which gets downgraded to http://
+ let uri =
+ `https://example.com/tests/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs?downgrade`;
+ testWin = window.open(uri);
+ }
+
+ // Set preference and start test
+ SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_first", true],
+ ["security.mixed_content.block_active_content", false],
+ ["security.mixed_content.block_display_content", false],
+ ["dom.security.https_only_check_path_upgrade_downgrade_endless_loop", true],
+ ]}, startTest);
+ </script>
+</body>
+</html>
diff --git a/dom/security/test/https-first/test_data_uri.html b/dom/security/test/https-first/test_data_uri.html
new file mode 100644
index 0000000000..b9891260db
--- /dev/null
+++ b/dom/security/test/https-first/test_data_uri.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1709069: Test that Data URI which makes a top-level request gets updated in https-first</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+window.addEventListener("message", receiveMessage);
+
+// HTML site which makes a top-level http request
+const HTML = `
+<html>
+<body>
+ DATA HTML
+<script>
+ window.open("http://example.com/tests/dom/security/test/https-first/file_data_uri.html");
+<\/script>
+<\/body>
+<\/html>
+`;
+
+const DATA_HTML = "data:text/html, " + HTML;
+
+// Verify that data uri top-level request got upgraded to https and
+// the reached location is correct
+async function receiveMessage(event){
+ let data = event.data;
+ is(data.location, "https://example.com/tests/dom/security/test/https-first/file_data_uri.html",
+ "Reached the correct location");
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+}
+
+function test_toplevel_https() {
+ document.getElementById("testframe").src = DATA_HTML;
+}
+
+SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_first", true],
+ ]}, test_toplevel_https);
+
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/https-first/test_downgrade_500_responses.html b/dom/security/test/https-first/test_downgrade_500_responses.html
new file mode 100644
index 0000000000..3943c9095c
--- /dev/null
+++ b/dom/security/test/https-first/test_downgrade_500_responses.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Bug 1747673 : HTTPS First fallback to http for non-standard 5xx status code responses</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+"use strict";
+/*
+ * Description of the test:
+ * Perform five tests where https-first receives an
+ * 5xx status code (standard and non-standard 5xx status) if request is send to site by https.
+ * Expected behaviour: https-first fallbacks to http after receiving 5xx error.
+ * Test 1: 501 Response
+ * Test 2: 504 Response
+ * Test 3: 521 Response
+ * Test 4: 530 Response
+ * Test 5: 560 Response
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+const REQUEST_URL =
+ "http://example.com/tests/dom/security/test/https-first/file_downgrade_500_responses.sjs";
+
+const redirectQueries = ["?test1a", "?test2a","?test3a", "?test4a", "?test5a"];
+let currentTest = 0;
+let testWin;
+let currentQuery;
+window.addEventListener("message", receiveMessage);
+
+// Receive message and verify that it is from an http site.
+// When the message is 'downgraded' then it was send by an http site
+// and the redirection worked.
+async function receiveMessage(event) {
+ let data = event.data;
+ currentQuery = redirectQueries[currentTest];
+ ok(data.result === "downgraded", "Redirected successful to 'http' for " + currentQuery);
+ is(data.scheme, "http:", "scheme is 'http' for " + currentQuery );
+ testWin.close();
+ if (++currentTest < redirectQueries.length) {
+ runTest();
+ return;
+ }
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+}
+
+async function runTest() {
+ currentQuery = redirectQueries[currentTest];
+ testWin = window.open(REQUEST_URL + currentQuery, "_blank");
+}
+
+SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_first", true]
+ ]}, runTest);
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/https-first/test_downgrade_bad_responses.html b/dom/security/test/https-first/test_downgrade_bad_responses.html
new file mode 100644
index 0000000000..39cef7f26a
--- /dev/null
+++ b/dom/security/test/https-first/test_downgrade_bad_responses.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Bug 1709552 : HTTPS-First: Add downgrade tests for bad responses to https request </title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+"use strict";
+/*
+ * Description of the test:
+ * We perform five tests where we expect https-first to detect
+ * that the target site only supports http
+ * Test 1: 400 Response
+ * Test 2: 401 Response
+ * Test 3: 403 Response
+ * Test 4: 416 Response
+ * Test 5: 418 Response
+ * Test 6: Timeout
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+const REQUEST_URL =
+ "http://example.com/tests/dom/security/test/https-first/file_downgrade_bad_responses.sjs";
+
+const redirectQueries = ["?test1a", "?test2a","?test3a", "?test4a", "?test5a", "?test6a"];
+let currentTest = 0;
+let testWin;
+let currentQuery;
+window.addEventListener("message", receiveMessage);
+
+// Receive message and verify that it is from an http site.
+// When the message is 'downgraded' then it was send by an http site
+// and the redirection worked.
+async function receiveMessage(event) {
+ let data = event.data;
+ currentQuery = redirectQueries[currentTest];
+ ok(data.result === "downgraded", "Redirected successful to 'http' for " + currentQuery);
+ ok(data.scheme === "http", "scheme is 'http' for " + currentQuery );
+ testWin.close();
+ if (++currentTest < redirectQueries.length) {
+ runTest();
+ return;
+ }
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+}
+
+async function runTest() {
+ currentQuery = redirectQueries[currentTest];
+ testWin = window.open(REQUEST_URL + currentQuery, "_blank");
+}
+
+SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_first", true]
+ ]}, runTest);
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/https-first/test_downgrade_request_upgrade_request.html b/dom/security/test/https-first/test_downgrade_request_upgrade_request.html
new file mode 100644
index 0000000000..b659636ace
--- /dev/null
+++ b/dom/security/test/https-first/test_downgrade_request_upgrade_request.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title> Bug 1706126: Test https-first, downgrade first request and then upgrade redirection to subdomain</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+"use strict";
+/*
+ * Description of the test:
+ * First we request http://redirect-example.com which HTTPS-First upgrades to https://redirect-example.com.
+ * The request https://redirect-example.com doesn't receive an answer (timeout), so we send a background
+ * request.
+ * The background request receives an answer. So the request https://redirect-example.com gets downgraded
+ * to http://redirect-example.com by the exempt flag.
+ * The request http://redirect-example.com gets redirected to http://wwww.redirect-example.com. At that stage
+ * HTTPS-First should clear the exempt flag and upgrade the redirection to https://wwww.redirect-example.com.
+ *
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+const REQUEST_URL =
+ "http://redirect-example.com/tests/dom/security/test/https-first/file_downgrade_request_upgrade_request.sjs";
+
+let testWin;
+window.addEventListener("message", receiveMessage);
+
+// Receive message and verify that it is from an https site.
+async function receiveMessage(event) {
+ let data = event.data;
+ ok(data.result === "upgraded", "Redirected successful to 'https' for subdomain ");
+ is(data.scheme,"https:", "scheme is 'https' for subdomain");
+ testWin.close();
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+}
+
+async function runTest() {
+ testWin = window.open(REQUEST_URL, "_blank");
+}
+
+SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_first", true]
+ ]}, runTest);
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/https-first/test_form_submission.html b/dom/security/test/https-first/test_form_submission.html
new file mode 100644
index 0000000000..a68c3501c6
--- /dev/null
+++ b/dom/security/test/https-first/test_form_submission.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1720103 - Https-first: Do not upgrade form submissions (for now)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+<script class="testbody" type="text/javascript">
+/*
+ * Description of the test:
+ * We test https-first behaviour with forms.
+ * We perform each test with once with same origin and the second time
+ * with a cross origin. We perform two GET form requests and two POST
+ * form requests.
+ * In more detail:
+ *
+ * 1. Test: Request that gets upgraded to https, GET form submission.
+ *
+ * 2. Test: Request that gets upgraded to https, that upgraded request
+ * gets timed out, so https-first send an http request, GET form submission.
+ *
+ * 3. Test: request that gets upgraded to https, and sends a POST form
+ * to http://example.com.
+ *
+ * 4. Test: Request where the https upgrade get timed out -> http, and sends a POST form
+ * to http://example.com,
+ *
+ */
+SimpleTest.waitForExplicitFinish();
+window.addEventListener("message", receiveMessage);
+
+const SAME_ORIGIN = "http://example.com/tests/dom/security/test/https-first/file_form_submission.sjs";
+const CROSS_ORIGIN = SAME_ORIGIN.replace(".com", ".org");
+const Tests = [{
+ // 1. Test GET, gets upgraded
+ query: "?test=1",
+ scheme: "https:",
+ method: "GET",
+ value: "test=success",
+},
+{
+ // 2. Test GET, initial request will be downgraded
+ query:"?test=2",
+ scheme: "http:",
+ method: "GET",
+ value: "test=success"
+},
+{ // 3. Test POST formular, gets upgraded
+ query: "?test=3",
+ scheme: "http:",
+ method: "POST",
+ value: "test=success"
+},
+{ // 4. Test POST formular, request will be downgraded
+ query: "?test=4",
+ scheme: "http:",
+ method: "POST",
+ value: "test=success"
+},
+];
+let currentTest;
+let counter = 0;
+let testWin;
+let sameOrigin = true;
+
+// Verify that top-level request got the expected scheme and reached the correct location.
+async function receiveMessage(event){
+ let data = event.data;
+ let origin = sameOrigin? SAME_ORIGIN : CROSS_ORIGIN
+ const expectedLocation = origin.replace("http:", currentTest.scheme);
+ // If GET request check that form was transfered by url
+ if (currentTest.method === "GET") {
+ is(data.location, expectedLocation + currentTest.query,
+ "Reached the correct location for " + currentTest.query );
+ } else {
+ // Since the form is always send to example.com we expect it here as location
+ is(data.location.includes(SAME_ORIGIN.replace("http:", currentTest.scheme)), true,
+ "Reached the correct location for " + currentTest.query );
+ }
+ is(data.scheme, currentTest.scheme,`${currentTest.query} upgraded or downgraded to ` + currentTest.scheme);
+ // Check that the form value is correct
+ is(data.form, currentTest.value, "Form was transfered");
+ testWin.close();
+ // Flip origin flag
+ sameOrigin ^= true;
+ // Only go to next test if already sent same and cross origin request for current test
+ if (sameOrigin) {
+ counter++;
+ }
+ // Check if we have test left, if not finish the testing
+ if (counter >= Tests.length) {
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+ return;
+ }
+ // If we didn't reached the end yet, run next test
+ runTest();
+}
+
+function runTest() {
+ currentTest = Tests[counter];
+ // If sameOrigin flag is set make a origin request, else a cross origin request
+ if (sameOrigin) {
+ testWin= window.open(SAME_ORIGIN + currentTest.query, "_blank");
+ } else {
+ testWin= window.open(CROSS_ORIGIN + currentTest.query, "_blank");
+ }
+}
+
+// Set prefs and start test
+SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_first", true],
+ ["security.warn_submit_secure_to_insecure", false]
+ ]}, runTest);
+
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/https-first/test_fragment.html b/dom/security/test/https-first/test_fragment.html
new file mode 100644
index 0000000000..4a27f198e1
--- /dev/null
+++ b/dom/security/test/https-first/test_fragment.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Bug 1706577: Have https-first mode account for fragment navigations</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+"use strict";
+/*
+ * Description of the test:
+ * Have https-first detect a fragment navigation rather than navigating away
+ * from the page.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+const REQUEST_URL = "http://example.com/tests/dom/security/test/https-first/file_fragment.html";
+const EXPECT_URL = REQUEST_URL.replace("http://", "https://");
+
+let winTest = null;
+let checkButtonClicked = false;
+
+async function receiveMessage(event) {
+ let data = event.data;
+ if (!checkButtonClicked) {
+ ok(data.result == EXPECT_URL, "location is correct");
+ ok(data.button, "button is clicked");
+ ok(data.info == "onload", "Onloading worked");
+ checkButtonClicked = true;
+ return;
+ }
+
+ // Once the button was clicked we know the tast has finished
+ ok(data.button, "button is clicked");
+ is(data.result, EXPECT_URL + "#foo", "location (hash) is correct");
+ ok(data.info == "scrolled-to-foo","Scrolled successfully without reloading!");
+ is(data.documentURI, EXPECT_URL + "#foo", "Document URI is correct");
+ window.removeEventListener("message",receiveMessage);
+ winTest.close();
+ SimpleTest.finish();
+}
+
+async function runTest() {
+ await SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_first", true],
+ ]});
+ winTest = window.open(REQUEST_URL);
+}
+
+window.addEventListener("message", receiveMessage);
+
+runTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/https-first/test_multiple_redirection.html b/dom/security/test/https-first/test_multiple_redirection.html
new file mode 100644
index 0000000000..d631f140e6
--- /dev/null
+++ b/dom/security/test/https-first/test_multiple_redirection.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1721410
+Test multiple redirects using https-first and ensure the entire redirect chain is using https
+-->
+
+<head>
+ <title>HTTPS-First-Mode - Test for multiple redirections</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ const testCase = [
+ // test 1: https-first upgrades http://example.com/test1 -> https://example.com/test1
+ // that's redirect to https://example.com/.../redirect which then redirects
+ // to http://example.com/../verify. Since the last redirect is http, and the
+ // the redirection chain contains already example.com we expect https-first
+ // to downgrade the request.
+ {name: "test last redirect HTTP", result: "scheme-http", query: "test1" },
+ // test 2: https-first upgrades http://example.com/test2 -> https://example.com/test2
+ // that's redirect to https://example.com/.../redirect which then redirects
+ // to https://example.com/../verify. Since the last redirect is https, we
+ // expect to reach an https website.
+ {name: "test last redirect HTTPS", result: "scheme-https", query: "test2"},
+ // test 3: https-first upgrades http://example.com/test3 -> https://example.com/test3
+ // that's redirect to https://example.com/.../hsts which then sets an hsts header
+ // and redirects to http://example.com/../verify. Since an hsts header was set
+ // we expect that to reach an https site
+ {name: "test last redirect HSTS", result: "scheme-https", query: "test3"},
+ // reset: reset hsts header for example.com
+ {name: "reset HSTS header", result: "scheme-https", query: "reset"},
+ ]
+ let currentTest = 0;
+ let testWin;
+ window.addEventListener("message", receiveMessage);
+
+ // receive message from loaded site verifying the scheme of
+ // the loaded document.
+ async function receiveMessage(event) {
+ let test = testCase[currentTest];
+ is(event.data.result,
+ test.result,
+ "same-origin redirect results in " + test.name
+ );
+ testWin.close();
+ if (++currentTest < testCase.length) {
+ startTest();
+ return;
+ }
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+ }
+
+ async function startTest() {
+ const test = testCase[currentTest];
+ // Load an http:// window which gets upgraded to https://
+ let uri =
+ `http://example.com/tests/dom/security/test/https-first/file_multiple_redirection.sjs?${test.query}`;
+ testWin = window.open(uri);
+ }
+
+ // Set preference and start test
+ SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_first", true],
+ ]}, startTest);
+ </script>
+</body>
+</html>
diff --git a/dom/security/test/https-first/test_redirect_downgrade.html b/dom/security/test/https-first/test_redirect_downgrade.html
new file mode 100644
index 0000000000..07f998c085
--- /dev/null
+++ b/dom/security/test/https-first/test_redirect_downgrade.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Bug 1707856: Test redirect downgrades with https-first</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+"use strict";
+/*
+ * Description of the test:
+ * We perform three tests where we expect https-first to detect
+ * that the target site only supports http
+ * Test 1: Meta Refresh
+ * Test 2: JS Redirect
+ * Test 3: 302 redirect
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+const REQUEST_URL =
+ "http://example.com/tests/dom/security/test/https-first/file_redirect_downgrade.sjs";
+
+const redirectQueries = ["?test1a", "?test2a","?test3a"];
+let currentTest = 0;
+let testWin;
+let currentQuery;
+window.addEventListener("message", receiveMessage);
+
+// Receive message and verify that it is from an https site.
+// When the message is 'downgraded' then it was send by an http site
+// and the redirection worked.
+async function receiveMessage(event) {
+ let data = event.data;
+ ok(data.result === "downgraded", "Redirected successful to 'http' for " + currentQuery);
+ ok(data.scheme === "http:", "scheme is 'http' for " + currentQuery );
+ testWin.close();
+ if (++currentTest < redirectQueries.length) {
+ runTest();
+ return;
+ }
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+}
+
+async function runTest() {
+ currentQuery = redirectQueries[currentTest];
+ testWin = window.open(REQUEST_URL + currentQuery, "_blank");
+}
+
+SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_first", true]
+ ]}, runTest);
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/https-first/test_redirect_upgrade.html b/dom/security/test/https-first/test_redirect_upgrade.html
new file mode 100644
index 0000000000..6cccf6af67
--- /dev/null
+++ b/dom/security/test/https-first/test_redirect_upgrade.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1706351
+Test that 302 redirect requests get upgraded to https:// with HTTPS-First Mode enabled
+-->
+
+<head>
+ <title>HTTPS-FirstMode - Redirect Upgrade</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+ <h1>HTTPS-First Mode</h1>
+ <p>Upgrade Test for insecure redirects.</p>
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1706351">Bug 1706351</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ const redirectCodes = ["301", "302","303","307"];
+ let currentTest = 0;
+ let testWin;
+ window.addEventListener("message", receiveMessage);
+
+ // Receive message and verify that it is from an https site.
+ // When the message is 'secure' then it was send by an https site.
+ async function receiveMessage(event) {
+ let data = event.data;
+ let currentRedirectCode = redirectCodes[currentTest];
+ ok(data.result === "secure", "Received 'https' for " + currentRedirectCode);
+ testWin.close();
+ if (++currentTest < redirectCodes.length) {
+ startTest();
+ return;
+ }
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+ }
+
+ async function startTest() {
+ const currentCode = redirectCodes[currentTest];
+ // Make a request to a site (eg. https://file_redirect.sjs?301), which will redirect to http://file_redirect.sjs?check.
+ // The response will either be secure-ok, if the request has been upgraded to https:// or secure-error if it didn't.
+ testWin = window.open(`https://example.com/tests/dom/security/test/https-first/file_redirect.sjs?${currentCode}`);
+ }
+
+ // Set preference and start test
+ SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_first", true],
+ ["security.mixed_content.block_active_content", false],
+ ["security.mixed_content.block_display_content", false],
+ ]}, startTest);
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</body>
+</html>
diff --git a/dom/security/test/https-first/test_referrer_policy.html b/dom/security/test/https-first/test_referrer_policy.html
new file mode 100644
index 0000000000..61521e2351
--- /dev/null
+++ b/dom/security/test/https-first/test_referrer_policy.html
@@ -0,0 +1,237 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Bug 1716706 : Write referrer-policy tests for https-first </title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+"use strict";
+/*
+ * Description of the test:
+ * We perform each test with 8 different settings.
+ * The first is a same origin request from an http site to an https site.
+ * The second is a same origin request from an https -> https.
+ * The third is a cross-origin request from an http -> https.
+ * The fourth is a cross-origin request from an https -> https.
+ * The fifth is a same origin request from an http -> http site.
+ * The sixth is a same origin request from an https -> http.
+ * The seventh is a cross-origin request from an http -> http.
+ * The last is a cross-origin request from an https -> http.
+ */
+
+SimpleTest.waitForExplicitFinish();
+// This test performs a lot of requests and checks (64 requests).
+// So to prevent to get a timeout before executing all test request longer timeout.
+SimpleTest.requestLongerTimeout(2);
+const SAME_ORIGIN =
+ "http://example.com/tests/dom/security/test/https-first/file_referrer_policy.sjs?";
+// SAME ORIGIN with "https" instead of "http"
+const SAME_ORIGIN_HTTPS = SAME_ORIGIN.replace("http", "https");
+
+const CROSS_ORIGIN =
+ "http://example.org/tests/dom/security/test/https-first/file_referrer_policy.sjs?";
+// CROSS ORIGIN with "https" instead of "http"
+const CROSS_ORIGIN_HTTPS = CROSS_ORIGIN.replace("http", "https");
+
+// Define test cases. Query equals the test case referrer policy.
+// We will set in the final request the url parameters such that 'rp=' equals the referrer policy
+//and 'upgrade=' equals '1' if the request should be https.
+// For a 'upgrade=0' url parameter the server lead to a timeout such that https-first downgrades
+// the request to http.
+const testCases = [
+ {
+ query: "no-referrer",
+ expectedResultSameOriginDownUp: "",
+ expectedResultSameOriginUpUp: "",
+ expectedResultCrossOriginDownUp:"",
+ expectedResultCrossOriginUpUp:"",
+ expectedResultSameOriginDownDown: "",
+ expectedResultSameOriginUpDown: "",
+ expectedResultCrossOriginDownDown:"",
+ expectedResultCrossOriginUpDown: "",
+ },
+ {
+ query: "no-referrer-when-downgrade",
+ expectedResultSameOriginDownUp: SAME_ORIGIN + "rp=no-referrer-when-downgrade&upgrade=http-https",
+ expectedResultSameOriginUpUp: SAME_ORIGIN_HTTPS + "rp=no-referrer-when-downgrade&upgrade=https-https",
+ expectedResultCrossOriginDownUp: CROSS_ORIGIN + "rp=no-referrer-when-downgrade&upgrade=http-https",
+ expectedResultCrossOriginUpUp: CROSS_ORIGIN_HTTPS + "rp=no-referrer-when-downgrade&upgrade=https-https",
+ expectedResultSameOriginDownDown: SAME_ORIGIN + "rp=no-referrer-when-downgrade&upgrade=http-http",
+ expectedResultSameOriginUpDown: "",
+ expectedResultCrossOriginDownDown: CROSS_ORIGIN + "rp=no-referrer-when-downgrade&upgrade=http-http",
+ expectedResultCrossOriginUpDown:"",
+ },
+ {
+ query: "origin",
+ expectedResultSameOriginDownUp: "http://example.com/",
+ expectedResultSameOriginUpUp: "https://example.com/",
+ expectedResultCrossOriginDownUp:"http://example.org/",
+ expectedResultCrossOriginUpUp:"https://example.org/",
+ expectedResultSameOriginDownDown: "http://example.com/",
+ expectedResultSameOriginUpDown: "https://example.com/",
+ expectedResultCrossOriginDownDown:"http://example.org/",
+ expectedResultCrossOriginUpDown:"https://example.org/",
+ },
+ {
+ query: "origin-when-cross-origin",
+ expectedResultSameOriginDownUp: "http://example.com/",
+ expectedResultSameOriginUpUp: SAME_ORIGIN_HTTPS + "rp=origin-when-cross-origin&upgrade=https-https",
+ expectedResultCrossOriginDownUp:"http://example.org/",
+ expectedResultCrossOriginUpUp:"https://example.org/",
+ expectedResultSameOriginDownDown: SAME_ORIGIN + "rp=origin-when-cross-origin&upgrade=http-http",
+ expectedResultSameOriginUpDown: "https://example.com/",
+ expectedResultCrossOriginDownDown:"http://example.org/",
+ expectedResultCrossOriginUpDown:"https://example.org/",
+ },
+ {
+ query: "same-origin",
+ expectedResultSameOriginDownUp: "",
+ expectedResultSameOriginUpUp: SAME_ORIGIN_HTTPS + "rp=same-origin&upgrade=https-https",
+ expectedResultCrossOriginDownUp:"",
+ expectedResultCrossOriginUpUp:"",
+ expectedResultSameOriginDownDown: SAME_ORIGIN + "rp=same-origin&upgrade=http-http",
+ expectedResultSameOriginUpDown: "",
+ expectedResultCrossOriginDownDown: "",
+ expectedResultCrossOriginUpDown:"",
+ },
+ {
+ query: "strict-origin",
+ expectedResultSameOriginDownUp: "http://example.com/",
+ expectedResultSameOriginUpUp: "https://example.com/",
+ expectedResultCrossOriginDownUp:"http://example.org/",
+ expectedResultCrossOriginUpUp:"https://example.org/",
+ expectedResultSameOriginDownDown: "http://example.com/",
+ expectedResultSameOriginUpDown: "",
+ expectedResultCrossOriginDownDown:"http://example.org/",
+ expectedResultCrossOriginUpDown:"",
+ },
+ {
+ query: "strict-origin-when-cross-origin",
+ expectedResultSameOriginDownUp: "http://example.com/",
+ expectedResultSameOriginUpUp: SAME_ORIGIN_HTTPS + "rp=strict-origin-when-cross-origin&upgrade=https-https",
+ expectedResultCrossOriginDownUp:"http://example.org/",
+ expectedResultCrossOriginUpUp:"https://example.org/",
+ expectedResultSameOriginDownDown: SAME_ORIGIN + "rp=strict-origin-when-cross-origin&upgrade=http-http",
+ expectedResultSameOriginUpDown: "",
+ expectedResultCrossOriginDownDown:"http://example.org/",
+ expectedResultCrossOriginUpDown:"",
+ },
+ {
+ query: "unsafe-url",
+ expectedResultSameOriginDownUp: SAME_ORIGIN + "rp=unsafe-url&upgrade=http-https",
+ expectedResultSameOriginUpUp: SAME_ORIGIN_HTTPS + "rp=unsafe-url&upgrade=https-https",
+ expectedResultCrossOriginDownUp: CROSS_ORIGIN + "rp=unsafe-url&upgrade=http-https",
+ expectedResultCrossOriginUpUp: CROSS_ORIGIN_HTTPS + "rp=unsafe-url&upgrade=https-https",
+ expectedResultSameOriginDownDown: SAME_ORIGIN + "rp=unsafe-url&upgrade=http-http",
+ expectedResultSameOriginUpDown: SAME_ORIGIN_HTTPS + "rp=unsafe-url&upgrade=https-http",
+ expectedResultCrossOriginDownDown:CROSS_ORIGIN + "rp=unsafe-url&upgrade=http-http",
+ expectedResultCrossOriginUpDown:CROSS_ORIGIN_HTTPS + "rp=unsafe-url&upgrade=https-http",
+ },
+];
+
+
+let currentTest = 0;
+let sameOriginRequest = true;
+let testWin;
+let currentQuery;
+window.addEventListener("message", receiveMessage);
+let currentRun = 0;
+// All combinations, HTTP -> HTTPS, HTTPS -> HTTPS, HTTP -> HTTP, HTTPS -> HTTP
+const ALL_COMB = ["http-https", "https-https" ,"http-http", "https-http"];
+
+// Receive message and verify that we receive the expected referrer header
+async function receiveMessage(event) {
+ let data = event.data;
+ currentQuery = testCases[currentTest].query;
+ let currentComb = ALL_COMB[currentRun];
+ // if request was http -> https
+ if (currentComb === "http-https") {
+ if (sameOriginRequest){
+ is(data.result, testCases[currentTest].expectedResultSameOriginDownUp ,
+ "We received for the downgraded same site request with referrer policy: " + currentQuery + " the correct referrer");
+ is(data.location, SAME_ORIGIN_HTTPS + "sendMe","Opened correct location");
+ } else {
+ is(data.result, testCases[currentTest].expectedResultCrossOriginDownUp ,
+ "We received for the downgraded cross site request with referrer policy: " + currentQuery + " the correct referrer");
+ is(data.location, SAME_ORIGIN_HTTPS + "sendMe", "Opened correct location");
+ }
+ // if request was https -> https
+ } else if (currentComb === "https-https") {
+ if (sameOriginRequest){
+ is(data.result, testCases[currentTest].expectedResultSameOriginUpUp ,
+ "We received for the upgraded same site request with referrer policy: " + currentQuery + " the correct referrer");
+ is(data.location, SAME_ORIGIN_HTTPS + "sendMe", "Opened correct location");
+ } else {
+ is(data.result, testCases[currentTest].expectedResultCrossOriginUpUp,
+ "We received for the upgraded cross site request with referrer policy: " + currentQuery + " the correct referrer");
+ is(data.location, SAME_ORIGIN_HTTPS + "sendMe", "Opened correct location");
+ }
+ } else if (currentComb === "http-http") {
+ if (sameOriginRequest){
+ is(data.result, testCases[currentTest].expectedResultSameOriginDownDown ,
+ "We received for the upgraded same site request with referrer policy: " + currentQuery + " the correct referrer");
+ is(data.location, SAME_ORIGIN + "sendMe2","Opened correct location for" + currentQuery + currentComb);
+ } else {
+ is(data.result, testCases[currentTest].expectedResultCrossOriginDownDown,
+ "We received for the upgraded cross site request with referrer policy: " + currentQuery + " the correct referrer");
+ is(data.location, SAME_ORIGIN + "sendMe2", "Opened correct location " + currentQuery + currentComb);
+ }
+ } else if (currentComb === "https-http") {
+ if (sameOriginRequest){
+ is(data.result, testCases[currentTest].expectedResultSameOriginUpDown ,
+ "We received for the upgraded same site request with referrer policy: " + currentQuery + " the correct referrer");
+ is(data.location, SAME_ORIGIN + "sendMe2","Opened correct location " + currentQuery + currentComb);
+ } else {
+ is(data.result, testCases[currentTest].expectedResultCrossOriginUpDown,
+ "We received for the upgraded cross site request with referrer policy: " + currentQuery + " the correct referrer");
+ is(data.location, SAME_ORIGIN + "sendMe2", "Opened correct location " + currentQuery + currentComb);
+ }
+ }
+ testWin.close();
+ currentRun++;
+ if (currentTest >= testCases.length -1 && currentRun === ALL_COMB.length && !sameOriginRequest) {
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+ return;
+ }
+ runTest();
+}
+
+async function runTest() {
+ currentQuery = testCases[currentTest].query;
+ // send same origin request
+ if (sameOriginRequest && currentRun < ALL_COMB.length) {
+ // if upgrade = 0 downgrade request, else upgrade
+ testWin = window.open(SAME_ORIGIN + "rp=" +currentQuery + "&upgrade=" + ALL_COMB[currentRun], "_blank");
+ } else {
+ // if same origin isn't set, check if we need to send cross origin requests
+ // eslint-disable-next-line no-lonely-if
+ if (!sameOriginRequest && currentRun < ALL_COMB.length ) {
+ // if upgrade = 0 downgrade request, else upgrade
+ testWin = window.open(CROSS_ORIGIN + "rp=" +currentQuery + "&upgrade=" + ALL_COMB[currentRun], "_blank");
+ } // else we completed all test case of the current query for the current origin. Prepare and call next test
+ else {
+ // reset currentRun and go to next query
+ currentRun = 0;
+ if(!sameOriginRequest){
+ currentTest++;
+ }
+ // run same test again for crossOrigin or start new test with sameOrigin
+ sameOriginRequest = !sameOriginRequest;
+ currentQuery = testCases[currentTest].query;
+ runTest();
+ }
+ }
+}
+
+SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_first", true],
+ ["network.http.referer.disallowCrossSiteRelaxingDefault", false],
+ ]}, runTest);
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/https-first/test_resource_upgrade.html b/dom/security/test/https-first/test_resource_upgrade.html
new file mode 100644
index 0000000000..c71879ac42
--- /dev/null
+++ b/dom/security/test/https-first/test_resource_upgrade.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>HTTPS-First Mode - Resource Upgrade</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+ <h1>HTTPS-First Mode</h1>
+ <p>Upgrade Test for various resources</p>
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1704454">Bug 1704454/a>
+ <iframe style="width:100%;" id="testframe"></iframe>
+
+ <script class="testbody" type="text/javascript">
+ /* Description of the test:
+ * We load resources (img, script, sytle, etc) over *http* and
+ * make sure they do not get upgraded to *https* because
+ * https-first only applies to top-level requests.
+ *
+ * In detail:
+ * We perform an XHR request to the *.sjs file which is processed async on
+ * the server and waits till all the requests were processed by the server.
+ * Once the server received all the different requests, the server responds
+ * to the initial XHR request with an array of results which must match
+ * the expected results from each test, making sure that all requests
+ * received by the server (*.sjs) were actually *http* requests.
+ */
+
+ const { AppConstants } = SpecialPowers.ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+ );
+ const splitRegex = /^(.*)-(.*)$/
+ const testConfig = {
+ topLevelScheme: "http://",
+ results: [
+ "iframe", "script", "img", "img-redir", "font", "xhr", "style",
+ "media", "object", "form", "nested-img","top-level"
+ ]
+ }
+
+
+ function runTest() {
+ // sends an xhr request to the server which is processed async, which only
+ // returns after the server has received all the expected requests.
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "file_upgrade_insecure_server.sjs?queryresult");
+ myXHR.onload = function (e) {
+ var results = myXHR.responseText.split(",");
+ for (var index in results) {
+ checkResult(results[index]);
+ }
+ }
+ myXHR.onerror = function (e) {
+ ok(false, "Could not query results from server (" + e.message + ")");
+ finishTest();
+ }
+ myXHR.send();
+
+ // give it some time and run the testpage
+ SimpleTest.executeSoon(() => {
+ var src = testConfig.topLevelScheme + "example.com/tests/dom/security/test/https-first/file_upgrade_insecure.html";
+ document.getElementById("testframe").src = src;
+ });
+ }
+
+ // a postMessage handler that is used by sandboxed iframes without
+ // 'allow-same-origin' to bubble up results back to this main page.
+ window.addEventListener("message", receiveMessage);
+ function receiveMessage(event) {
+ checkResult(event.data.result);
+ }
+
+ function finishTest() {
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+ }
+
+ function checkResult(response) {
+ // A response looks either like this "iframe-ok" or "[key]-[result]"
+ const [, key, result] = splitRegex.exec(response)
+ // try to find the expected result within the results array
+ var index = testConfig.results.indexOf(key);
+
+ // If the response is not even part of the results array, something is super wrong
+ if (index == -1) {
+ ok(false, `Unexpected response from server (${response})`);
+ finishTest();
+ }
+
+ // take the element out the array and continue till the results array is empty
+ if (index != -1) {
+ testConfig.results.splice(index, 1);
+ }
+
+ // Check if the result was okay or had an error
+ is(result, 'ok', `Upgrade all requests on toplevel http for '${key}' came back with: '${result}'`)
+
+ // If we're not expecting any more resulsts, finish the test
+ if (!testConfig.results.length) {
+ finishTest();
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ // Set preference and start test
+ SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_first", true],
+ ["security.mixed_content.block_active_content", false],
+ ["security.mixed_content.block_display_content", false]
+ ] }, runTest);
+
+ </script>
+</body>
+
+</html>
diff --git a/dom/security/test/https-first/test_toplevel_cookies.html b/dom/security/test/https-first/test_toplevel_cookies.html
new file mode 100644
index 0000000000..2c0c64db46
--- /dev/null
+++ b/dom/security/test/https-first/test_toplevel_cookies.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Bug 1711453 : HTTPS-First: Add test for cookies </title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+"use strict";
+/*
+ * Description of the test:
+ * We perform each test with 4 different cookie settings and
+ * expect https-first to detect which cookie is same origin and
+ * which is cross origin. The cookies are in an image or in a frame.
+ * The 4 cookie settings differ in two flags which are set or not.
+ * The first call is always with secure flag not set and sameSite=none
+ * In the second call we don't set the secure flag but sameSite=strict
+ * In the third call we set the secure flag and sameSite=none
+ * In the forth call we set the secure flag and sameSite=strict
+ * More detailed:
+ * We run the tests in the following order.
+ * Test 1a: Image is loaded with cookie same-origin, not secure and sameSite=none
+ * Test 1b: Image is loaded with cookie same-origin, not secure and sameSite=strict
+ * Test 1c: Image is loaded with cookie same-origin, secure and sameSite=none
+ * Test 1d: Image is loaded with cookie same-origin, secure and sameSite=strict
+ * Test 1e: Image is loaded with cookie cross-origin, not secure and sameSite=none
+ * Test 1f: Image is loaded with cookie cross-origin, not secure and sameSite=strict
+ * Test 2a: Load frame navigation with cookie same-origin, not secure and sameSite=none
+ * ...
+ * Test 3a: Load frame navigation blank with cookie same-origin, not secure and sameSite=none
+ * ...
+ * Test 4a: Load frame Inc with cookie same-origin, not secure and sameSite=none
+ * ...
+ * Test 5a: Load frame Inc Blank with cookie same-origin, not secure and sameSite=none
+ * ...
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+const SAME_ORIGIN =
+ "http://example.com/tests/dom/security/test/https-first/file_toplevel_cookies.sjs?";
+
+const CROSS_ORIGIN =
+ "http://example.org/tests/dom/security/test/https-first/file_toplevel_cookies.sjs?";
+
+const redirectQueries = ["setImage", "loadNav", "loadNavBlank","loadframeInc", "loadframeIncBlank"];
+let currentTest = 0;
+let sameOriginRequest = true;
+let testWin;
+let currentQuery;
+window.addEventListener("message", receiveMessage);
+let currentRun = 0;
+// All possible cookie attribute combinations
+// cookie attributes are secure=set/not set and sameSite= none/ strict
+const ALL_COOKIE_COMB = ["notSecure,none", "notSecure,strict", "secure,none", "secure,strict"]
+
+// Receive message and verify that it is from an https site.
+// When the message is 'upgraded' then it was send by an https site
+// and validate that we received the right cookie. Verify that for a cross
+//origin request we didn't receive a cookie.
+async function receiveMessage(event) {
+ let data = event.data;
+ currentQuery = redirectQueries[currentTest];
+ ok(data.result === "upgraded", "Upgraded successful to https for " + currentQuery);
+ ok(data.loc.includes("https"), "scheme is 'https' for " + currentQuery );
+ if (!sameOriginRequest) {
+ ok(data.cookie === "", "Cookie from cross-Origin site shouldn't be accepted " + currentQuery + " " + ALL_COOKIE_COMB[currentRun]);
+ } else {
+ is(data.cookie.includes(currentQuery + "=" + currentRun), true, "Cookie successfully arrived for " + currentQuery + " " + ALL_COOKIE_COMB[currentRun]);
+ }
+ testWin.close();
+ currentRun++;
+ if (currentTest >= redirectQueries.length -1 && currentRun === ALL_COOKIE_COMB.length && !sameOriginRequest) {
+ window.removeEventListener("message", receiveMessage);
+ SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
+ SimpleTest.finish();
+ return;
+ }
+ runTest();
+}
+
+async function runTest() {
+ currentQuery = redirectQueries[currentTest];
+ // send same origin request
+ if (sameOriginRequest && currentRun < ALL_COOKIE_COMB.length) {
+ testWin = window.open(SAME_ORIGIN + currentQuery + currentRun, "_blank");
+ } else {
+ // if same origin isn't set, check if we need to send cross origin requests
+ // eslint-disable-next-line no-lonely-if
+ if (!sameOriginRequest && currentRun < ALL_COOKIE_COMB.length ) {
+ testWin = window.open(CROSS_ORIGIN + currentQuery + currentRun, "_blank");
+ } // else we completed all test case of the current query for the current origin. Prepare and call next test
+ else {
+ // reset currentRun and go to next query
+ currentRun = 0;
+ if(!sameOriginRequest){
+ currentTest++;
+ }
+ // run same test again for crossOrigin or start new test with sameOrigin
+ sameOriginRequest = !sameOriginRequest;
+ currentQuery = redirectQueries[currentTest];
+ runTest();
+ }
+ }
+}
+
+SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_first", true],
+ ["network.cookie.sameSite.noneRequiresSecure", false],
+ ]}, runTest);
+
+</script>
+</body>
+</html>