Bug 1620242 - Basic implementation for HTTPS Only Mode. r=ckerschb,mixedpuppy
authorJulianWels <julianwels@mozilla.com>
Tue, 17 Mar 2020 19:24:31 +0000
changeset 519221 f6d98a73d50af419173fbfbad9911221b7e1c42f
parent 519220 ba5d4333f967382444f4d7a905e138f226e54dbe
child 519222 1f21bc6af7282ef11073f7e536e39a1c6fe12dc2
push id37224
push userbtara@mozilla.com
push dateWed, 18 Mar 2020 04:14:11 +0000
treeherdermozilla-central@70f8ce3e2d39 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckerschb, mixedpuppy
bugs1620242
milestone76.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1620242 - Basic implementation for HTTPS Only Mode. r=ckerschb,mixedpuppy Differential Revision: https://phabricator.services.mozilla.com/D62590
.eslintrc.js
dom/locales/en-US/chrome/security/security.properties
dom/security/CSPEvalChecker.cpp
dom/security/moz.build
dom/security/nsHTTPSOnlyUtils.cpp
dom/security/nsHTTPSOnlyUtils.h
dom/security/nsMixedContentBlocker.cpp
dom/security/test/https-only/file_redirect.sjs
dom/security/test/https-only/file_upgrade_insecure.html
dom/security/test/https-only/file_upgrade_insecure_server.sjs
dom/security/test/https-only/file_upgrade_insecure_wsh.py
dom/security/test/https-only/mochitest.ini
dom/security/test/https-only/test_redirect_upgrade.html
dom/security/test/https-only/test_resource_upgrade.html
dom/security/test/moz.build
dom/websocket/WebSocket.cpp
ipc/glue/BackgroundUtils.cpp
modules/libpref/init/StaticPrefList.yaml
netwerk/base/LoadInfo.cpp
netwerk/base/LoadInfo.h
netwerk/base/NetworkConnectivityService.cpp
netwerk/base/nsILoadInfo.idl
netwerk/base/nsNetUtil.cpp
netwerk/ipc/NeckoChannelParams.ipdlh
netwerk/protocol/http/nsCORSListenerProxy.cpp
security/manager/ssl/nsNSSCallbacks.cpp
toolkit/components/captivedetect/CaptiveDetect.jsm
toolkit/modules/GMPInstallManager.jsm
toolkit/mozapps/extensions/internal/ProductAddonChecker.jsm
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -248,16 +248,17 @@ module.exports = {
         "dom/network/**",
         "dom/payments/**",
         "dom/performance/**",
         "dom/permission/**",
         "dom/quota/**",
         "dom/security/test/cors/**",
         "dom/security/test/csp/**",
         "dom/security/test/general/**",
+        "dom/security/test/https-only/**",
         "dom/security/test/mixedcontentblocker/**",
         "dom/security/test/sri/**",
         "dom/security/test/referrer-policy/**",
         "dom/serviceworkers/**",
         "dom/smil/**",
         "dom/tests/mochitest/**",
         "dom/u2f/**",
         "dom/vr/**",
--- a/dom/locales/en-US/chrome/security/security.properties
+++ b/dom/locales/en-US/chrome/security/security.properties
@@ -137,8 +137,14 @@ ReferrerOriginLengthOverLimitation=HTTP Referrer header: Length of origin within referrer is over “%1$S” bytes limit - removing referrer with origin “%2$S”.
 
 # X-Frame-Options
 # LOCALIZATION NOTE: %1$S is the header value, %2$S is frame URI and %3$S is the parent document URI.
 XFOInvalid = Invalid X-Frame-Options: “%1$S” header from “%2$S” loaded into “%3$S”.
 # LOCALIZATION NOTE: %1$S is the header value, %2$S is frame URI and %3$S is the parent document URI.
 XFODeny = Load denied by X-Frame-Options: “%1$S” from “%2$S”, site does not permit any framing. Attempted to load into “%3$S”.
 # LOCALIZATION NOTE: %1$S is the header value, %2$S is frame URI and %3$S is the parent document URI.
 XFOSameOrigin = Load denied by X-Frame-Options: “%1$S” from “%2$S”, site does not permit cross-origin framing from “%3$S”.
+
+# HTTPS-Only Mode
+# LOCALIZATION NOTE: %1$S is the URL of the upgraded request; %2$S is the upgraded scheme.
+HTTPSOnlyUpgradeRequest = Upgrading insecure request “%1$S” to use “%2$S”.
+# LOCALIZATION NOTE: %1$S is the URL of request.
+HTTPSOnlyNoUpgrade = Request for “%1$S” was not upgraded because it had the NoUpgrade-flag.
--- a/dom/security/CSPEvalChecker.cpp
+++ b/dom/security/CSPEvalChecker.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/CSPEvalChecker.h"
 #include "mozilla/dom/Document.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerRunnable.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/ErrorResult.h"
+#include "nsIParentChannel.h"
 #include "nsGlobalWindowInner.h"
 #include "nsContentSecurityUtils.h"
 #include "nsContentUtils.h"
 #include "nsCOMPtr.h"
 #include "nsJSUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
--- a/dom/security/moz.build
+++ b/dom/security/moz.build
@@ -16,16 +16,17 @@ EXPORTS.mozilla.dom += [
     'DOMSecurityManager.h',
     'DOMSecurityMonitor.h',
     'FramingChecker.h',
     'nsContentSecurityManager.h',
     'nsContentSecurityUtils.h',
     'nsCSPContext.h',
     'nsCSPService.h',
     'nsCSPUtils.h',
+    'nsHTTPSOnlyUtils.h',
     'nsMixedContentBlocker.h',
     'PolicyTokenizer.h',
     'ReferrerInfo.h',
     'SecFetch.h',
     'SRICheck.h',
     'SRILogHelper.h',
     'SRIMetadata.h',
 ]
@@ -43,16 +44,17 @@ UNIFIED_SOURCES += [
     'DOMSecurityMonitor.cpp',
     'FramingChecker.cpp',
     'nsContentSecurityManager.cpp',
     'nsContentSecurityUtils.cpp',
     'nsCSPContext.cpp',
     'nsCSPParser.cpp',
     'nsCSPService.cpp',
     'nsCSPUtils.cpp',
+    'nsHTTPSOnlyUtils.cpp',
     'nsMixedContentBlocker.cpp',
     'PolicyTokenizer.cpp',
     'ReferrerInfo.cpp',
     'SecFetch.cpp',
     'SRICheck.cpp',
     'SRIMetadata.cpp',
 ]
 
new file mode 100644
--- /dev/null
+++ b/dom/security/nsHTTPSOnlyUtils.cpp
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/StaticPrefs_dom.h"
+#include "nsContentUtils.h"
+#include "nsHTTPSOnlyUtils.h"
+#include "nsIConsoleService.h"
+#include "nsIScriptError.h"
+
+/* static */
+bool nsHTTPSOnlyUtils::ShouldUpgradeRequest(nsIURI* aURI,
+                                            nsILoadInfo* aLoadInfo) {
+  // 1. Check if HTTPS-Only mode is enabled
+  if (!mozilla::StaticPrefs::dom_security_https_only_mode()) {
+    return false;
+  }
+  // 2. Check if NoUpgrade-flag is set in LoadInfo
+  if (aLoadInfo->GetHttpsOnlyNoUpgrade()) {
+    // Let's log to the console, that we didn't upgrade this request
+    uint32_t innerWindowId = aLoadInfo->GetInnerWindowID();
+    AutoTArray<nsString, 2> params = {
+        NS_ConvertUTF8toUTF16(aURI->GetSpecOrDefault())};
+    nsHTTPSOnlyUtils::LogLocalizedString(
+        "HTTPSOnlyNoUpgrade", params, nsIScriptError::infoFlag, innerWindowId,
+        !!aLoadInfo->GetOriginAttributes().mPrivateBrowsingId, aURI);
+    return false;
+  }
+
+  // 3. Upgrade the request
+
+  // Let's log it to the console
+  // Append the additional 's' just for the logging
+  nsAutoCString scheme;
+  aURI->GetScheme(scheme);
+  scheme.AppendLiteral("s");
+  NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
+  NS_ConvertUTF8toUTF16 reportScheme(scheme);
+
+  uint32_t innerWindowId = aLoadInfo->GetInnerWindowID();
+  AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
+  nsHTTPSOnlyUtils::LogLocalizedString(
+      "HTTPSOnlyUpgradeRequest", params, nsIScriptError::warningFlag,
+      innerWindowId, !!aLoadInfo->GetOriginAttributes().mPrivateBrowsingId,
+      aURI);
+
+  return true;
+}
+
+/** Logging **/
+
+/* static */
+void nsHTTPSOnlyUtils::LogLocalizedString(
+    const char* aName, const nsTArray<nsString>& aParams, uint32_t aFlags,
+    uint64_t aInnerWindowID, bool aFromPrivateWindow, nsIURI* aURI) {
+  nsAutoString logMsg;
+  nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES,
+                                        aName, aParams, logMsg);
+  LogMessage(logMsg, aFlags, aInnerWindowID, aFromPrivateWindow, aURI);
+}
+
+/* static */
+void nsHTTPSOnlyUtils::LogMessage(const nsAString& aMessage, uint32_t aFlags,
+                                  uint64_t aInnerWindowID,
+                                  bool aFromPrivateWindow, nsIURI* aURI) {
+  // Prepending HTTPS-Only to the outgoing console message
+  nsString message;
+  message.AppendLiteral(u"HTTPS-Only Mode: ");
+  message.Append(aMessage);
+
+  // Allow for easy distinction in devtools code.
+  nsCString category("HTTPSOnly");
+
+  if (aInnerWindowID > 0) {
+    // Send to content console
+    nsContentUtils::ReportToConsoleByWindowID(message, aFlags, category,
+                                              aInnerWindowID, aURI);
+  } else {
+    // Send to browser console
+    LogSimpleConsoleError(message, category.get(), aFromPrivateWindow,
+                          true /* from chrome context */, aFlags);
+  }
+}
+
+/* static */
+void nsHTTPSOnlyUtils::LogSimpleConsoleError(const nsAString& aErrorText,
+                                             const char* aCategory,
+                                             bool aFromPrivateWindow,
+                                             bool aFromChromeContext,
+                                             uint32_t aErrorFlags) {
+  nsCOMPtr<nsIScriptError> scriptError =
+      do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
+  if (!scriptError) {
+    return;
+  }
+  nsCOMPtr<nsIConsoleService> console =
+      do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+  if (!console) {
+    return;
+  }
+  nsresult rv = scriptError->Init(aErrorText, EmptyString(), EmptyString(), 0,
+                                  0, aErrorFlags, aCategory, aFromPrivateWindow,
+                                  aFromChromeContext);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+  console->LogMessage(scriptError);
+}
new file mode 100644
--- /dev/null
+++ b/dom/security/nsHTTPSOnlyUtils.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHTTPSOnlyUtils_h___
+#define nsHTTPSOnlyUtils_h___
+
+#include "nsIScriptError.h"
+
+class nsHTTPSOnlyUtils {
+ public:
+  /**
+   * Determines if a request should get because of the HTTPS-Only mode
+   * @param  aURI           nsIURI of request
+   * @param  aLoadInfo      nsILoadInfo of request
+   * @param  aShouldUpgrade true if request should get upgraded
+   */
+  static bool ShouldUpgradeRequest(nsIURI* aURI, nsILoadInfo* aLoadInfo);
+
+  /**
+   * Logs localized message to either content console or browser console
+   * @param aName              Localization key
+   * @param aParams            Localization parameters
+   * @param aFlags             Logging Flag (see nsIScriptError)
+   * @param aInnerWindowID     Inner Window ID (Logged on browser console if 0)
+   * @param aFromPrivateWindow If from private window
+   * @param [aURI]             Optional: URI to log
+   */
+  static void LogLocalizedString(const char* aName,
+                                 const nsTArray<nsString>& aParams,
+                                 uint32_t aFlags, uint64_t aInnerWindowID,
+                                 bool aFromPrivateWindow,
+                                 nsIURI* aURI = nullptr);
+
+ private:
+  /**
+   * Logs localized message to either content console or browser console
+   * @param aMessage           Message to log
+   * @param aFlags             Logging Flag (see nsIScriptError)
+   * @param aInnerWindowID     Inner Window ID (Logged on browser console if 0)
+   * @param aFromPrivateWindow If from private window
+   * @param [aURI]             Optional: URI to log
+   */
+  static void LogMessage(const nsAString& aMessage, uint32_t aFlags,
+                         uint64_t aInnerWindowID, bool aFromPrivateWindow,
+                         nsIURI* aURI = nullptr);
+
+  /**
+   * Report simple error message to the browser console
+   *   @param aErrorText the error message
+   *   @param aCategory Name of the module reporting error
+   *   @param aFromPrivateWindow Whether from private window or not
+   *   @param aFromChromeContext Whether from chrome context or not
+   *   @param [aErrorFlags] See nsIScriptError.
+   */
+  static void LogSimpleConsoleError(
+      const nsAString& aErrorText, const char* aCategory,
+      bool aFromPrivateWindow, bool aFromChromeContext,
+      uint32_t aErrorFlags = nsIScriptError::errorFlag);
+};
+
+#endif /* nsHTTPSOnlyUtils_h___ */
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -14,16 +14,18 @@
 #include "nsDocShell.h"
 #include "nsIWebProgressListener.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/Document.h"
 #include "nsIChannel.h"
 #include "nsIParentChannel.h"
 #include "mozilla/Preferences.h"
 #include "nsIScriptObjectPrincipal.h"
+#include "nsIProtocolHandler.h"
+#include "nsCharSeparatedTokenizer.h"
 #include "nsISecureBrowserUI.h"
 #include "nsIWebNavigation.h"
 #include "nsLoadGroup.h"
 #include "nsIScriptError.h"
 #include "nsIURI.h"
 #include "nsIChannelEventSink.h"
 #include "nsNetUtil.h"
 #include "nsAsyncRedirectVerifyHelper.h"
@@ -35,16 +37,17 @@
 #include "mozilla/Logging.h"
 #include "mozilla/StaticPrefs_dom.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/net/DNS.h"
 
 using namespace mozilla;
+using namespace mozilla::dom;
 
 enum nsMixedContentBlockerMessageType { eBlocked = 0x00, eUserOverride = 0x01 };
 
 // Is mixed script blocking (fonts, plugin content, scripts, stylesheets,
 // iframes, websockets, XHR) enabled?
 bool nsMixedContentBlocker::sBlockMixedScript = false;
 
 bool nsMixedContentBlocker::sBlockMixedObjectSubrequest = false;
@@ -807,16 +810,22 @@ nsresult nsMixedContentBlocker::ShouldLo
   }
 
   bool isHttpScheme = innerContentLocation->SchemeIs("http");
   if (isHttpScheme && IsPotentiallyTrustworthyOrigin(innerContentLocation)) {
     *aDecision = ACCEPT;
     return NS_OK;
   }
 
+  // If https-only mode is enabled we'll upgrade this later anyway
+  if (StaticPrefs::dom_security_https_only_mode()) {
+    *aDecision = ACCEPT;
+    return NS_OK;
+  }
+
   // The page might have set the CSP directive 'upgrade-insecure-requests'. In
   // such a case allow the http: load to succeed with the promise that the
   // channel will get upgraded to https before fetching any data from the
   // netwerk. Please see: nsHttpChannel::Connect()
   //
   // Please note that the CSP directive 'upgrade-insecure-requests' only applies
   // to http: and ws: (for websockets). Websockets are not subject to mixed
   // content blocking since insecure websockets are not allowed within secure
copy from dom/security/test/csp/file_redirect_report.sjs
copy to dom/security/test/https-only/file_redirect.sjs
--- a/dom/security/test/csp/file_redirect_report.sjs
+++ b/dom/security/test/https-only/file_redirect.sjs
@@ -1,17 +1,37 @@
-// https://bugzilla.mozilla.org/show_bug.cgi?id=650386
-// This SJS file serves as CSP violation report target
-// and issues a redirect, to make sure the browser does not post to the target
-// of the redirect, per CSP spec.
-// This handles 301, 302, 303 and 307 redirects. The HTTP status code
-// returned/type of redirect to do comes from the query string
-// parameter
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1613063
+
+// 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_SECURE = "secure-ok";
+const RESPONSE_INSECURE = "secure-error";
+const RESPONSE_ERROR = "unexpected-query";
+
 function handleRequest(request, response) {
   response.setHeader("Cache-Control", "no-cache", false);
 
-  var redirect = request.queryString;
+  const query = request.queryString;
+
+  // Send redirect header
+  if ((query >= 301 && query <= 303) || query == 307) {
+    const loc =
+      "http://example.com/tests/dom/security/test/https-only/file_redirect.sjs?check";
+    response.setStatusLine(request.httpVersion, query, "Moved");
+    response.setHeader("Location", loc, false);
+    return;
+  }
 
-  var loc = "http://example.com/some/fake/path";
-  response.setStatusLine("1.1", redirect, "Found");
-  response.setHeader("Location", loc, false);
-  return;
+  // Check if scheme is http:// oder 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);
 }
copy from dom/security/test/csp/file_upgrade_insecure.html
copy to dom/security/test/https-only/file_upgrade_insecure.html
--- a/dom/security/test/csp/file_upgrade_insecure.html
+++ b/dom/security/test/https-only/file_upgrade_insecure.html
@@ -1,86 +1,87 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
-  <title>Bug 1139297 - Implement CSP upgrade-insecure-requests directive</title>
+  <title>Bug 1613063 - HTTPS Only Mode</title>
   <!-- style -->
-  <link rel='stylesheet' type='text/css' href='http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?style' media='screen' />
+  <link rel='stylesheet' type='text/css' href='http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?style' media='screen' />
 
   <!-- font -->
   <style>
     @font-face {
       font-family: "foofont";
-      src: url('http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?font');
+      src: url('http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?font');
     }
     .div_foo { font-family: "foofont"; }
   </style>
 </head>
 <body>
 
   <!-- images: -->
-  <img src="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?img"></img>
+  <img src="http://example.com/tests/dom/security/test/https-only/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/csp/file_upgrade_insecure_server.sjs?redirect-image"></img>
+  <img src="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?redirect-image"></img>
 
   <!-- script: -->
-  <script src="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?script"></script>
+  <script src="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?script"></script>
 
   <!-- media: -->
-  <audio src="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?media"></audio>
+  <audio src="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?media"></audio>
 
   <!-- objects: -->
-  <object width="10" height="10" data="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?object"></object>
+  <object width="10" height="10" data="http://example.com/tests/dom/security/test/https-only/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/csp/file_upgrade_insecure_server.sjs?iframe">
+  <iframe src="http://example.com/tests/dom/security/test/https-only/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>
 
   <!-- xhr: -->
   <script type="application/javascript">
     var myXHR = new XMLHttpRequest();
-    myXHR.open("GET", "http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?xhr");
+    myXHR.open("GET", "http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?xhr");
     myXHR.send(null);
   </script>
 
   <!-- websockets: upgrade ws:// to wss://-->
   <script type="application/javascript">
     // WebSocket tests are not supported on Android yet. Bug 1566168
     const {AppConstants} = SpecialPowers.Cu.import("resource://gre/modules/AppConstants.jsm", {});
     if (AppConstants.platform !== "android") {
-      var mySocket = new WebSocket("ws://example.com/tests/dom/security/test/csp/file_upgrade_insecure");
+      var mySocket = new WebSocket("ws://example.com/tests/dom/security/test/https-only/file_upgrade_insecure");
       mySocket.onopen = function(e) {
         if (mySocket.url.includes("wss://")) {
           window.parent.postMessage({result: "websocket-ok"}, "*");
         }
         else {
           window.parent.postMessage({result: "websocket-error"}, "*");
         }
         mySocket.close();
       };
       mySocket.onerror = function(e) {
         // debug information for Bug 1316305
         dump("  xxx mySocket.onerror: (mySocket): " + mySocket + "\n");
         dump("  xxx mySocket.onerror: (mySocket.url): " + mySocket.url + "\n");
         dump("  xxx mySocket.onerror: (e): " + e + "\n");
         dump("  xxx mySocket.onerror: (e.message): " + e.message + "\n");
+        dump("  xxx mySocket.onerror: This might be related to Bug 1316305!\n");
         window.parent.postMessage({result: "websocket-unexpected-error"}, "*");
       };
     }
   </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/csp/file_upgrade_insecure_server.sjs?form" method="POST">
+  <form target="formFrame" action="http://example.com/tests/dom/security/test/https-only/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>
 
copy from dom/security/test/csp/file_upgrade_insecure_server.sjs
copy to dom/security/test/https-only/file_upgrade_insecure_server.sjs
--- a/dom/security/test/csp/file_upgrade_insecure_server.sjs
+++ b/dom/security/test/https-only/file_upgrade_insecure_server.sjs
@@ -1,29 +1,39 @@
-// Custom *.sjs file specifically for the needs of Bug:
-// Bug 1139297 - Implement CSP upgrade-insecure-requests directive
+// SJS file for HTTPS-Only Mode mochitests
+// Bug 1613063 - HTTPS Only Mode
 
 const TOTAL_EXPECTED_REQUESTS = 11;
 
 const IFRAME_CONTENT =
   "<!DOCTYPE HTML>" +
   "<html>" +
   "<head><meta charset='utf-8'>" +
-  "<title>Bug 1139297 - Implement CSP upgrade-insecure-requests directive</title>" +
+  "<title>Bug 1613063 - HTTPS Only Mode</title>" +
   "</head>" +
   "<body>" +
-  "<img src='http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?nested-img'></img>" +
+  "<img src='http://example.com/tests/dom/security/test/https-only/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"];
+const expectedQueries = [
+  "script",
+  "style",
+  "img",
+  "iframe",
+  "form",
+  "xhr",
+  "media",
+  "object",
+  "font",
+  "img-redir",
+  "nested-img",
+];
 
-function handleRequest(request, response)
-{
+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") {
@@ -32,30 +42,30 @@ function handleRequest(request, response
     response.processAsync();
     setObjectState("queryResult", response);
     return;
   }
 
   // handle img redirect (https->http)
   if (queryString == "redirect-image") {
     var newLocation =
-      "http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?img-redir";
+      "http://example.com/tests/dom/security/test/https-only/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.indexOf(queryString) == -1) {
-    response.write("doh!");
+    response.write("unexpected-response");
     return;
   }
 
   // make sure all the requested queries are indeed https
-  queryString += (request.scheme == "https") ? "-ok" : "-error";
+  queryString += request.scheme == "https" ? "-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
@@ -78,17 +88,17 @@ function handleRequest(request, response
   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")) {
+  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) {
copy from dom/security/test/csp/file_upgrade_insecure_wsh.py
copy to dom/security/test/https-only/file_upgrade_insecure_wsh.py
new file mode 100644
--- /dev/null
+++ b/dom/security/test/https-only/mochitest.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+support-files =
+  file_redirect.sjs
+  file_upgrade_insecure.html
+  file_upgrade_insecure_server.sjs
+  file_upgrade_insecure_wsh.py
+prefs =
+  security.mixed_content.upgrade_display_content=false
+
+[test_resource_upgrade.html]
+scheme=https
+[test_redirect_upgrade.html]
+scheme=https
new file mode 100644
--- /dev/null
+++ b/dom/security/test/https-only/test_redirect_upgrade.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1613063
+Test that 302 redirect requests get upgraded to https:// with HTTPS-Only Mode enabled
+-->
+
+<head>
+  <title>HTTPS-Only Mode - XHR 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-Only Mode</h1>
+  <p>Upgrade Test for insecure XHR redirects.</p>
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1613063">Bug 1613063</a>
+
+  <script type="application/javascript">
+
+    const redirectCodes = ["301", "302", "303", "307"]
+    let currentTest = 0
+
+    function startTest() {
+      const currentCode = redirectCodes[currentTest];
+
+      const myXHR = new XMLHttpRequest();
+      // 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.
+      myXHR.open("GET", `https://example.com/tests/dom/security/test/https-only/file_redirect.sjs?${currentCode}`);
+      myXHR.onload = (e) => {
+        is(myXHR.responseText, "secure-ok", `a ${currentCode} redirect when posting violation report should be blocked`)
+        testDone();
+      }
+      // This should not happen
+      myXHR.onerror = (e) => {
+        ok(false, `Could not query results from server for ${currentCode}-redirect test (" + e.message + ")`);
+        testDone();
+      }
+      myXHR.send();
+    }
+
+    function testDone() {
+      // Check if there are remaining tests
+      if (++currentTest < redirectCodes.length) {
+        startTest()
+      } else {
+        SimpleTest.finish();
+      }
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    // Set preference and start test
+    SpecialPowers.pushPrefEnv({ set: [["dom.security.https_only_mode", true]] }, startTest);
+
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/https-only/test_resource_upgrade.html
@@ -0,0 +1,123 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  <title>HTTPS-Only Mode - Resource Upgrade</title>
+  <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+  <h1>HTTPS-Only Mode</h1>
+  <p>Upgrade Test for various resources</p>
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1613063">Bug 1613063</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
+     * that all the resources get upgraded to use >> https << when the
+     * preference "dom.security.https_only_mode" is set to true. We further
+     * test that subresources within nested contexts (iframes) get upgraded
+     * and also test the handling of server side redirects.
+     *
+     * 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 *https* requests.
+     */
+
+    const { AppConstants } = SpecialPowers.Cu.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"
+      ]
+    }
+    // TODO: WebSocket tests are not supported on Android Yet. Bug 1566168.
+    if (AppConstants.platform !== "android") {
+      testConfig.results.push("websocket");
+    }
+
+
+    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-only/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 == 0) {
+        finishTest();
+      }
+    }
+
+    SimpleTest.waitForExplicitFinish();
+
+    // Set preference and start test
+    SpecialPowers.pushPrefEnv({ set: [["dom.security.https_only_mode", true]] }, runTest);
+
+  </script>
+</body>
+
+</html>
--- a/dom/security/test/moz.build
+++ b/dom/security/test/moz.build
@@ -14,16 +14,17 @@ XPCSHELL_TESTS_MANIFESTS += [
 TEST_DIRS += [
     'gtest',
 ]
 
 MOCHITEST_MANIFESTS += [
     'cors/mochitest.ini',
     'csp/mochitest.ini',
     'general/mochitest.ini',
+    'https-only/mochitest.ini',
     'mixedcontentblocker/mochitest.ini',
     'referrer-policy/mochitest.ini',
     'sri/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'general/chrome.ini',
 ]
--- a/dom/websocket/WebSocket.cpp
+++ b/dom/websocket/WebSocket.cpp
@@ -14,23 +14,25 @@
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/net/WebSocketChannel.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/MessageEvent.h"
 #include "mozilla/dom/MessageEventBinding.h"
 #include "mozilla/dom/nsCSPContext.h"
 #include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
 #include "mozilla/dom/nsMixedContentBlocker.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/SerializedStackHolder.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerRef.h"
 #include "mozilla/dom/WorkerRunnable.h"
 #include "mozilla/dom/WorkerScope.h"
+#include "mozilla/StaticPrefs_dom.h"
 #include "nsAutoPtr.h"
 #include "mozilla/LoadInfo.h"
 #include "nsGlobalWindow.h"
 #include "nsIScriptGlobalObject.h"
 #include "mozilla/dom/Document.h"
 #include "nsXPCOM.h"
 #include "nsContentUtils.h"
 #include "nsError.h"
@@ -1573,16 +1575,36 @@ nsresult WebSocketImpl::Init(JSContext* 
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (NS_CP_REJECTED(shouldLoad)) {
       // Disallowed by content policy
       return NS_ERROR_CONTENT_BLOCKED;
     }
   }
 
+  // If the HTTPS-Only mode is enabled, we need to upgrade the websocket
+  // connection from ws:// to wss:// and mark it as secure.
+  if (!mIsServerSide && !mSecure &&
+      StaticPrefs::dom_security_https_only_mode()) {
+    // let's use the old specification before the upgrade for logging
+    AutoTArray<nsString, 2> params;
+    CopyUTF8toUTF16(mURI, *params.AppendElement());
+
+    mURI.ReplaceSubstring("ws://", "wss://");
+    if (NS_WARN_IF(mURI.Find("wss://") != 0)) {
+      return NS_OK;
+    }
+    mSecure = true;
+
+    params.AppendElement(NS_LITERAL_STRING("wss"));
+    nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyUpgradeInsecureRequest",
+                                         params, nsIScriptError::warningFlag,
+                                         mInnerWindowID, mPrivateBrowsing);
+  }
+
   // Potentially the page uses the CSP directive 'upgrade-insecure-requests'.
   // In such a case we have to upgrade ws: to wss: and also update mSecure
   // to reflect that upgrade. Please note that we can not upgrade from ws:
   // to wss: before performing content policy checks because CSP needs to
   // send reports in case the scheme is about to be upgraded.
   if (!mIsServerSide && !mSecure && originDoc &&
       originDoc->GetUpgradeInsecureRequests(false)) {
     // let's use the old specification before the upgrade for logging
--- a/ipc/glue/BackgroundUtils.cpp
+++ b/ipc/glue/BackgroundUtils.cpp
@@ -575,16 +575,17 @@ nsresult LoadInfoToLoadInfoArgs(nsILoadI
       ipcReservedClientInfo, ipcInitialClientInfo, ipcController,
       aLoadInfo->CorsUnsafeHeaders(), aLoadInfo->GetForcePreflight(),
       aLoadInfo->GetIsPreflight(), aLoadInfo->GetLoadTriggeredFromExternal(),
       aLoadInfo->GetServiceWorkerTaintingSynthesized(),
       aLoadInfo->GetDocumentHasUserInteracted(),
       aLoadInfo->GetDocumentHasLoaded(),
       aLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain(),
       cspNonce, aLoadInfo->GetSkipContentSniffing(),
+      aLoadInfo->GetHttpsOnlyNoUpgrade(),
       aLoadInfo->GetIsFromProcessingFrameAttributes(), cookieJarSettingsArgs,
       aLoadInfo->GetRequestBlockingReason(), maybeCspToInheritInfo));
 
   return NS_OK;
 }
 
 nsresult LoadInfoArgsToLoadInfo(
     const Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs,
@@ -772,32 +773,34 @@ nsresult LoadInfoArgsToLoadInfo(
       std::move(ancestorPrincipals), loadInfoArgs.ancestorOuterWindowIDs(),
       loadInfoArgs.corsUnsafeHeaders(), loadInfoArgs.forcePreflight(),
       loadInfoArgs.isPreflight(), loadInfoArgs.loadTriggeredFromExternal(),
       loadInfoArgs.serviceWorkerTaintingSynthesized(),
       loadInfoArgs.documentHasUserInteracted(),
       loadInfoArgs.documentHasLoaded(),
       loadInfoArgs.allowListFutureDocumentsCreatedFromThisRedirectChain(),
       loadInfoArgs.cspNonce(), loadInfoArgs.skipContentSniffing(),
-      loadInfoArgs.requestBlockingReason(), loadingContext);
+      loadInfoArgs.httpsOnlyNoUpgrade(), loadInfoArgs.requestBlockingReason(),
+      loadingContext);
 
   if (loadInfoArgs.isFromProcessingFrameAttributes()) {
     loadInfo->SetIsFromProcessingFrameAttributes();
   }
 
   loadInfo.forget(outLoadInfo);
   return NS_OK;
 }
 
 void LoadInfoToParentLoadInfoForwarder(
     nsILoadInfo* aLoadInfo, ParentLoadInfoForwarderArgs* aForwarderArgsOut) {
   if (!aLoadInfo) {
     *aForwarderArgsOut = ParentLoadInfoForwarderArgs(
         false, false, Nothing(), nsILoadInfo::TAINTING_BASIC,
         false,  // SkipContentSniffing
+        false,  // HttpsOnlyNoUpgrade
         false,  // serviceWorkerTaintingSynthesized
         false,  // documentHasUserInteracted
         false,  // documentHasLoaded
         false,  // allowListFutureDocumentsCreatedFromThisRedirectChain
         Maybe<CookieJarSettingsArgs>(),
         nsILoadInfo::BLOCKING_REASON_NONE);  // requestBlockingReason
     return;
   }
@@ -822,17 +825,17 @@ void LoadInfoToParentLoadInfoForwarder(
     CookieJarSettingsArgs args;
     cs->Serialize(args);
     cookieJarSettingsArgs = Some(args);
   }
 
   *aForwarderArgsOut = ParentLoadInfoForwarderArgs(
       aLoadInfo->GetAllowInsecureRedirectToDataURI(),
       aLoadInfo->GetBypassCORSChecks(), ipcController, tainting,
-      aLoadInfo->GetSkipContentSniffing(),
+      aLoadInfo->GetSkipContentSniffing(), aLoadInfo->GetHttpsOnlyNoUpgrade(),
       aLoadInfo->GetServiceWorkerTaintingSynthesized(),
       aLoadInfo->GetDocumentHasUserInteracted(),
       aLoadInfo->GetDocumentHasLoaded(),
       aLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain(),
       cookieJarSettingsArgs, aLoadInfo->GetRequestBlockingReason());
 }
 
 nsresult MergeParentLoadInfoForwarder(
@@ -861,16 +864,19 @@ nsresult MergeParentLoadInfoForwarder(
         static_cast<LoadTainting>(aForwarderArgs.tainting()));
   } else {
     aLoadInfo->MaybeIncreaseTainting(aForwarderArgs.tainting());
   }
 
   rv = aLoadInfo->SetSkipContentSniffing(aForwarderArgs.skipContentSniffing());
   NS_ENSURE_SUCCESS(rv, rv);
 
+  rv = aLoadInfo->SetHttpsOnlyNoUpgrade(aForwarderArgs.httpsOnlyNoUpgrade());
+  NS_ENSURE_SUCCESS(rv, rv);
+
   MOZ_ALWAYS_SUCCEEDS(aLoadInfo->SetDocumentHasUserInteracted(
       aForwarderArgs.documentHasUserInteracted()));
   MOZ_ALWAYS_SUCCEEDS(
       aLoadInfo->SetDocumentHasLoaded(aForwarderArgs.documentHasLoaded()));
   MOZ_ALWAYS_SUCCEEDS(
       aLoadInfo->SetAllowListFutureDocumentsCreatedFromThisRedirectChain(
           aForwarderArgs
               .allowListFutureDocumentsCreatedFromThisRedirectChain()));
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -2401,16 +2401,23 @@
 # For testing purposes only; Flipping this pref to true allows
 # to skip the assertion that remote scripts can not be loaded
 # in system privileged contexts.
 - name: dom.security.skip_remote_script_assertion_in_system_priv_context
   type: RelaxedAtomicBool
   value: false
   mirror: always
 
+# If true, all content requests will get upgraded to HTTPS://
+# (some Firefox functionality requests, like OCSP will not be affected)
+- name: dom.security.https_only_mode
+  type: RelaxedAtomicBool
+  value: false
+  mirror: always
+
 # Is support for selection event APIs enabled?
 - name: dom.select_events.enabled
   type: bool
   value: true
   mirror: always
 
 - name: dom.separate_event_queue_for_post_message.enabled
   type: bool
--- a/netwerk/base/LoadInfo.cpp
+++ b/netwerk/base/LoadInfo.cpp
@@ -97,16 +97,17 @@ LoadInfo::LoadInfo(
       mForcePreflight(false),
       mIsPreflight(false),
       mLoadTriggeredFromExternal(false),
       mServiceWorkerTaintingSynthesized(false),
       mDocumentHasUserInteracted(false),
       mDocumentHasLoaded(false),
       mAllowListFutureDocumentsCreatedFromThisRedirectChain(false),
       mSkipContentSniffing(false),
+      mHttpsOnlyNoUpgrade(false),
       mIsFromProcessingFrameAttributes(false) {
   MOZ_ASSERT(mLoadingPrincipal);
   MOZ_ASSERT(mTriggeringPrincipal);
 
 #ifdef DEBUG
   // TYPE_DOCUMENT loads initiated by javascript tests will go through
   // nsIOService and use the wrong constructor.  Don't enforce the
   // !TYPE_DOCUMENT check in those cases
@@ -359,16 +360,17 @@ LoadInfo::LoadInfo(nsPIDOMWindowOuter* a
       mForcePreflight(false),
       mIsPreflight(false),
       mLoadTriggeredFromExternal(false),
       mServiceWorkerTaintingSynthesized(false),
       mDocumentHasUserInteracted(false),
       mDocumentHasLoaded(false),
       mAllowListFutureDocumentsCreatedFromThisRedirectChain(false),
       mSkipContentSniffing(false),
+      mHttpsOnlyNoUpgrade(false),
       mIsFromProcessingFrameAttributes(false) {
   // Top-level loads are never third-party
   // Grab the information we can out of the window.
   MOZ_ASSERT(aOuterWindow);
   MOZ_ASSERT(mTriggeringPrincipal);
 
   // if the load is sandboxed, we can not also inherit the principal
   if (mSandboxFlags & SANDBOXED_ORIGIN) {
@@ -459,16 +461,17 @@ LoadInfo::LoadInfo(dom::CanonicalBrowsin
       mForcePreflight(false),
       mIsPreflight(false),
       mLoadTriggeredFromExternal(false),
       mServiceWorkerTaintingSynthesized(false),
       mDocumentHasUserInteracted(false),
       mDocumentHasLoaded(false),
       mAllowListFutureDocumentsCreatedFromThisRedirectChain(false),
       mSkipContentSniffing(false),
+      mHttpsOnlyNoUpgrade(false),
       mIsFromProcessingFrameAttributes(false) {
   // Top-level loads are never third-party
   // Grab the information we can out of the window.
   MOZ_ASSERT(aBrowsingContext);
   MOZ_ASSERT(mTriggeringPrincipal);
   MOZ_ASSERT(aSecurityFlags !=
              nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK);
 
@@ -559,16 +562,17 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
       // redirect
       mServiceWorkerTaintingSynthesized(false),
       mDocumentHasUserInteracted(rhs.mDocumentHasUserInteracted),
       mDocumentHasLoaded(rhs.mDocumentHasLoaded),
       mAllowListFutureDocumentsCreatedFromThisRedirectChain(
           rhs.mAllowListFutureDocumentsCreatedFromThisRedirectChain),
       mCspNonce(rhs.mCspNonce),
       mSkipContentSniffing(rhs.mSkipContentSniffing),
+      mHttpsOnlyNoUpgrade(rhs.mHttpsOnlyNoUpgrade),
       mIsFromProcessingFrameAttributes(rhs.mIsFromProcessingFrameAttributes) {}
 
 LoadInfo::LoadInfo(
     nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
     nsIPrincipal* aPrincipalToInherit, nsIPrincipal* aSandboxedLoadingPrincipal,
     nsIPrincipal* aTopLevelPrincipal,
     nsIPrincipal* aTopLevelStorageAreaPrincipal, nsIURI* aResultPrincipalURI,
     nsICookieJarSettings* aCookieJarSettings,
@@ -596,17 +600,18 @@ LoadInfo::LoadInfo(
     nsTArray<nsCOMPtr<nsIPrincipal>>&& aAncestorPrincipals,
     const nsTArray<uint64_t>& aAncestorOuterWindowIDs,
     const nsTArray<nsCString>& aCorsUnsafeHeaders, bool aForcePreflight,
     bool aIsPreflight, bool aLoadTriggeredFromExternal,
     bool aServiceWorkerTaintingSynthesized, bool aDocumentHasUserInteracted,
     bool aDocumentHasLoaded,
     bool aAllowListFutureDocumentsCreatedFromThisRedirectChain,
     const nsAString& aCspNonce, bool aSkipContentSniffing,
-    uint32_t aRequestBlockingReason, nsINode* aLoadingContext)
+    bool aHttpsOnlyNoUpgrade, uint32_t aRequestBlockingReason,
+    nsINode* aLoadingContext)
     : mLoadingPrincipal(aLoadingPrincipal),
       mTriggeringPrincipal(aTriggeringPrincipal),
       mPrincipalToInherit(aPrincipalToInherit),
       mTopLevelPrincipal(aTopLevelPrincipal),
       mTopLevelStorageAreaPrincipal(aTopLevelStorageAreaPrincipal),
       mResultPrincipalURI(aResultPrincipalURI),
       mCookieJarSettings(aCookieJarSettings),
       mCspToInherit(aCspToInherit),
@@ -652,16 +657,17 @@ LoadInfo::LoadInfo(
       mLoadTriggeredFromExternal(aLoadTriggeredFromExternal),
       mServiceWorkerTaintingSynthesized(aServiceWorkerTaintingSynthesized),
       mDocumentHasUserInteracted(aDocumentHasUserInteracted),
       mDocumentHasLoaded(aDocumentHasLoaded),
       mAllowListFutureDocumentsCreatedFromThisRedirectChain(
           aAllowListFutureDocumentsCreatedFromThisRedirectChain),
       mCspNonce(aCspNonce),
       mSkipContentSniffing(aSkipContentSniffing),
+      mHttpsOnlyNoUpgrade(aHttpsOnlyNoUpgrade),
       mIsFromProcessingFrameAttributes(false) {
   // Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal
   MOZ_ASSERT(mLoadingPrincipal ||
              aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT);
   MOZ_ASSERT(mTriggeringPrincipal);
 
   mRedirectChainIncludingInternalRedirects.SwapElements(
       aRedirectChainIncludingInternalRedirects);
@@ -1458,16 +1464,28 @@ LoadInfo::GetSkipContentSniffing(bool* a
 
 NS_IMETHODIMP
 LoadInfo::SetSkipContentSniffing(bool aSkipContentSniffing) {
   mSkipContentSniffing = aSkipContentSniffing;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+LoadInfo::GetHttpsOnlyNoUpgrade(bool* aHttpsOnlyNoUpgrade) {
+  *aHttpsOnlyNoUpgrade = mHttpsOnlyNoUpgrade;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetHttpsOnlyNoUpgrade(bool aHttpsOnlyNoUpgrade) {
+  mHttpsOnlyNoUpgrade = aHttpsOnlyNoUpgrade;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 LoadInfo::GetIsTopLevelLoad(bool* aResult) {
   *aResult = mFrameOuterWindowID ? mFrameOuterWindowID == mOuterWindowID
                                  : mParentOuterWindowID == mOuterWindowID;
   return NS_OK;
 }
 
 void LoadInfo::SetIsFromProcessingFrameAttributes() {
   mIsFromProcessingFrameAttributes = true;
--- a/netwerk/base/LoadInfo.h
+++ b/netwerk/base/LoadInfo.h
@@ -158,17 +158,18 @@ class LoadInfo final : public nsILoadInf
            nsTArray<nsCOMPtr<nsIPrincipal>>&& aAncestorPrincipals,
            const nsTArray<uint64_t>& aAncestorOuterWindowIDs,
            const nsTArray<nsCString>& aUnsafeHeaders, bool aForcePreflight,
            bool aIsPreflight, bool aLoadTriggeredFromExternal,
            bool aServiceWorkerTaintingSynthesized,
            bool aDocumentHasUserInteracted, bool aDocumentHasLoaded,
            bool aAllowListFutureDocumentsCreatedFromThisRedirectChain,
            const nsAString& aCspNonce, bool aSkipContentSniffing,
-           uint32_t aRequestBlockingReason, nsINode* aLoadingContext);
+           bool aHttpsOnlyNoUpgrade, uint32_t aRequestBlockingReason,
+           nsINode* aLoadingContext);
   LoadInfo(const LoadInfo& rhs);
 
   NS_IMETHOD GetRedirects(JSContext* aCx,
                           JS::MutableHandle<JS::Value> aRedirects,
                           const RedirectHistoryArray& aArra);
 
   friend nsresult mozilla::ipc::LoadInfoArgsToLoadInfo(
       const Maybe<mozilla::net::LoadInfoArgs>& aLoadInfoArgs,
@@ -253,16 +254,17 @@ class LoadInfo final : public nsILoadInf
   bool mIsPreflight;
   bool mLoadTriggeredFromExternal;
   bool mServiceWorkerTaintingSynthesized;
   bool mDocumentHasUserInteracted;
   bool mDocumentHasLoaded;
   bool mAllowListFutureDocumentsCreatedFromThisRedirectChain;
   nsString mCspNonce;
   bool mSkipContentSniffing;
+  bool mHttpsOnlyNoUpgrade;
 
   // Is true if this load was triggered by processing the attributes of the
   // browsing context container.
   // See nsILoadInfo.isFromProcessingFrameAttributes
   bool mIsFromProcessingFrameAttributes;
 };
 
 }  // namespace net
--- a/netwerk/base/NetworkConnectivityService.cpp
+++ b/netwerk/base/NetworkConnectivityService.cpp
@@ -201,16 +201,22 @@ static inline already_AddRefed<nsIChanne
       nullptr,  // aLoadGroup
       nullptr,
       nsIRequest::LOAD_BYPASS_CACHE |    // don't read from the cache
           nsIRequest::INHIBIT_CACHING |  // don't write the response to cache
           nsIRequest::LOAD_ANONYMOUS);   // prevent privacy leaks
 
   channel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE);
 
+  {
+    // Prevent HTTPS-Only Mode from upgrading the OCSP request.
+    nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+    loadInfo->SetHttpsOnlyNoUpgrade(true);
+  }
+
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(channel);
   NS_ENSURE_TRUE(internalChan, nullptr);
 
   if (ipv4) {
     internalChan->SetIPv6Disabled();
   } else {
--- a/netwerk/base/nsILoadInfo.idl
+++ b/netwerk/base/nsILoadInfo.idl
@@ -402,26 +402,31 @@ interface nsILoadInfo : nsISupports
    */
   [infallible] readonly attribute unsigned long sandboxFlags;
 
   /**
    * Allows to query only the security mode bits from above.
    */
   [infallible] readonly attribute unsigned long securityMode;
 
-
   /**
    * This flag is used for any browsing context where we should not sniff
    * the content type. E.g if an iframe has the XCTO nosniff header, then
    * that flag is set to true so we skip content sniffing for that browsing
    * context.
    */
   [infallible] attribute boolean skipContentSniffing;
 
   /**
+   * If httpsOnlyNoUpgrade is true, the request won't get upgraded by the
+   * HTTPS-Only Mode.
+   */
+  [infallible] attribute boolean httpsOnlyNoUpgrade;
+
+  /**
    * True if this request is embedded in a context that can't be third-party
    * (i.e. an iframe embedded in a cross-origin parent window). If this is
    * false, then this request may be third-party if it's a third-party to
    * loadingPrincipal.
    */
   [infallible] readonly attribute boolean isInThirdPartyContext;
 
   /**
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -64,16 +64,17 @@
 #include "nsSyncStreamListener.h"
 #include "nsITextToSubURI.h"
 #include "nsIURIWithSpecialOrigin.h"
 #include "nsIViewSourceChannel.h"
 #include "nsInterfaceRequestorAgg.h"
 #include "plstr.h"
 #include "nsINestedURI.h"
 #include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
 #include "mozilla/dom/nsMixedContentBlocker.h"
 #include "mozilla/dom/BlobURLProtocolHandler.h"
 #include "mozilla/net/HttpBaseChannel.h"
 #include "nsIScriptError.h"
 #include "nsISiteSecurityService.h"
 #include "nsHttpHandler.h"
 #include "nsNSSComponent.h"
 #include "nsIRedirectHistoryEntry.h"
@@ -2834,16 +2835,22 @@ nsresult NS_ShouldSecureUpgrade(
   // data (it is read-only).
   // if the connection is not using SSL and either the exact host matches or
   // a superdomain wants to force HTTPS, do it.
   bool isHttps = aURI->SchemeIs("https");
 
   if (!isHttps &&
       !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aURI)) {
     if (aLoadInfo) {
+      // Check if the request can get upgraded with the HTTPS-Only mode
+      if (nsHTTPSOnlyUtils::ShouldUpgradeRequest(aURI, aLoadInfo)) {
+        aShouldUpgrade = true;
+        return NS_OK;
+      }
+
       // If any of the documents up the chain to the root document makes use of
       // the CSP directive 'upgrade-insecure-requests', then it's time to
       // fulfill the promise to CSP and mixed content blocking to upgrade the
       // channel from http to https.
       if (aLoadInfo->GetUpgradeInsecureRequests() ||
           aLoadInfo->GetBrowserUpgradeInsecureRequests()) {
         // let's log a message to the console that we are upgrading a request
         nsAutoCString scheme;
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -139,16 +139,17 @@ struct LoadInfoArgs
   bool                        isPreflight;
   bool                        loadTriggeredFromExternal;
   bool                        serviceWorkerTaintingSynthesized;
   bool                        documentHasUserInteracted;
   bool                        documentHasLoaded;
   bool allowListFutureDocumentsCreatedFromThisRedirectChain;
   nsString                    cspNonce;
   bool                        skipContentSniffing;
+  bool                        httpsOnlyNoUpgrade;
   bool                        isFromProcessingFrameAttributes;
   CookieJarSettingsArgs       cookieJarSettings;
   uint32_t                    requestBlockingReason;
   CSPInfo?                    cspToInheritInfo;
 };
 
 /**
  * This structure is used to carry selected properties of a LoadInfo
@@ -172,22 +173,25 @@ struct ParentLoadInfoForwarderArgs
   // The ServiceWorker controller that may be set in the parent when
   // interception occurs.
   IPCServiceWorkerDescriptor? controller;
 
   // The service worker may synthesize a Response with a particular
   // tainting value.
   uint32_t tainting;
 
-
   // This flag is used for any browsing context where we should not sniff
   // the content type. E.g if an iframe has the XCTO nosniff header, then
   // that flag is set to true so we skip content sniffing for that browsing
   bool skipContentSniffing;
 
+  // If httpsOnlyNoUpgrade is true, the request won't get upgraded by the
+  // HTTPS-Only Mode.
+  bool httpsOnlyNoUpgrade;
+
   // We must also note that the tainting value was explicitly set
   // by the service worker.
   bool serviceWorkerTaintingSynthesized;
 
   bool documentHasUserInteracted;
   bool documentHasLoaded;
   bool allowListFutureDocumentsCreatedFromThisRedirectChain;
 
--- a/netwerk/protocol/http/nsCORSListenerProxy.cpp
+++ b/netwerk/protocol/http/nsCORSListenerProxy.cpp
@@ -39,16 +39,17 @@
 #include "nsISupportsImpl.h"
 #include "nsHttpChannel.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/NullPrincipal.h"
 #include "nsIHttpHeaderVisitor.h"
 #include "nsQueryObject.h"
 #include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_dom.h"
 #include <algorithm>
 
 using namespace mozilla;
 using namespace mozilla::net;
 
 #define PREFLIGHT_CACHE_SIZE 100
 // 5 seconds is chosen to be compatible with Chromium.
 #define PREFLIGHT_DEFAULT_EXPIRY_SECONDS 5
@@ -769,27 +770,27 @@ nsCORSListenerProxy::CheckListenerChain(
   }
   if (!retargetableListener) {
     return NS_ERROR_NO_INTERFACE;
   }
 
   return retargetableListener->CheckListenerChain();
 }
 
-// Please note that the CSP directive 'upgrade-insecure-requests' relies
-// on the promise that channels get updated from http: to https: before
-// the channel fetches any data from the netwerk. Such channels should
-// not be blocked by CORS and marked as cross origin requests. E.g.:
-// toplevel page: https://www.example.com loads
-//           xhr: http://www.example.com/foo which gets updated to
-//                https://www.example.com/foo
+// Please note that the CSP directive 'upgrade-insecure-requests' and the
+// HTTPS-Only Mode are relying on the promise that channels get updated from
+// http: to https: before the channel fetches any data from the netwerk. Such
+// channels should not be blocked by CORS and marked as cross origin requests.
+// E.g.: toplevel page: https://www.example.com loads
+//                 xhr: http://www.example.com/foo which gets updated to
+//                      https://www.example.com/foo
 // In such a case we should bail out of CORS and rely on the promise that
 // nsHttpChannel::Connect() upgrades the request from http to https.
-bool CheckUpgradeInsecureRequestsPreventsCORS(
-    nsIPrincipal* aRequestingPrincipal, nsIChannel* aChannel) {
+bool CheckInsecureUpgradePreventsCORS(nsIPrincipal* aRequestingPrincipal,
+                                      nsIChannel* aChannel) {
   nsCOMPtr<nsIURI> channelURI;
   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
   NS_ENSURE_SUCCESS(rv, false);
 
   // upgrade insecure requests is only applicable to http requests
   if (!channelURI->SchemeIs("http")) {
     return false;
   }
@@ -812,21 +813,17 @@ bool CheckUpgradeInsecureRequestsPrevent
     return false;
   }
 
   // also check that uri matches the one of the originalURI
   if (!channelHost.EqualsIgnoreCase(origChannelHost.get())) {
     return false;
   }
 
-  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
-  // lets see if the loadInfo indicates that the request will
-  // be upgraded before fetching any data from the netwerk.
-  return loadInfo->GetUpgradeInsecureRequests() ||
-         loadInfo->GetBrowserUpgradeInsecureRequests();
+  return true;
 }
 
 nsresult nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel,
                                             DataURIHandling aAllowDataURI,
                                             UpdateType aUpdateType) {
   nsCOMPtr<nsIURI> uri, originalURI;
   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -874,26 +871,34 @@ nsresult nsCORSListenerProxy::UpdateChan
 
   if (!mHasBeenCrossSite &&
       NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(uri, false)) &&
       (originalURI == uri ||
        NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(originalURI, false)))) {
     return NS_OK;
   }
 
-  // if the CSP directive 'upgrade-insecure-requests' is used then we should
-  // not incorrectly require CORS if the only difference of a subresource
-  // request and the main page is the scheme.
-  // e.g. toplevel page: https://www.example.com loads
-  //                xhr: http://www.example.com/somefoo,
+  // If the CSP directive 'upgrade-insecure-requests' is used or the HTTPS-Only
+  // Mode is enabled then we should not incorrectly require CORS if the only
+  // difference of a subresource request and the main page is the scheme. e.g.
+  // toplevel page: https://www.example.com loads
+  //           xhr: http://www.example.com/somefoo,
   // then the xhr request will be upgraded to https before it fetches any data
   // from the netwerk, hence we shouldn't require CORS in that specific case.
-  if (CheckUpgradeInsecureRequestsPreventsCORS(mRequestingPrincipal,
-                                               aChannel)) {
-    return NS_OK;
+  if (CheckInsecureUpgradePreventsCORS(mRequestingPrincipal, aChannel)) {
+    // Check if HTTPS-Only Mode is enabled
+    if (!loadInfo->GetHttpsOnlyNoUpgrade() &&
+        StaticPrefs::dom_security_https_only_mode()) {
+      return NS_OK;
+    }
+    // Check if 'upgrade-insecure-requests' is used
+    if (loadInfo->GetUpgradeInsecureRequests() ||
+        loadInfo->GetBrowserUpgradeInsecureRequests()) {
+      return NS_OK;
+    }
   }
 
   // Check if we need to do a preflight, and if so set one up. This must be
   // called once we know that the request is going, or has gone, cross-origin.
   rv = CheckPreflightNeeded(aChannel, aUpdateType);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // It's a cross site load
--- a/security/manager/ssl/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -260,29 +260,33 @@ OCSPRequest::Run() {
   if (priorityChannel) {
     priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
   }
 
   channel->SetLoadFlags(nsIRequest::LOAD_ANONYMOUS |
                         nsIChannel::LOAD_BYPASS_SERVICE_WORKER |
                         nsIChannel::LOAD_BYPASS_URL_CLASSIFIER);
 
+  nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+
+  // Prevent HTTPS-Only Mode from upgrading the OCSP request.
+  loadInfo->SetHttpsOnlyNoUpgrade(true);
+
   // For OCSP requests, only the first party domain and private browsing id
   // aspects of origin attributes are used. This means that:
   // a) if first party isolation is enabled, OCSP requests will be isolated
   // according to the first party domain of the original https request
   // b) OCSP requests are shared across different containers as long as first
   // party isolation is not enabled and none of the containers are in private
   // browsing mode.
   if (mOriginAttributes != OriginAttributes()) {
     OriginAttributes attrs;
     attrs.mFirstPartyDomain = mOriginAttributes.mFirstPartyDomain;
     attrs.mPrivateBrowsingId = mOriginAttributes.mPrivateBrowsingId;
 
-    nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
     rv = loadInfo->SetOriginAttributes(attrs);
     if (NS_FAILED(rv)) {
       return NotifyDone(rv, lock);
     }
   }
 
   nsCOMPtr<nsIInputStream> uploadStream;
   rv = NS_NewByteInputStream(getter_AddRefs(uploadStream), mPOSTData,
--- a/toolkit/components/captivedetect/CaptiveDetect.jsm
+++ b/toolkit/components/captivedetect/CaptiveDetect.jsm
@@ -32,16 +32,18 @@ function URLFetcher(url, timeout) {
   // Prevent the request from writing to the cache.
   xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
   // Prevent privacy leaks
   xhr.channel.loadFlags |= Ci.nsIRequest.LOAD_ANONYMOUS;
   // Use the system's resolver for this check
   xhr.channel.setTRRMode(Ci.nsIRequest.TRR_DISABLED_MODE);
   // We except this from being classified
   xhr.channel.loadFlags |= Ci.nsIChannel.LOAD_BYPASS_URL_CLASSIFIER;
+  // Prevent HTTPS-Only Mode from upgrading the request.
+  xhr.channel.loadInfo.httpsOnlyNoUpgrade = true;
 
   // We don't want to follow _any_ redirects
   xhr.channel.QueryInterface(Ci.nsIHttpChannel).redirectionLimit = 0;
 
   // The Cache-Control header is only interpreted by proxies and the
   // final destination. It does not help if a resource is already
   // cached locally.
   xhr.setRequestHeader("Cache-Control", "no-cache");
--- a/toolkit/modules/GMPInstallManager.jsm
+++ b/toolkit/modules/GMPInstallManager.jsm
@@ -390,16 +390,19 @@ GMPAddon.prototype = {
       this.version &&
       GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_VERSION, "", this.id) ===
         this.version
     );
   },
   get isEME() {
     return this.id == "gmp-widevinecdm" || this.id.indexOf("gmp-eme-") == 0;
   },
+  get isOpenH264() {
+    return this.id == "gmp-gmpopenh264";
+  },
   /**
    * @return true if the addon has been previously installed and this is
    * a new version, if this is a fresh install return false
    */
   get isUpdate() {
     return (
       this.version &&
       GMPPrefs.getBool(GMPPrefs.KEY_PLUGIN_VERSION, false, this.id)
@@ -470,36 +473,43 @@ GMPDownloader.prototype = {
 
     if (!gmpAddon.isValid) {
       log.info("gmpAddon is not valid, will not continue");
       return Promise.reject({
         target: this,
         type: "downloaderr",
       });
     }
-
-    return ProductAddonChecker.downloadAddon(gmpAddon).then(zipPath => {
-      let relativePath = OS.Path.join(gmpAddon.id, gmpAddon.version);
-      log.info("install to directory path: " + relativePath);
-      let gmpInstaller = new GMPExtractor(zipPath, relativePath);
-      let installPromise = gmpInstaller.install();
-      return installPromise.then(extractedPaths => {
-        // Success, set the prefs
-        let now = Math.round(Date.now() / 1000);
-        GMPPrefs.setInt(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, now, gmpAddon.id);
-        // Remember our ABI, so that if the profile is migrated to another
-        // platform or from 32 -> 64 bit, we notice and don't try to load the
-        // unexecutable plugin library.
-        let abi = GMPUtils._expectedABI(gmpAddon);
-        log.info("Setting ABI to '" + abi + "' for " + gmpAddon.id);
-        GMPPrefs.setString(GMPPrefs.KEY_PLUGIN_ABI, abi, gmpAddon.id);
-        // Setting the version pref signals installation completion to consumers,
-        // if you need to set other prefs etc. do it before this.
-        GMPPrefs.setString(
-          GMPPrefs.KEY_PLUGIN_VERSION,
-          gmpAddon.version,
-          gmpAddon.id
-        );
-        return extractedPaths;
-      });
-    });
+    // If the HTTPS-Only Mode is enabled, every insecure request gets upgraded
+    // by default. This upgrade has to be prevented for openh264 downloads since
+    // the server doesn't support https://
+    const downloadOptions = {
+      httpsOnlyNoUpgrade: gmpAddon.isOpenH264,
+    };
+    return ProductAddonChecker.downloadAddon(gmpAddon, downloadOptions).then(
+      zipPath => {
+        let relativePath = OS.Path.join(gmpAddon.id, gmpAddon.version);
+        log.info("install to directory path: " + relativePath);
+        let gmpInstaller = new GMPExtractor(zipPath, relativePath);
+        let installPromise = gmpInstaller.install();
+        return installPromise.then(extractedPaths => {
+          // Success, set the prefs
+          let now = Math.round(Date.now() / 1000);
+          GMPPrefs.setInt(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, now, gmpAddon.id);
+          // Remember our ABI, so that if the profile is migrated to another
+          // platform or from 32 -> 64 bit, we notice and don't try to load the
+          // unexecutable plugin library.
+          let abi = GMPUtils._expectedABI(gmpAddon);
+          log.info("Setting ABI to '" + abi + "' for " + gmpAddon.id);
+          GMPPrefs.setString(GMPPrefs.KEY_PLUGIN_ABI, abi, gmpAddon.id);
+          // Setting the version pref signals installation completion to consumers,
+          // if you need to set other prefs etc. do it before this.
+          GMPPrefs.setString(
+            GMPPrefs.KEY_PLUGIN_VERSION,
+            gmpAddon.version,
+            gmpAddon.id
+          );
+          return extractedPaths;
+        });
+      }
+    );
   },
 };
--- a/toolkit/mozapps/extensions/internal/ProductAddonChecker.jsm
+++ b/toolkit/mozapps/extensions/internal/ProductAddonChecker.jsm
@@ -304,20 +304,23 @@ function downloadLocalConfig() {
   });
 }
 
 /**
  * Downloads file from a URL using XHR.
  *
  * @param  url
  *         The url to download from.
+ * @param  options (optional)
+ * @param  options.httpsOnlyNoUpgrade
+ *         Prevents upgrade to https:// when HTTPS-Only Mode is enabled.
  * @return a promise that resolves to the path of a temporary file or rejects
  *         with a JS exception in case of error.
  */
-function downloadFile(url) {
+function downloadFile(url, options = { httpsOnlyNoUpgrade: false }) {
   return new Promise((resolve, reject) => {
     let xhr = new XMLHttpRequest();
     xhr.onload = function(response) {
       logger.info("downloadXHR File download. status=" + xhr.status);
       if (xhr.status != 200 && xhr.status != 206) {
         reject(Components.Exception("File download failed", xhr.status));
         return;
       }
@@ -347,16 +350,17 @@ function downloadFile(url) {
       reject(ex);
     };
     xhr.addEventListener("error", fail);
     xhr.addEventListener("abort", fail);
 
     xhr.responseType = "arraybuffer";
     try {
       xhr.open("GET", url);
+      xhr.channel.loadInfo.httpsOnlyNoUpgrade = options.httpsOnlyNoUpgrade;
       // Use conservative TLS settings. See bug 1325501.
       // TODO move to ServiceRequest.
       if (xhr.channel instanceof Ci.nsIHttpChannelInternal) {
         xhr.channel.QueryInterface(
           Ci.nsIHttpChannelInternal
         ).beConservative = true;
       }
       xhr.send(null);
@@ -475,21 +479,24 @@ const ProductAddonChecker = {
   },
 
   /**
    * Downloads an add-on to a local file and checks that it matches the expected
    * file. The caller is responsible for deleting the temporary file returned.
    *
    * @param  addon
    *         The addon to download.
+   * @param  options (optional)
+   * @param  options.httpsOnlyNoUpgrade
+   *         Prevents upgrade to https:// when HTTPS-Only Mode is enabled.
    * @return a promise that resolves to the temporary file downloaded or rejects
    *         with a JS exception in case of error.
    */
-  async downloadAddon(addon) {
-    let path = await downloadFile(addon.URL);
+  async downloadAddon(addon, options = { httpsOnlyNoUpgrade: false }) {
+    let path = await downloadFile(addon.URL, options);
     try {
       await verifyFile(addon, path);
       return path;
     } catch (e) {
       await OS.File.remove(path);
       throw e;
     }
   },