Bug 1359987 - Update HSTS priming telemetry r?mayhemer,ckerschb p=francois draft
authorKate McKinley <kmckinley@mozilla.com>
Tue, 09 May 2017 15:36:07 -0700
changeset 593589 e52075740c4373177f42ea78ec96a2d4c03fef1a
parent 592668 2a63a6c35033b5cbc6c98cabc7657c7290284691
child 633158 b1408c23d3891c1247294995a972325e5f7aa391
push id63747
push userbmo:kmckinley@mozilla.com
push dateTue, 13 Jun 2017 22:03:36 +0000
reviewersmayhemer, ckerschb
bugs1359987
milestone56.0a1
Bug 1359987 - Update HSTS priming telemetry r?mayhemer,ckerschb p=francois Collect telemetry for all requests to get an exact percentage of requests that are subject to HSTS priming, and how many result in an HSTS Priming request being sent. Clean up telemetry to remove instances of double counting requests if a priming request was sent. HSTSPrimingListener::ReportTiming was using mCallback to calculate timing telemetry, but we were calling swap() on the nsCOMPtr. Give it an explicit argument for the callback. Add tests for telemetry values to all of the HSTS priming tests. This tests for the minimum as telemetry may be gathered on background or other requests. MozReview-Commit-ID: 5V2Nf0Ugc3r
dom/security/nsMixedContentBlocker.cpp
dom/security/test/hsts/browser_hsts-priming_allow_active.js
dom/security/test/hsts/browser_hsts-priming_allow_display.js
dom/security/test/hsts/browser_hsts-priming_block_active.js
dom/security/test/hsts/browser_hsts-priming_block_active_css.js
dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js
dom/security/test/hsts/browser_hsts-priming_block_display.js
dom/security/test/hsts/browser_hsts-priming_cache-timeout.js
dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js
dom/security/test/hsts/browser_hsts-priming_include-subdomains.js
dom/security/test/hsts/browser_hsts-priming_no-duplicates.js
dom/security/test/hsts/browser_hsts-priming_no-ip-address.js
dom/security/test/hsts/browser_hsts-priming_no-non-standard-ports.js
dom/security/test/hsts/browser_hsts-priming_timeout.js
dom/security/test/hsts/head.js
ipc/glue/BackgroundUtils.cpp
netwerk/base/LoadInfo.cpp
netwerk/base/LoadInfo.h
netwerk/base/nsILoadInfo.idl
netwerk/base/nsNetUtil.cpp
netwerk/ipc/NeckoChannelParams.ipdlh
netwerk/protocol/http/HSTSPrimerListener.cpp
netwerk/protocol/http/HSTSPrimerListener.h
netwerk/protocol/http/nsHttpChannel.cpp
toolkit/components/telemetry/Histograms.json
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -85,16 +85,39 @@ IsEligibleForHSTSPriming(nsIURI* aConten
   nsAutoCString hostname;
   rv = aContentLocation->GetHost(hostname);
   NS_ENSURE_SUCCESS(rv, false);
 
   PRNetAddr hostAddr;
   return (PR_StringToNetAddr(hostname.get(), &hostAddr) != PR_SUCCESS);
 }
 
+enum MixedContentHSTSState {
+  MCB_HSTS_PASSIVE_NO_HSTS   = 0,
+  MCB_HSTS_PASSIVE_WITH_HSTS = 1,
+  MCB_HSTS_ACTIVE_NO_HSTS    = 2,
+  MCB_HSTS_ACTIVE_WITH_HSTS  = 3
+};
+
+// Similar to the existing mixed-content HSTS, except MCB_HSTS_*_NO_HSTS is
+// broken into two distinct states, indicating whether we plan to send a priming
+// request or not. If we decided not go send a priming request, it could be
+// because it is a type we do not support, or because we cached a previous
+// negative response.
+enum MixedContentHSTSPrimingState {
+  eMCB_HSTS_PASSIVE_WITH_HSTS  = 0,
+  eMCB_HSTS_ACTIVE_WITH_HSTS   = 1,
+  eMCB_HSTS_PASSIVE_NO_PRIMING = 2,
+  eMCB_HSTS_PASSIVE_DO_PRIMING = 3,
+  eMCB_HSTS_ACTIVE_NO_PRIMING  = 4,
+  eMCB_HSTS_ACTIVE_DO_PRIMING  = 5,
+  eMCB_HSTS_PASSIVE_UPGRADE    = 6,
+  eMCB_HSTS_ACTIVE_UPGRADE     = 7,
+};
+
 // Fired at the document that attempted to load mixed content.  The UI could
 // handle this event, for example, by displaying an info bar that offers the
 // choice to reload the page with mixed content permitted.
 class nsMixedContentEvent : public Runnable
 {
 public:
   nsMixedContentEvent(nsISupports *aContext, MixedContentTypes aType, bool aRootHasSecureConnection)
     : mContext(aContext), mType(aType), mRootHasSecureConnection(aRootHasSecureConnection)
@@ -887,29 +910,33 @@ nsMixedContentBlocker::ShouldLoad(bool a
 
   OriginAttributes originAttributes;
   if (principal) {
     originAttributes = principal->OriginAttributesRef();
   } else if (aRequestPrincipal) {
     originAttributes = aRequestPrincipal->OriginAttributesRef();
   }
 
+  bool active = (classification == eMixedScript);
   bool doHSTSPriming = false;
   if (IsEligibleForHSTSPriming(aContentLocation)) {
     bool hsts = false;
     bool cached = false;
     nsCOMPtr<nsISiteSecurityService> sss =
       do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aContentLocation,
         0, originAttributes, &cached, &hsts);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (hsts && sUseHSTS) {
       // assume we will be upgraded later
+      Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
+          (active) ? MixedContentHSTSPrimingState::eMCB_HSTS_ACTIVE_UPGRADE
+                   : MixedContentHSTSPrimingState::eMCB_HSTS_PASSIVE_UPGRADE);
       *aDecision = ACCEPT;
       return NS_OK;
     }
 
     // Send a priming request if the result is not already cached and priming
     // requests are allowed
     if (!cached && sSendHSTSPriming) {
       // add this URI as a priming location
@@ -927,17 +954,16 @@ nsMixedContentBlocker::ShouldLoad(bool a
   //
   // Note that we count this for redirects as well as primary requests. This
   // will cause some degree of double-counting, especially when mixed content
   // is not blocked (e.g., for images).  For more detail, see:
   //   https://bugzilla.mozilla.org/show_bug.cgi?id=1198572#c19
   //
   // We do not count requests aHadInsecureImageRedirect=true, since these are
   // just an artifact of the image caching system.
-  bool active = (classification == eMixedScript);
   if (!aHadInsecureImageRedirect) {
     if (XRE_IsParentProcess()) {
       AccumulateMixedContentHSTS(innerContentLocation, active, doHSTSPriming,
                                  originAttributes);
     } else {
       // Ask the parent process to do the same call
       mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
       if (cc) {
@@ -1098,37 +1124,16 @@ nsMixedContentBlocker::ShouldProcess(uin
     }
   }
 
   return ShouldLoad(aContentType, aContentLocation, aRequestingLocation,
                     aRequestingContext, aMimeGuess, aExtra, aRequestPrincipal,
                     aDecision);
 }
 
-enum MixedContentHSTSState {
-  MCB_HSTS_PASSIVE_NO_HSTS   = 0,
-  MCB_HSTS_PASSIVE_WITH_HSTS = 1,
-  MCB_HSTS_ACTIVE_NO_HSTS    = 2,
-  MCB_HSTS_ACTIVE_WITH_HSTS  = 3
-};
-
-// Similar to the existing mixed-content HSTS, except MCB_HSTS_*_NO_HSTS is
-// broken into two distinct states, indicating whether we plan to send a priming
-// request or not. If we decided not go send a priming request, it could be
-// because it is a type we do not support, or because we cached a previous
-// negative response.
-enum MixedContentHSTSPrimingState {
-  eMCB_HSTS_PASSIVE_WITH_HSTS  = 0,
-  eMCB_HSTS_ACTIVE_WITH_HSTS   = 1,
-  eMCB_HSTS_PASSIVE_NO_PRIMING = 2,
-  eMCB_HSTS_PASSIVE_DO_PRIMING = 3,
-  eMCB_HSTS_ACTIVE_NO_PRIMING  = 4,
-  eMCB_HSTS_ACTIVE_DO_PRIMING  = 5
-};
-
 // Record information on when HSTS would have made mixed content not mixed
 // content (regardless of whether it was actually blocked)
 void
 nsMixedContentBlocker::AccumulateMixedContentHSTS(
   nsIURI* aURI, bool aActive, bool aHasHSTSPriming,
   const OriginAttributes& aOriginAttributes)
 {
   // This method must only be called in the parent, because
@@ -1153,45 +1158,45 @@ nsMixedContentBlocker::AccumulateMixedCo
   // states: would upgrade, would prime, hsts info cached
   // active, passive
   //
   if (!aActive) {
     if (!hsts) {
       Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
                             MCB_HSTS_PASSIVE_NO_HSTS);
       if (aHasHSTSPriming) {
-        Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+        Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
                               eMCB_HSTS_PASSIVE_DO_PRIMING);
       } else {
-        Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+        Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
                               eMCB_HSTS_PASSIVE_NO_PRIMING);
       }
     }
     else {
       Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
                             MCB_HSTS_PASSIVE_WITH_HSTS);
-      Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+      Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
                             eMCB_HSTS_PASSIVE_WITH_HSTS);
     }
   } else {
     if (!hsts) {
       Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
                             MCB_HSTS_ACTIVE_NO_HSTS);
       if (aHasHSTSPriming) {
-        Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+        Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
                               eMCB_HSTS_ACTIVE_DO_PRIMING);
       } else {
-        Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+        Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
                               eMCB_HSTS_ACTIVE_NO_PRIMING);
       }
     }
     else {
       Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
                             MCB_HSTS_ACTIVE_WITH_HSTS);
-      Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+      Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
                             eMCB_HSTS_ACTIVE_WITH_HSTS);
     }
   }
 }
 
 //static
 nsresult
 nsMixedContentBlocker::MarkLoadInfoForPriming(nsIURI* aURI,
--- a/dom/security/test/hsts/browser_hsts-priming_allow_active.js
+++ b/dom/security/test/hsts/browser_hsts-priming_allow_active.js
@@ -1,24 +1,40 @@
 /*
  * Description of the test:
  *   Check that HSTS priming occurs correctly with mixed content when active
  *   content is allowed.
  */
 'use strict';
 
+var expected_telemetry = {
+  "histograms": {
+    "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+    "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+  },
+  "keyed-histograms": {
+    "HSTS_PRIMING_REQUEST_DURATION": {
+      "success": 1,
+      "failure": 2,
+    },
+  }
+};
+
 //jscs:disable
 add_task(function*() {
   //jscs:enable
   Services.obs.addObserver(Observer, "console-api-log-event");
   Services.obs.addObserver(Observer, "http-on-examine-response");
   registerCleanupFunction(do_cleanup);
 
   let which = "allow_active";
 
   SetupPrefTestEnvironment(which);
+  clear_hists(expected_telemetry);
 
   for (let server of Object.keys(test_servers)) {
     yield execute_test(server, test_settings[which].mimetype);
   }
 
+  test_telemetry(expected_telemetry);
+
   SpecialPowers.popPrefEnv();
 });
--- a/dom/security/test/hsts/browser_hsts-priming_allow_display.js
+++ b/dom/security/test/hsts/browser_hsts-priming_allow_display.js
@@ -1,24 +1,40 @@
 /*
  * Description of the test:
  *   Check that HSTS priming occurs correctly with mixed content when display
  *   content is allowed.
  */
 'use strict';
 
+var expected_telemetry = {
+  "histograms": {
+    "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+    "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+  },
+  "keyed-histograms": {
+    "HSTS_PRIMING_REQUEST_DURATION": {
+      "success": 1,
+      "failure": 2,
+    },
+  }
+};
+
 //jscs:disable
 add_task(function*() {
   //jscs:enable
   Services.obs.addObserver(Observer, "console-api-log-event");
   Services.obs.addObserver(Observer, "http-on-examine-response");
   registerCleanupFunction(do_cleanup);
 
   let which = "allow_display";
 
   SetupPrefTestEnvironment(which);
+  clear_hists(expected_telemetry);
 
   for (let server of Object.keys(test_servers)) {
     yield execute_test(server, test_settings[which].mimetype);
   }
 
+  test_telemetry(expected_telemetry);
+
   SpecialPowers.popPrefEnv();
 });
--- a/dom/security/test/hsts/browser_hsts-priming_block_active.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_active.js
@@ -1,24 +1,40 @@
 /*
  * Description of the test:
  *   Check that HSTS priming occurs correctly with mixed content when active
  *   content is blocked.
  */
 'use strict';
 
+var expected_telemetry = {
+  "histograms": {
+    "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+    "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+  },
+  "keyed-histograms": {
+    "HSTS_PRIMING_REQUEST_DURATION": {
+      "success": 1,
+      "failure": 2,
+    },
+  }
+};
+
 //jscs:disable
 add_task(function*() {
   //jscs:enable
   Services.obs.addObserver(Observer, "console-api-log-event");
   Services.obs.addObserver(Observer, "http-on-examine-response");
   registerCleanupFunction(do_cleanup);
 
   let which = "block_active";
 
   SetupPrefTestEnvironment(which);
+  clear_hists(expected_telemetry);
 
   for (let server of Object.keys(test_servers)) {
     yield execute_test(server, test_settings[which].mimetype);
   }
 
+  test_telemetry(expected_telemetry);
+
   SpecialPowers.popPrefEnv();
 });
--- a/dom/security/test/hsts/browser_hsts-priming_block_active_css.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_active_css.js
@@ -1,24 +1,40 @@
 /*
  * Description of the test:
  *   Check that HSTS priming occurs correctly with mixed content when active
  *   content is blocked for css.
  */
 'use strict';
 
+var expected_telemetry = {
+  "histograms": {
+    "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+    "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+  },
+  "keyed-histograms": {
+    "HSTS_PRIMING_REQUEST_DURATION": {
+      "success": 1,
+      "failure": 2,
+    },
+  }
+};
+
 //jscs:disable
 add_task(function*() {
   //jscs:enable
   Services.obs.addObserver(Observer, "console-api-log-event");
   Services.obs.addObserver(Observer, "http-on-examine-response");
   registerCleanupFunction(do_cleanup);
 
   let which = "block_active_css";
 
   SetupPrefTestEnvironment(which);
+  clear_hists(expected_telemetry);
 
   for (let server of Object.keys(test_servers)) {
     yield execute_test(server, test_settings[which].mimetype);
   }
 
+  test_telemetry(expected_telemetry);
+
   SpecialPowers.popPrefEnv();
 });
--- a/dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js
@@ -1,24 +1,40 @@
 /*
  * Description of the test:
  *   Check that HSTS priming occurs correctly with mixed content when active
  *   content is blocked and redirect to the same host should still upgrade.
  */
 'use strict';
 
+var expected_telemetry = {
+  "histograms": {
+    "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+    "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+  },
+  "keyed-histograms": {
+    "HSTS_PRIMING_REQUEST_DURATION": {
+      "success": 1,
+      "failure": 2,
+    },
+  }
+};
+
 //jscs:disable
 add_task(function*() {
   //jscs:enable
   Services.obs.addObserver(Observer, "console-api-log-event");
   Services.obs.addObserver(Observer, "http-on-examine-response");
   registerCleanupFunction(do_cleanup);
 
   let which = "block_active_with_redir_same";
 
   SetupPrefTestEnvironment(which);
+  clear_hists(expected_telemetry);
 
   for (let server of Object.keys(test_servers)) {
     yield execute_test(server, test_settings[which].mimetype);
   }
 
+  test_telemetry(expected_telemetry);
+
   SpecialPowers.popPrefEnv();
 });
--- a/dom/security/test/hsts/browser_hsts-priming_block_display.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_display.js
@@ -1,24 +1,40 @@
 /*
  * Description of the test:
  *   Check that HSTS priming occurs correctly with mixed content when display
  *   content is blocked.
  */
 'use strict';
 
+var expected_telemetry = {
+  "histograms": {
+    "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+    "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+  },
+  "keyed-histograms": {
+    "HSTS_PRIMING_REQUEST_DURATION": {
+      "success": 1,
+      "failure": 2,
+    },
+  }
+};
+
 //jscs:disable
 add_task(function*() {
   //jscs:enable
   Services.obs.addObserver(Observer, "console-api-log-event");
   Services.obs.addObserver(Observer, "http-on-examine-response");
   registerCleanupFunction(do_cleanup);
 
   let which = "block_display";
 
   SetupPrefTestEnvironment(which);
+  clear_hists(expected_telemetry);
 
   for (let server of Object.keys(test_servers)) {
     yield execute_test(server, test_settings[which].mimetype);
   }
 
+  test_telemetry(expected_telemetry);
+
   SpecialPowers.popPrefEnv();
 });
--- a/dom/security/test/hsts/browser_hsts-priming_cache-timeout.js
+++ b/dom/security/test/hsts/browser_hsts-priming_cache-timeout.js
@@ -1,24 +1,37 @@
 /*
  * Description of the test:
  * Test that the network.hsts_priming.cache_timeout preferene causes the cache
  * to timeout
  */
 'use strict';
 
+var expected_telemetry = {
+  "histograms": {
+    "MIXED_CONTENT_HSTS_PRIMING_RESULT": 2,
+    "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 4,
+  },
+  "keyed-histograms": {
+    "HSTS_PRIMING_REQUEST_DURATION": {
+      "failure": 2,
+    },
+  }
+};
+
 //jscs:disable
 add_task(function*() {
   //jscs:enable
   Observer.add_observers(Services);
   registerCleanupFunction(do_cleanup);
 
   let which = "block_display";
 
   SetupPrefTestEnvironment(which, [["security.mixed_content.hsts_priming_cache_timeout", 1]]);
+  clear_hists(expected_telemetry);
 
   yield execute_test("no-ssl", test_settings[which].mimetype);
 
   let pre_promise = performance.now();
 
   while ((performance.now() - pre_promise) < 2000) {
     yield new Promise(function (resolve) {
       setTimeout(resolve, 2000);
@@ -27,10 +40,12 @@ add_task(function*() {
 
   // clear the fact that we saw a priming request
   test_settings[which].priming = {};
 
   yield execute_test("no-ssl", test_settings[which].mimetype);
   is(test_settings[which].priming["no-ssl"], true,
     "Correctly send a priming request after expiration.");
 
+  test_telemetry(expected_telemetry);
+
   SpecialPowers.popPrefEnv();
 });
--- a/dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js
+++ b/dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js
@@ -1,24 +1,40 @@
 /*
  * Description of the test:
  *   Check that HSTS priming occurs correctly with mixed content when the
  *   mixed-content blocks before HSTS.
  */
 'use strict';
 
+var expected_telemetry = {
+  "histograms": {
+    "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+    "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+  },
+  "keyed-histograms": {
+    "HSTS_PRIMING_REQUEST_DURATION": {
+      "success": 1,
+      "failure": 2,
+    },
+  }
+};
+
 //jscs:disable
 add_task(function*() {
   //jscs:enable
   Services.obs.addObserver(Observer, "console-api-log-event");
   Services.obs.addObserver(Observer, "http-on-examine-response");
   registerCleanupFunction(do_cleanup);
 
   let which = "hsts_after_mixed";
 
   SetupPrefTestEnvironment(which);
+  clear_hists(expected_telemetry);
 
   for (let server of Object.keys(test_servers)) {
     yield execute_test(server, test_settings[which].mimetype);
   }
 
+  test_telemetry(expected_telemetry);
+
   SpecialPowers.popPrefEnv();
 });
--- a/dom/security/test/hsts/browser_hsts-priming_include-subdomains.js
+++ b/dom/security/test/hsts/browser_hsts-priming_include-subdomains.js
@@ -5,16 +5,28 @@
 /*
  * Description of the test:
  *   If the top-level domain sends the STS header but does not have
  *   includeSubdomains, HSTS priming requests should still be sent to
  *   subdomains.
  */
 'use strict';
 
+var expected_telemetry = {
+  "histograms": {
+    "MIXED_CONTENT_HSTS_PRIMING_RESULT": 2,
+    "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 4,
+  },
+  "keyed-histograms": {
+    "HSTS_PRIMING_REQUEST_DURATION": {
+      "success": 2,
+    },
+  }
+};
+
 //jscs:disable
 add_task(function*() {
   //jscs:enable
   Observer.add_observers(Services);
   registerCleanupFunction(do_cleanup);
 
   // add the top-level server
   test_servers['top-level'] = {
@@ -22,18 +34,21 @@ add_task(function*() {
     response: true,
     id: 'top-level',
   };
   test_settings.block_active.result['top-level'] = 'secure';
 
   let which = "block_active";
 
   SetupPrefTestEnvironment(which);
+  clear_hists(expected_telemetry);
 
   yield execute_test("top-level", test_settings[which].mimetype);
 
   yield execute_test("prime-hsts", test_settings[which].mimetype);
 
   ok("prime-hsts" in test_settings[which].priming,
      "HSTS priming on a subdomain when top-level does not includeSubDomains");
 
+  test_telemetry(expected_telemetry);
+
   SpecialPowers.popPrefEnv();
 });
--- a/dom/security/test/hsts/browser_hsts-priming_no-duplicates.js
+++ b/dom/security/test/hsts/browser_hsts-priming_no-duplicates.js
@@ -1,28 +1,44 @@
 /*
  * Description of the test:
  *   Only one request should be sent per host, even if we run the test more
  *   than once.
  */
 'use strict';
 
+var expected_telemetry = {
+  "histograms": {
+    "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+    "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 8,
+  },
+  "keyed-histograms": {
+    "HSTS_PRIMING_REQUEST_DURATION": {
+      "success": 1,
+      "failure": 2,
+    },
+  }
+};
+
 //jscs:disable
 add_task(function*() {
   //jscs:enable
   Observer.add_observers(Services);
   registerCleanupFunction(do_cleanup);
 
   let which = "block_display";
 
   SetupPrefTestEnvironment(which);
+  clear_hists(expected_telemetry);
 
   for (let server of Object.keys(test_servers)) {
     yield execute_test(server, test_settings[which].mimetype);
   }
 
   // run the tests twice to validate the cache is being used
   for (let server of Object.keys(test_servers)) {
     yield execute_test(server, test_settings[which].mimetype);
   }
 
+  test_telemetry(expected_telemetry);
+
   SpecialPowers.popPrefEnv();
 });
--- a/dom/security/test/hsts/browser_hsts-priming_no-ip-address.js
+++ b/dom/security/test/hsts/browser_hsts-priming_no-ip-address.js
@@ -1,16 +1,25 @@
 /*
  * Description of the test:
  *   If the top-level domain sends the STS header but does not have
  *   includeSubdomains, HSTS priming requests should still be sent to
  *   subdomains.
  */
 'use strict';
 
+var expected_telemetry = {
+  "histograms": {
+    "MIXED_CONTENT_HSTS_PRIMING_RESULT": 0,
+    "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 1,
+  },
+  "keyed-histograms": {
+  }
+};
+
 //jscs:disable
 add_task(function*() {
   //jscs:enable
   Observer.add_observers(Services);
   registerCleanupFunction(do_cleanup);
 
   // add the top-level server
   test_servers['localhost-ip'] = {
@@ -18,13 +27,16 @@ add_task(function*() {
     response: true,
     id: 'localhost-ip',
   };
   test_settings.block_active.result['localhost-ip'] = 'blocked';
 
   let which = "block_active";
 
   SetupPrefTestEnvironment(which);
+  clear_hists(expected_telemetry);
 
   yield execute_test("localhost-ip", test_settings[which].mimetype);
 
+  test_telemetry(expected_telemetry);
+
   SpecialPowers.popPrefEnv();
 });
--- a/dom/security/test/hsts/browser_hsts-priming_no-non-standard-ports.js
+++ b/dom/security/test/hsts/browser_hsts-priming_no-non-standard-ports.js
@@ -1,34 +1,49 @@
 /*
  * Description of the test:
  *   If the top-level domain sends the STS header but does not have
  *   includeSubdomains, HSTS priming requests should still be sent to
  *   subdomains.
  */
 'use strict';
 
+var expected_telemetry = {
+  "histograms": {
+    "MIXED_CONTENT_HSTS_PRIMING_RESULT": 1,
+    "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 3,
+  },
+  "keyed-histograms": {
+    "HSTS_PRIMING_REQUEST_DURATION": {
+      "success": 1,
+    },
+  }
+};
+
 //jscs:disable
 add_task(function*() {
   //jscs:enable
   Observer.add_observers(Services);
   registerCleanupFunction(do_cleanup);
 
   // add the top-level server
   test_servers['non-standard-port'] = {
-    host: 'example.com:1234',
+    host: 'test1.example.com:1234',
     response: true,
     id: 'non-standard-port',
   };
   test_settings.block_active.result['non-standard-port'] = 'blocked';
 
   let which = "block_active";
 
   SetupPrefTestEnvironment(which);
+  clear_hists(expected_telemetry);
 
   yield execute_test("non-standard-port", test_settings[which].mimetype);
 
   yield execute_test("prime-hsts", test_settings[which].mimetype);
 
   ok("prime-hsts" in test_settings[which_test].priming, "Sent priming request on standard port after non-standard was not primed");
 
+  test_telemetry(expected_telemetry);
+
   SpecialPowers.popPrefEnv();
 });
--- a/dom/security/test/hsts/browser_hsts-priming_timeout.js
+++ b/dom/security/test/hsts/browser_hsts-priming_timeout.js
@@ -1,24 +1,39 @@
 /*
  * Description of the test:
  *   Only one request should be sent per host, even if we run the test more
  *   than once.
  */
 'use strict';
 
+var expected_telemetry = {
+  "histograms": {
+    "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+    "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 3,
+  },
+  "keyed-histograms": {
+    "HSTS_PRIMING_REQUEST_DURATION": {
+      "failure": 3,
+    },
+  }
+};
+
 //jscs:disable
 add_task(function*() {
   //jscs:enable
   Observer.add_observers(Services);
   registerCleanupFunction(do_cleanup);
 
   let which = "timeout";
 
   SetupPrefTestEnvironment(which, [["security.mixed_content.hsts_priming_request_timeout",
                 1000]]);
+  clear_hists(expected_telemetry);
 
   for (let server of Object.keys(test_servers)) {
     yield execute_test(server, test_settings[which].mimetype);
   }
 
+  test_telemetry(expected_telemetry);
+
   SpecialPowers.popPrefEnv();
 });
--- a/dom/security/test/hsts/head.js
+++ b/dom/security/test/hsts/head.js
@@ -372,22 +372,26 @@ function openTab(uri) {
 }
 
 function clear_sts_data() {
   for (let test in test_servers) {
     SpecialPowers.cleanUpSTSData('http://'+test_servers[test].host);
   }
 }
 
+var oldCanRecord = Services.telemetry.canRecordExtended;
+
 function do_cleanup() {
   clear_sts_data();
 
   Services.obs.removeObserver(Observer, "console-api-log-event");
   Services.obs.removeObserver(Observer, "http-on-examine-response");
 
+  Services.telemetry.canRecordExtended = oldCanRecord;
+
   Observer.cleanup();
 }
 
 function SetupPrefTestEnvironment(which, additional_prefs) {
   which_test = which;
   clear_sts_data();
 
   var settings = test_settings[which];
@@ -399,24 +403,27 @@ function SetupPrefTestEnvironment(which,
   var prefs = [["security.mixed_content.block_active_content",
                 settings.block_active],
                ["security.mixed_content.block_display_content",
                 settings.block_display],
                ["security.mixed_content.use_hsts",
                 settings.use_hsts],
                ["security.mixed_content.send_hsts_priming",
                 settings.send_hsts_priming],
+               ["toolkit.telemetry.enabled", true],
   ];
 
   if (additional_prefs) {
     for (let idx in additional_prefs) {
       prefs.push(additional_prefs[idx]);
     }
   }
 
+  Services.telemetry.canRecordExtended = true;
+
   SpecialPowers.pushPrefEnv({'set': prefs});
 }
 
 // make the top-level test uri
 function build_test_uri(base_uri, host, test_id, type, timeout) {
   return base_uri +
           "?host=" + escape(host) +
           "&id=" + escape(test_id) +
@@ -428,8 +435,93 @@ function build_test_uri(base_uri, host, 
 // open a new tab, load the test, and wait for it to finish
 async function execute_test(test, mimetype) {
   var src = build_test_uri(TOP_URI, test_servers[test].host,
       test, test_settings[which_test].type,
       test_settings[which_test].timeout);
 
   await BrowserTestUtils.withNewTab(src, () => {});
 }
+
+/* Expected should look something like this:
+ * The numbers are the sum of all telemetry values.
+  var expected_telemetry = {
+    "histograms": {
+      "MIXED_CONTENT_HSTS_PRIMING_RESULT": 6,
+      "HSTS_PRIMING_REQUESTS": 10,
+    },
+    "keyed-histograms": {
+      "HSTS_PRIMING_REQUEST_DURATION": {
+        "success": 1,
+        "failure": 2,
+      },
+    }
+  };
+ */
+function test_telemetry(expected) {
+  for (let key in expected['histograms']) {
+    let hs = undefined;
+    try {
+      let hist = Services.telemetry.getHistogramById(key);
+      hs = hist.snapshot();
+      hist.clear();
+    } catch(e) {
+      ok(false, "Caught exception trying to get histogram for key " + key + ":" + e);
+      continue;
+    }
+
+    if (!hs) {
+      ok(false, "No histogram found for key " + key);
+      continue;
+    }
+
+    // there may have been other background requests processed
+    ok(hs.counts.reduce(sum) >= expected['histograms'][key], "Histogram counts match expected, got " + hs.counts.reduce(sum) + ", expected at least " + expected['histograms'][key]);
+  }
+
+  for (let key in expected['keyed-histograms']) {
+    let hs = undefined;
+    try {
+      let hist = Services.telemetry.getKeyedHistogramById(key);
+      hs = hist.snapshot();
+      hist.clear();
+    } catch(e) {
+      ok(false, "Caught exception trying to get histogram for key " + key + " :" + e);
+      continue;
+    }
+
+    if (!hs) {
+      ok(false, "No keyed histogram found for key " + key);
+      continue;
+    }
+
+    for (let hist_key in expected['keyed-histograms'][key]) {
+      ok(hist_key in hs, "Keyed histogram exists with key");
+      if (hist_key in hs) {
+        ok(hs[hist_key].counts.reduce(sum) >= expected['keyed-histograms'][key][hist_key], "Keyed histogram counts match expected got " + hs[hist_key].counts.reduce(sum) + ", expected at least " + expected['keyed-histograms'][key][hist_key])
+      }
+    }
+  }
+}
+
+function sum(a, b) {
+  return a+b;
+}
+
+function clear_hists(hists) {
+  for (let key in hists['histograms']) {
+    try {
+      let hist = Services.telemetry.getHistogramById(key);
+      hist.clear();
+    } catch(e) {
+      continue;
+    }
+  }
+
+  for (let key in hists['keyed-histograms']) {
+    try {
+      let hist = Services.telemetry.getKeyedHistogramById(key);
+      hist.clear();
+    } catch(e) {
+      continue;
+    }
+  }
+}
--- a/ipc/glue/BackgroundUtils.cpp
+++ b/ipc/glue/BackgroundUtils.cpp
@@ -384,17 +384,20 @@ LoadInfoToLoadInfoArgs(nsILoadInfo *aLoa
       aLoadInfo->GetIsInThirdPartyContext(),
       aLoadInfo->GetOriginAttributes(),
       redirectChainIncludingInternalRedirects,
       redirectChain,
       aLoadInfo->CorsUnsafeHeaders(),
       aLoadInfo->GetForcePreflight(),
       aLoadInfo->GetIsPreflight(),
       aLoadInfo->GetForceHSTSPriming(),
-      aLoadInfo->GetMixedContentWouldBlock());
+      aLoadInfo->GetMixedContentWouldBlock(),
+      aLoadInfo->GetIsHSTSPriming(),
+      aLoadInfo->GetIsHSTSPrimingUpgrade()
+      );
 
   return NS_OK;
 }
 
 nsresult
 LoadInfoArgsToLoadInfo(const OptionalLoadInfoArgs& aOptionalLoadInfoArgs,
                        nsILoadInfo** outLoadInfo)
 {
@@ -469,17 +472,19 @@ LoadInfoArgsToLoadInfo(const OptionalLoa
                           loadInfoArgs.isInThirdPartyContext(),
                           loadInfoArgs.originAttributes(),
                           redirectChainIncludingInternalRedirects,
                           redirectChain,
                           loadInfoArgs.corsUnsafeHeaders(),
                           loadInfoArgs.forcePreflight(),
                           loadInfoArgs.isPreflight(),
                           loadInfoArgs.forceHSTSPriming(),
-                          loadInfoArgs.mixedContentWouldBlock()
+                          loadInfoArgs.mixedContentWouldBlock(),
+                          loadInfoArgs.isHSTSPriming(),
+                          loadInfoArgs.isHSTSPrimingUpgrade()
                           );
 
    loadInfo.forget(outLoadInfo);
    return NS_OK;
 }
 
 } // namespace ipc
 } // namespace mozilla
--- a/netwerk/base/LoadInfo.cpp
+++ b/netwerk/base/LoadInfo.cpp
@@ -53,16 +53,18 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
   , mFrameOuterWindowID(0)
   , mEnforceSecurity(false)
   , mInitialSecurityCheckDone(false)
   , mIsThirdPartyContext(false)
   , mForcePreflight(false)
   , mIsPreflight(false)
   , mForceHSTSPriming(false)
   , mMixedContentWouldBlock(false)
+  , mIsHSTSPriming(false)
+  , mIsHSTSPrimingUpgrade(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
@@ -226,16 +228,18 @@ LoadInfo::LoadInfo(nsPIDOMWindowOuter* a
   , mFrameOuterWindowID(0)
   , mEnforceSecurity(false)
   , mInitialSecurityCheckDone(false)
   , mIsThirdPartyContext(false) // NB: TYPE_DOCUMENT implies not third-party.
   , mForcePreflight(false)
   , mIsPreflight(false)
   , mForceHSTSPriming(false)
   , mMixedContentWouldBlock(false)
+  , mIsHSTSPriming(false)
+  , mIsHSTSPrimingUpgrade(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 (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED) {
@@ -289,16 +293,18 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
   , mRedirectChainIncludingInternalRedirects(
       rhs.mRedirectChainIncludingInternalRedirects)
   , mRedirectChain(rhs.mRedirectChain)
   , mCorsUnsafeHeaders(rhs.mCorsUnsafeHeaders)
   , mForcePreflight(rhs.mForcePreflight)
   , mIsPreflight(rhs.mIsPreflight)
   , mForceHSTSPriming(rhs.mForceHSTSPriming)
   , mMixedContentWouldBlock(rhs.mMixedContentWouldBlock)
+  , mIsHSTSPriming(rhs.mIsHSTSPriming)
+  , mIsHSTSPrimingUpgrade(rhs.mIsHSTSPrimingUpgrade)
 {
 }
 
 LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal,
                    nsIPrincipal* aTriggeringPrincipal,
                    nsIPrincipal* aPrincipalToInherit,
                    nsIPrincipal* aSandboxedLoadingPrincipal,
                    nsSecurityFlags aSecurityFlags,
@@ -317,17 +323,19 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
                    bool aIsThirdPartyContext,
                    const OriginAttributes& aOriginAttributes,
                    RedirectHistoryArray& aRedirectChainIncludingInternalRedirects,
                    RedirectHistoryArray& aRedirectChain,
                    const nsTArray<nsCString>& aCorsUnsafeHeaders,
                    bool aForcePreflight,
                    bool aIsPreflight,
                    bool aForceHSTSPriming,
-                   bool aMixedContentWouldBlock)
+                   bool aMixedContentWouldBlock,
+                   bool aIsHSTSPriming,
+                   bool aIsHSTSPrimingUpgrade)
   : mLoadingPrincipal(aLoadingPrincipal)
   , mTriggeringPrincipal(aTriggeringPrincipal)
   , mPrincipalToInherit(aPrincipalToInherit)
   , mSecurityFlags(aSecurityFlags)
   , mInternalContentPolicyType(aContentPolicyType)
   , mTainting(aTainting)
   , mUpgradeInsecureRequests(aUpgradeInsecureRequests)
   , mVerifySignedContent(aVerifySignedContent)
@@ -341,16 +349,18 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
   , mInitialSecurityCheckDone(aInitialSecurityCheckDone)
   , mIsThirdPartyContext(aIsThirdPartyContext)
   , mOriginAttributes(aOriginAttributes)
   , mCorsUnsafeHeaders(aCorsUnsafeHeaders)
   , mForcePreflight(aForcePreflight)
   , mIsPreflight(aIsPreflight)
   , mForceHSTSPriming (aForceHSTSPriming)
   , mMixedContentWouldBlock(aMixedContentWouldBlock)
+  , mIsHSTSPriming(aIsHSTSPriming)
+  , mIsHSTSPrimingUpgrade(aIsHSTSPrimingUpgrade)
 {
   // Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal
   MOZ_ASSERT(mLoadingPrincipal || aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT);
   MOZ_ASSERT(mTriggeringPrincipal);
 
   mRedirectChainIncludingInternalRedirects.SwapElements(
     aRedirectChainIncludingInternalRedirects);
 
@@ -929,16 +939,48 @@ LoadInfo::SetHSTSPriming(bool aMixedCont
 void
 LoadInfo::ClearHSTSPriming()
 {
   mForceHSTSPriming = false;
   mMixedContentWouldBlock = false;
 }
 
 NS_IMETHODIMP
+LoadInfo::SetIsHSTSPriming(bool aIsHSTSPriming)
+{
+  MOZ_ASSERT(aIsHSTSPriming);
+  mIsHSTSPriming = aIsHSTSPriming;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsHSTSPriming(bool* aIsHSTSPriming)
+{
+  MOZ_ASSERT(aIsHSTSPriming);
+  *aIsHSTSPriming = mIsHSTSPriming;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsHSTSPrimingUpgrade(bool aIsHSTSPrimingUpgrade)
+{
+  MOZ_ASSERT(aIsHSTSPrimingUpgrade);
+  mIsHSTSPrimingUpgrade = aIsHSTSPrimingUpgrade;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsHSTSPrimingUpgrade(bool* aIsHSTSPrimingUpgrade)
+{
+  MOZ_ASSERT(aIsHSTSPrimingUpgrade);
+  *aIsHSTSPrimingUpgrade = mIsHSTSPrimingUpgrade;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 LoadInfo::GetTainting(uint32_t* aTaintingOut)
 {
   MOZ_ASSERT(aTaintingOut);
   *aTaintingOut = static_cast<uint32_t>(mTainting);
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/netwerk/base/LoadInfo.h
+++ b/netwerk/base/LoadInfo.h
@@ -110,17 +110,19 @@ private:
            bool aIsThirdPartyRequest,
            const OriginAttributes& aOriginAttributes,
            RedirectHistoryArray& aRedirectChainIncludingInternalRedirects,
            RedirectHistoryArray& aRedirectChain,
            const nsTArray<nsCString>& aUnsafeHeaders,
            bool aForcePreflight,
            bool aIsPreflight,
            bool aForceHSTSPriming,
-           bool aMixedContentWouldBlock);
+           bool aMixedContentWouldBlock,
+           bool aIsHSTSPriming,
+           bool aIsHSTSPrimingUpgrade);
   LoadInfo(const LoadInfo& rhs);
 
   NS_IMETHOD GetRedirects(JSContext* aCx, JS::MutableHandle<JS::Value> aRedirects,
                           const RedirectHistoryArray& aArra);
 
   friend nsresult
   mozilla::ipc::LoadInfoArgsToLoadInfo(
     const mozilla::net::OptionalLoadInfoArgs& aLoadInfoArgs,
@@ -160,15 +162,17 @@ private:
   RedirectHistoryArray             mRedirectChainIncludingInternalRedirects;
   RedirectHistoryArray             mRedirectChain;
   nsTArray<nsCString>              mCorsUnsafeHeaders;
   bool                             mForcePreflight;
   bool                             mIsPreflight;
 
   bool                             mForceHSTSPriming : 1;
   bool                             mMixedContentWouldBlock : 1;
+  bool                             mIsHSTSPriming: 1;
+  bool                             mIsHSTSPrimingUpgrade: 1;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // mozilla_LoadInfo_h
 
--- a/netwerk/base/nsILoadInfo.idl
+++ b/netwerk/base/nsILoadInfo.idl
@@ -671,16 +671,26 @@ interface nsILoadInfo : nsISupports
   [noscript, infallible] readonly attribute boolean forceHSTSPriming;
   /**
    * Carry the decision whether this load would be blocked by mixed content so
    * that if HSTS priming fails, the correct decision can be made.
    */
   [noscript, infallible] readonly attribute boolean mixedContentWouldBlock;
 
   /**
+   * True if this load is an HSTS priming request.
+   */
+  [noscript, infallible] attribute boolean isHSTSPriming;
+
+  /**
+   * True if this load was upgraded from HSTS priming
+   */
+  [noscript, infallible] attribute boolean isHSTSPrimingUpgrade;
+
+  /**
    * Mark this LoadInfo as needing HSTS Priming
    *
    * @param wouldBlock Carry the decision of Mixed Content Blocking to be
    * applied when HSTS priming is complete.
    */
   [noscript, notxpcom, nostdcall]
   void setHSTSPriming(in boolean mixeContentWouldBlock);
   [noscript, notxpcom, nostdcall]
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -2564,16 +2564,24 @@ NS_ShouldSecureUpgrade(nsIURI* aURI,
                             0, // aColumnNumber
                             nsIScriptError::warningFlag, "CSP",
                             innerWindowId);
 
         Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 4);
         aShouldUpgrade = true;
         return NS_OK;
       }
+
+      if (aLoadInfo->GetForceHSTSPriming()) {
+        // don't log requests which might be upgraded due to HSTS Priming
+        // they get logged in nsHttpChannel::OnHSTSPrimingSucceeded or
+        // nsHttpChannel::OnHSTSPrimingFailed if the load is allowed to proceed.
+        aShouldUpgrade = false;
+        return NS_OK;
+      }
     }
 
     // enforce Strict-Transport-Security
     nsISiteSecurityService* sss = gHttpHandler->GetSSService();
     NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
 
     bool isStsHost = false;
     uint32_t flags = aPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
@@ -2593,16 +2601,31 @@ NS_ShouldSecureUpgrade(nsIURI* aURI,
         return NS_OK;
       } else {
         Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 2);
       }
     } else {
       Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 1);
     }
   } else {
+    if (aLoadInfo) {
+      if (aLoadInfo->GetIsHSTSPriming()) {
+        // don't log HSTS priming requests
+        aShouldUpgrade = false;
+        return NS_OK;
+      }
+
+      if (aLoadInfo->GetIsHSTSPrimingUpgrade()) {
+        // if the upgrade occured due to HSTS priming, it was logged in
+        // nsHttpChannel::OnHSTSPrimingSucceeded before redirect
+        aShouldUpgrade = false;
+        return NS_OK;
+      }
+    }
+
     Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 0);
   }
   aShouldUpgrade = false;
   return NS_OK;
 }
 
 nsresult
 NS_GetSecureUpgradedURI(nsIURI* aURI, nsIURI** aUpgradedURI)
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -58,16 +58,18 @@ struct LoadInfoArgs
   OriginAttributes            originAttributes;
   RedirectHistoryEntryInfo[]  redirectChainIncludingInternalRedirects;
   RedirectHistoryEntryInfo[]  redirectChain;
   nsCString[]                 corsUnsafeHeaders;
   bool                        forcePreflight;
   bool                        isPreflight;
   bool                        forceHSTSPriming;
   bool                        mixedContentWouldBlock;
+  bool                        isHSTSPriming;
+  bool                        isHSTSPrimingUpgrade;
 };
 
 /**
  * Not every channel necessarily has a loadInfo attached.
  */
 union OptionalLoadInfoArgs
 {
   void_t;
--- a/netwerk/protocol/http/HSTSPrimerListener.cpp
+++ b/netwerk/protocol/http/HSTSPrimerListener.cpp
@@ -46,31 +46,34 @@ HSTSPrimingListener::HSTSPrimingListener
 
 NS_IMETHODIMP
 HSTSPrimingListener::GetInterface(const nsIID & aIID, void **aResult)
 {
   return QueryInterface(aIID, aResult);
 }
 
 void
-HSTSPrimingListener::ReportTiming(nsresult aResult)
+HSTSPrimingListener::ReportTiming(nsIHstsPrimingCallback* aCallback, nsresult aResult)
 {
   nsCOMPtr<nsITimedChannel> timingChannel =
-    do_QueryInterface(mCallback);
-  if (timingChannel) {
-    TimeStamp channelCreationTime;
-    nsresult rv = timingChannel->GetChannelCreation(&channelCreationTime);
-    if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) {
-      PRUint32 interval =
-        (PRUint32) (TimeStamp::Now() - channelCreationTime).ToMilliseconds();
-      Telemetry::Accumulate(Telemetry::HSTS_PRIMING_REQUEST_DURATION,
-          (NS_SUCCEEDED(aResult)) ? NS_LITERAL_CSTRING("success")
-                                  : NS_LITERAL_CSTRING("failure"),
-          interval);
-    }
+    do_QueryInterface(aCallback);
+  if (!timingChannel) {
+    LOG(("HSTS priming: mCallback is not an nsITimedChannel!"));
+    return;
+  }
+
+  TimeStamp channelCreationTime;
+  nsresult rv = timingChannel->GetChannelCreation(&channelCreationTime);
+  if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) {
+    PRUint32 interval =
+      (PRUint32) (TimeStamp::Now() - channelCreationTime).ToMilliseconds();
+    Telemetry::Accumulate(Telemetry::HSTS_PRIMING_REQUEST_DURATION,
+        (NS_SUCCEEDED(aResult)) ? NS_LITERAL_CSTRING("success")
+                                : NS_LITERAL_CSTRING("failure"),
+        interval);
   }
 }
 
 NS_IMETHODIMP
 HSTSPrimingListener::OnStartRequest(nsIRequest *aRequest,
                                     nsISupports *aContext)
 {
   nsCOMPtr<nsIHstsPrimingCallback> callback;
@@ -83,17 +86,17 @@ HSTSPrimingListener::OnStartRequest(nsIR
 
   // if callback is null, we have already canceled this request and reported
   // the failure
   if (!callback) {
     return NS_OK;
   }
 
   nsresult primingResult = CheckHSTSPrimingRequestStatus(aRequest);
-  ReportTiming(primingResult);
+  ReportTiming(callback, primingResult);
 
   if (NS_FAILED(primingResult)) {
     LOG(("HSTS Priming Failed (request was not approved)"));
     return callback->OnHSTSPrimingFailed(primingResult, false);
   }
 
   LOG(("HSTS Priming Succeeded (request was approved)"));
   return callback->OnHSTSPrimingSucceeded(false);
@@ -187,17 +190,17 @@ HSTSPrimingListener::Notify(nsITimer* ti
   nsresult rv;
   nsCOMPtr<nsIHstsPrimingCallback> callback;
   callback.swap(mCallback);
   if (!callback) {
     // we already processed this channel
     return NS_OK;
   }
 
-  ReportTiming(NS_ERROR_HSTS_PRIMING_TIMEOUT);
+  ReportTiming(callback, NS_ERROR_HSTS_PRIMING_TIMEOUT);
 
   if (mPrimingChannel) {
     rv = mPrimingChannel->Cancel(NS_ERROR_HSTS_PRIMING_TIMEOUT);
     if (NS_FAILED(rv)) {
       NS_ERROR("HSTS Priming timed out, and we got an error canceling the priming channel.");
     }
   }
 
@@ -232,36 +235,41 @@ HSTSPrimingListener::StartHSTSPriming(ns
   NS_GetOriginAttributes(aRequestChannel, originAttributes);
 
   rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, 0,
                         originAttributes, &cached, &hsts);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (hsts) {
     // already saw this host and will upgrade if allowed by preferences
+    Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
+                          HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_CACHED_HSTS);
     return aCallback->OnHSTSPrimingSucceeded(true);
   }
 
   if (cached) {
     // there is a non-expired entry in the cache that doesn't allow us to
     // upgrade, so go ahead and fail early.
+    Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
+                          HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_CACHED_NO_HSTS);
     return aCallback->OnHSTSPrimingFailed(NS_ERROR_CONTENT_BLOCKED, true);
   }
 
   // Either it wasn't cached or the cached result has expired. Build a
   // channel for the HEAD request.
 
   nsCOMPtr<nsILoadInfo> originalLoadInfo = aRequestChannel->GetLoadInfo();
   MOZ_ASSERT(originalLoadInfo, "can not perform HSTS priming without a loadInfo");
   if (!originalLoadInfo) {
     return NS_ERROR_FAILURE;
   }
 
   nsCOMPtr<nsILoadInfo> loadInfo = static_cast<mozilla::LoadInfo*>
     (originalLoadInfo.get())->CloneForNewRequest();
+  loadInfo->SetIsHSTSPriming(true);
 
   // the LoadInfo must have a security flag set in order to pass through priming
   // if none of these security flags are set, go ahead and fail now instead of
   // crashing in nsContentSecurityManager::ValidateSecurityFlags
   nsSecurityFlags securityMode = loadInfo->GetSecurityMode();
   if (securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS &&
       securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED &&
       securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS &&
@@ -365,13 +373,16 @@ HSTSPrimingListener::StartHSTSPriming(ns
                                sHSTSPrimingTimeout,
                                nsITimer::TYPE_ONE_SHOT);
   if (NS_FAILED(rv)) {
     NS_ERROR("HSTS Priming failed to initialize channel cancellation timer");
   }
 
   listener->mHSTSPrimingTimer.swap(timer);
 
+  Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
+                        HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_SENT);
+
   return NS_OK;
 }
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/HSTSPrimerListener.h
+++ b/netwerk/protocol/http/HSTSPrimerListener.h
@@ -22,16 +22,41 @@ class nsIHstsPrimingCallback;
 
 namespace mozilla {
 namespace net {
 
 class HttpChannelParent;
 class nsHttpChannel;
 
 /*
+ * How often do we send an HSTS priming request (over all requests)
+ */
+enum HSTSPrimingRequest {
+  // No HSTS priming request. The request is not mixed-content, or we have
+  // already cached the result before nsMixedContentBlocker::ShouldLoad
+  eHSTS_PRIMING_NO_REQUEST = 0,
+  // Sent an HSTS priming request
+  eHSTS_PRIMING_REQUEST_SENT = 1,
+  // Channel marked for priming, but already had a cached result
+  eHSTS_PRIMING_REQUEST_CACHED_HSTS = 2,
+  // Channel marked for priming, but already had a cached result
+  eHSTS_PRIMING_REQUEST_CACHED_NO_HSTS = 3,
+  // An error occured setting up the the priming request channel. If the
+  // priming channel failed in OnstopRequest, there is no HSTS, or the
+  // channel is redirected, that is recorded by
+  // MIXED_CONTENT_HSTS_PRIMING_RESULT.
+  eHSTS_PRIMING_REQUEST_ERROR = 4,
+  // The channel had no load info, so is ineligible for priming
+  eHSTS_PRIMING_REQUEST_NO_LOAD_INFO = 5,
+  // The request was marked for HSTS priming, but was upgraded by
+  // NS_ShouldSecureUpgrade before HSTS priming was sent.
+  eHSTS_PRIMING_REQUEST_ALREADY_UPGRADED = 6,
+};
+
+/*
  * How often do we get back an HSTS priming result which upgrades the connection to HTTPS?
  */
 enum HSTSPrimingResult {
   // This site has been seen before and won't be upgraded
   eHSTS_PRIMING_CACHED_NO_UPGRADE = 0,
   // This site has been seen before and will be upgraded
   eHSTS_PRIMING_CACHED_DO_UPGRADE = 1,
   // This site has been seen before and will be blocked
@@ -51,17 +76,17 @@ enum HSTSPrimingResult {
   eHSTS_PRIMING_FAILED_BLOCK      = 7,
   // HSTS priming failed, and the load is allowed by mixed-content
   eHSTS_PRIMING_FAILED_ACCEPT     = 8,
   // The HSTS Priming request timed out, and the load is blocked by
   // mixed-content
   eHSTS_PRIMING_TIMEOUT_BLOCK     = 9,
   // The HSTS Priming request timed out, and the load is allowed by
   // mixed-content
-  eHSTS_PRIMING_TIMEOUT_ACCEPT    = 10
+  eHSTS_PRIMING_TIMEOUT_ACCEPT    = 10,
 };
 
 //////////////////////////////////////////////////////////////////////////
 // Class used as streamlistener and notification callback when
 // doing the HEAD request for an HSTS Priming check. Needs to be an
 // nsIStreamListener in order to receive events from AsyncOpen2
 class HSTSPrimingListener final : public nsIStreamListener,
                                   public nsIInterfaceRequestor,
@@ -98,17 +123,17 @@ private:
 
   /**
    * Given a request, return NS_OK if it has resulted in a cached HSTS update.
    * We don't need to check for the header as that has already been done for us.
    */
   nsresult CheckHSTSPrimingRequestStatus(nsIRequest* aRequest);
 
   // send telemetry about how long HSTS priming requests take
-  void ReportTiming(nsresult aResult);
+  void ReportTiming(nsIHstsPrimingCallback* aCallback, nsresult aResult);
 
   /**
    * the nsIHttpChannel to notify with the result of HSTS priming.
    */
   nsCOMPtr<nsIHstsPrimingCallback> mCallback;
 
   /**
    * Keep a handle to the priming channel so we can cancel it on timeout
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -478,43 +478,68 @@ nsHttpChannel::Connect()
 
     return TriggerNetwork(0);
 }
 
 nsresult
 nsHttpChannel::TryHSTSPriming()
 {
     if (mLoadInfo) {
+        if (mLoadInfo->GetIsHSTSPriming()) {
+            // shortcut priming requests so they don't get counted
+            return ContinueConnect();
+        }
+
         // HSTS priming requires the LoadInfo provided with AsyncOpen2
         bool requireHSTSPriming =
             mLoadInfo->GetForceHSTSPriming();
 
         if (requireHSTSPriming &&
                 nsMixedContentBlocker::sSendHSTSPriming &&
                 mInterceptCache == DO_NOT_INTERCEPT) {
             bool isHttpsScheme;
             nsresult rv = mURI->SchemeIs("https", &isHttpsScheme);
             NS_ENSURE_SUCCESS(rv, rv);
             if (!isHttpsScheme) {
                 rv = HSTSPrimingListener::StartHSTSPriming(this, this);
 
                 if (NS_FAILED(rv)) {
                     CloseCacheEntry(false);
+                    Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
+                                      HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_ERROR);
                     return rv;
                 }
 
                 return NS_OK;
             }
 
-            // The request was already upgraded, for example by
-            // upgrade-insecure-requests or a prior successful priming request
-            Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
-                    HSTSPrimingResult::eHSTS_PRIMING_ALREADY_UPGRADED);
+            if (!mLoadInfo->GetIsHSTSPrimingUpgrade()) {
+                // The request was already upgraded, for example by a prior
+                // successful priming request
+                LOG(("HSTS Priming: request already upgraded"));
+                Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
+                              HSTSPrimingResult::eHSTS_PRIMING_ALREADY_UPGRADED);
+
+                // No HSTS Priming request was sent.
+                Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
+                              HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_ALREADY_UPGRADED);
+            }
+
             mLoadInfo->ClearHSTSPriming();
-        }
+            return ContinueConnect();
+        }
+
+        if (!mLoadInfo->GetIsHSTSPrimingUpgrade()) {
+            // No HSTS Priming request was sent, and we didn't already record this request
+            Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
+                                  HSTSPrimingRequest::eHSTS_PRIMING_NO_REQUEST);
+        }
+    } else {
+        Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
+                          HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_NO_LOAD_INFO);
     }
 
     return ContinueConnect();
 }
 
 // nsIInputAvailableCallback (nsIStreamTransportService.idl)
 NS_IMETHODIMP
 nsHttpChannel::OnInputAvailableComplete(uint64_t size, nsresult status)
@@ -8593,45 +8618,54 @@ nsHttpChannel::OnPreflightFailed(nsresul
 
 /*
  * May be invoked synchronously if HSTS priming has already been performed
  * for the host.
  */
 nsresult
 nsHttpChannel::OnHSTSPrimingSucceeded(bool aCached)
 {
+    // If "security.mixed_content.use_hsts" is false, record the result of
+    // HSTS priming and block or proceed with the load as required by
+    // mixed-content blocking
+    bool wouldBlock = mLoadInfo->GetMixedContentWouldBlock();
+    // Clear out the HSTS priming flags on the LoadInfo to simplify the logic in
+    // TryHSTSPriming()
+    mLoadInfo->ClearHSTSPriming();
+
     if (nsMixedContentBlocker::sUseHSTS) {
         // redirect the channel to HTTPS if the pref
         // "security.mixed_content.use_hsts" is true
         LOG(("HSTS Priming succeeded, redirecting to HTTPS [this=%p]", this));
         Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
                 (aCached) ? HSTSPrimingResult::eHSTS_PRIMING_CACHED_DO_UPGRADE :
                             HSTSPrimingResult::eHSTS_PRIMING_SUCCEEDED);
+        // we have to record this upgrade here
+        Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 3);
+        mLoadInfo->SetIsHSTSPrimingUpgrade(true);
         return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
     }
 
-    // If "security.mixed_content.use_hsts" is false, record the result of
-    // HSTS priming and block or proceed with the load as required by
-    // mixed-content blocking
-    bool wouldBlock = mLoadInfo->GetMixedContentWouldBlock();
-
     // preserve the mixed-content-before-hsts order and block if required
     if (wouldBlock) {
         LOG(("HSTS Priming succeeded, blocking for mixed-content [this=%p]",
                     this));
         Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
                               HSTSPrimingResult::eHSTS_PRIMING_SUCCEEDED_BLOCK);
         CloseCacheEntry(false);
         return AsyncAbort(NS_ERROR_CONTENT_BLOCKED);
     }
 
     LOG(("HSTS Priming succeeded, loading insecure: [this=%p]", this));
     Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
                           HSTSPrimingResult::eHSTS_PRIMING_SUCCEEDED_HTTP);
 
+    // log HTTP_SCHEME_UPGRADE telemetry
+    Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 0);
+
     nsresult rv = ContinueConnect();
     if (NS_FAILED(rv)) {
         CloseCacheEntry(false);
         return AsyncAbort(rv);
     }
 
     return NS_OK;
 }
@@ -8639,16 +8673,19 @@ nsHttpChannel::OnHSTSPrimingSucceeded(bo
 /*
  * May be invoked synchronously if HSTS priming has already been performed
  * for the host.
  */
 nsresult
 nsHttpChannel::OnHSTSPrimingFailed(nsresult aError, bool aCached)
 {
     bool wouldBlock = mLoadInfo->GetMixedContentWouldBlock();
+    // Clear out the HSTS priming flags on the LoadInfo to simplify the logic in
+    // TryHSTSPriming()
+    mLoadInfo->ClearHSTSPriming();
 
     LOG(("HSTS Priming Failed [this=%p], %s the load", this,
                 (wouldBlock) ? "blocking" : "allowing"));
     if (aError == NS_ERROR_HSTS_PRIMING_TIMEOUT) {
         // A priming request was sent, but timed out
         Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
                 (wouldBlock) ?  HSTSPrimingResult::eHSTS_PRIMING_TIMEOUT_BLOCK :
                 HSTSPrimingResult::eHSTS_PRIMING_TIMEOUT_ACCEPT);
@@ -8680,16 +8717,19 @@ nsHttpChannel::OnHSTSPrimingFailed(nsres
     }
 
     // If we would block, go ahead and abort with the error provided
     if (wouldBlock) {
         CloseCacheEntry(false);
         return AsyncAbort(aError);
     }
 
+    // log HTTP_SCHEME_UPGRADE telemetry
+    Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 0);
+
     // we can continue the load and the UI has been updated as mixed content
     rv = ContinueConnect();
     if (NS_FAILED(rv)) {
         CloseCacheEntry(false);
         return AsyncAbort(rv);
     }
 
     return NS_OK;
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -10102,45 +10102,54 @@
     "record_in_processes": ["main", "content"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 3,
     "description": "A simple counter of daily mixed-content unblock operations and top documents loaded"
   },
   "MIXED_CONTENT_HSTS": {
     "record_in_processes": ["main", "content"],
-    "alert_emails": ["seceng@mozilla.org"],
+    "alert_emails": ["seceng-telemetry@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 10,
     "description": "How often would blocked mixed content be allowed if HSTS upgrades were allowed? 0=display/no-HSTS, 1=display/HSTS, 2=active/no-HSTS, 3=active/HSTS"
   },
-  "MIXED_CONTENT_HSTS_PRIMING": {
-    "record_in_processes": ["main", "content"],
-    "alert_emails": ["seceng@mozilla.org"],
-    "bug_numbers": [1246540],
+  "MIXED_CONTENT_HSTS_PRIMING_2": {
+    "record_in_processes": [ "main" ],
+    "alert_emails": ["seceng-telemetry@mozilla.com"],
+    "bug_numbers": [1359987],
     "expires_in_version": "60",
     "kind": "enumerated",
     "n_values": 16,
-    "description": "How often would blocked mixed content be allowed if HSTS upgrades were allowed, including how often would we send an HSTS priming request? 0=display/no-HSTS, 1=display/HSTS, 2=active/no-HSTS, 3=active/HSTS, 4=display/no-HSTS-priming, 5=display/do-HSTS-priming, 6=active/no-HSTS-priming, 7=active/do-HSTS-priming"
+    "description": "How often would blocked mixed content be allowed if HSTS upgrades were allowed, including how often would we send an HSTS priming request? 0=passive/HSTS, 1=active/HSTS, 2=passive/no priming, 3=passive/priming, 4=active/no priming, 5=active/priming, 6=passive/will HSTS upgrade, 7=active/will HSTS upgrade"
   },
   "MIXED_CONTENT_HSTS_PRIMING_RESULT": {
-    "record_in_processes": ["main", "content"],
-    "alert_emails": ["seceng@mozilla.org"],
+    "record_in_processes": [ "main" ],
+    "alert_emails": ["seceng-telemetry@mozilla.com"],
     "bug_numbers": [1246540],
     "expires_in_version": "60",
     "kind": "enumerated",
     "n_values": 16,
     "description": "How often do we get back an HSTS priming result which upgrades the connection to HTTPS? 0=cached (no upgrade), 1=cached (do upgrade), 2=cached (blocked), 3=already upgraded, 4=priming succeeded, 5=priming succeeded (block due to pref), 6=priming succeeded (no upgrade due to pref), 7=priming failed (block), 8=priming failed (accept), 9=timeout (block), 10=timeout (accept)"
   },
+  "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": {
+    "record_in_processes": [ "main" ],
+    "alert_emails": ["seceng-telemetry@mozilla.com"],
+    "bug_numbers": [1359987],
+    "expires_in_version": "62",
+    "kind": "enumerated",
+    "n_values": 10,
+    "description": "How often does a request result in HSTS priming? (0=Sent HSTS priming, 1=No priming, 2=Priming skipped due to cached HSTS, 3=Priming skipped due to cached NO HSTS, 4=Priming failed (request error), 5=Priming skipped (missing load info), 6=Priming skipped (already upgraded)"
+  },
   "HSTS_PRIMING_REQUEST_DURATION": {
-    "record_in_processes": ["main", "content"],
-    "alert_emails": ["seceng-telemetry@mozilla.org"],
-    "bug_numbers": [1311893],
-    "expires_in_version": "58",
+    "record_in_processes": [ "main" ],
+    "alert_emails": ["seceng-telemetry@mozilla.com"],
+    "bug_numbers": [1311893, 1359987],
+    "expires_in_version": "62",
     "kind": "exponential",
     "low": 100,
     "high": 30000,
     "n_buckets": 100,
     "keyed": true,
     "description": "The amount of time required for HSTS priming requests (ms), keyed by success or failure of the priming request. (success, failure)"
   },
   "MIXED_CONTENT_OBJECT_SUBREQUEST": {