Bug 1533363 - Part 1: Add HttpTrafficAnalyzer service; r=dragana a=pascalc
☠☠ backed out by ca196717df07 ☠ ☠
authorLiang-Heng Chen <xeonchen@gmail.com>
Thu, 04 Apr 2019 21:38:28 +0000
changeset 523177 a258da1f2a67b56af0f3fe8d817eb84a433c53c6
parent 523176 fb31245cc40c1dc7f4d179552415ee1b03ab959e
child 523178 e27ad01b9daa2da02cca20e94b9d60ca862a21d4
push id11088
push useropoprus@mozilla.com
push dateMon, 15 Apr 2019 12:42:31 +0000
treeherdermozilla-beta@74c650902989 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdragana, pascalc
bugs1533363
milestone67.0
Bug 1533363 - Part 1: Add HttpTrafficAnalyzer service; r=dragana a=pascalc Differential Revision: https://phabricator.services.mozilla.com/D24517
modules/libpref/init/StaticPrefList.h
netwerk/protocol/http/HttpTrafficAnalyzer.cpp
netwerk/protocol/http/HttpTrafficAnalyzer.h
netwerk/protocol/http/HttpTrafficAnalyzer.inc
netwerk/protocol/http/moz.build
netwerk/protocol/http/nsAHttpConnection.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnection.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1975,16 +1975,23 @@ PREF("network.predictor.cleaned-up", boo
 
 // A testing flag.
 VARCACHE_PREF(
   "network.predictor.doing-tests",
    network_predictor_doing_tests,
   bool, false
 )
 
+// Telemetry of traffic categories
+VARCACHE_PREF(
+  "network.traffic_analyzer.enabled",
+  network_traffic_analyzer_enabled,
+  RelaxedAtomicBool, false
+)
+
 //---------------------------------------------------------------------------
 // ContentSessionStore prefs
 //---------------------------------------------------------------------------
 // Maximum number of bytes of DOMSessionStorage data we collect per origin.
 VARCACHE_PREF(
   "browser.sessionstore.dom_storage_limit",
   browser_sessionstore_dom_storage_limit,
   uint32_t, 2048
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/HttpTrafficAnalyzer.cpp
@@ -0,0 +1,162 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "HttpTrafficAnalyzer.h"
+#include "HttpLog.h"
+
+#include "mozilla/StaticPrefs.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+
+#define DEFINE_CATEGORY(_name, _idx) NS_LITERAL_CSTRING("Y=" #_idx "|" #_name),
+static const nsCString gKeyName[] = {
+#include "HttpTrafficAnalyzer.inc"
+};
+#undef DEFINE_CATEGORY
+
+// ----------------------------------------------------
+// | Flags                           |   Load Type    |
+// ----------------------------------------------------
+// | nsIClassOfService::Leader       |       A        |
+// | w/o nsIRequest::LOAD_BACKGROUND |       B        |
+// | w/ nsIRequest::LOAD_BACKGROUND  |       C        |
+// ----------------------------------------------------
+// | Category                        | List Category  |
+// ----------------------------------------------------
+// | Content                         |       I        |
+// | Basic Disconnected List         |      II        |
+// | Fingerprinting                  |     III        |
+// ----------------------------------------------------
+// ====================================================
+// | Normal Mode                                      |
+// ----------------------------------------------------
+// | Y = 0 for first party                            |
+// | Y = 1 for non-listed third party type            |
+// ----------------------------------------------------
+// |          \Y\          | Type A | Type B | Type C |
+// ----------------------------------------------------
+// | Category I            |    2   |    3   |    4   |
+// | Category II           |    5   |    6   |    7   |
+// | Category III          |    8   |    9   |   10   |
+// ====================================================
+// | Private Mode                                     |
+// ----------------------------------------------------
+// | Y = 11 for first party                           |
+// | Y = 12 for non-listed third party type           |
+// ----------------------------------------------------
+// |          \Y\          | Type A | Type B | Type C |
+// ----------------------------------------------------
+// | Category I            |   13   |   14   |   15   |
+// | Category II           |   16   |   17   |   18   |
+// | Category III          |   19   |   20   |   21   |
+// ====================================================
+
+HttpTrafficCategory HttpTrafficAnalyzer::CreateTrafficCategory(
+    bool aIsPrivateMode, bool aIsThirdParty, ClassOfService aClassOfService,
+    TrackingClassification aClassification) {
+  uint8_t category = aIsPrivateMode ? 11 : 0;
+  if (!aIsThirdParty) {
+    return static_cast<HttpTrafficCategory>(category);
+  }
+
+  switch (aClassification) {
+    case TrackingClassification::eNone:
+      return static_cast<HttpTrafficCategory>(category + 1);
+    case TrackingClassification::eBasic:
+      category += 2;
+      break;
+    case TrackingClassification::eContent:
+      category += 5;
+      break;
+    case TrackingClassification::eFingerprinting:
+      category += 8;
+      break;
+    default:
+      MOZ_ASSERT(false, "incorrect classification");
+      return HttpTrafficCategory::eInvalid;
+  }
+
+  switch (aClassOfService) {
+    case ClassOfService::eLeader:
+      return static_cast<HttpTrafficCategory>(category);
+    case ClassOfService::eBackground:
+      return static_cast<HttpTrafficCategory>(category + 1);
+    case ClassOfService::eOther:
+      return static_cast<HttpTrafficCategory>(category + 2);
+  }
+
+  MOZ_ASSERT(false, "incorrect class of service");
+  return HttpTrafficCategory::eInvalid;
+}
+
+nsresult HttpTrafficAnalyzer::IncrementHttpTransaction(
+    HttpTrafficCategory aCategory) {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+  MOZ_ASSERT(aCategory != HttpTrafficCategory::eInvalid, "invalid category");
+
+  LOG(("HttpTrafficAnalyzer::IncrementHttpTransaction [%s] [this=%p]\n",
+       gKeyName[aCategory].get(), this));
+
+  return NS_OK;
+}
+
+nsresult HttpTrafficAnalyzer::IncrementHttpConnection(
+    HttpTrafficCategory aCategory) {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+  MOZ_ASSERT(aCategory != HttpTrafficCategory::eInvalid, "invalid category");
+
+  LOG(("HttpTrafficAnalyzer::IncrementHttpConnection [%s] [this=%p]\n",
+       gKeyName[aCategory].get(), this));
+
+  return NS_OK;
+}
+
+nsresult HttpTrafficAnalyzer::IncrementHttpConnection(
+    nsTArray<HttpTrafficCategory> &&aCategories) {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+  MOZ_ASSERT(!aCategories.IsEmpty(), "empty category");
+
+  nsTArray<HttpTrafficCategory> categories(std::move(aCategories));
+
+  LOG(("HttpTrafficAnalyzer::IncrementHttpConnection size=%" PRIuPTR
+       " [this=%p]\n",
+       categories.Length(), this));
+
+  // divide categories into 4 parts:
+  //   1) normal 1st-party (Y = 0)
+  //   2) normal 3rd-party (0 < Y < 11)
+  //   3) private 1st-party (Y = 11)
+  //   4) private 3rd-party (11 < Y < 22)
+  // Normal and private transaction should not share the same connection,
+  // and we choose 3rd-party prior than 1st-party.
+  HttpTrafficCategory best = categories[0];
+  if ((best == 0 || best == 11) && categories.Length() > 1) {
+    best = categories[1];
+  }
+  Unused << IncrementHttpConnection(best);
+
+  return NS_OK;
+}
+
+nsresult HttpTrafficAnalyzer::AccumulateHttpTransferredSize(
+    HttpTrafficCategory aCategory, uint64_t aBytesRead, uint64_t aBytesSent) {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+  MOZ_ASSERT(aCategory != HttpTrafficCategory::eInvalid, "invalid category");
+
+  LOG(("HttpTrafficAnalyzer::AccumulateHttpTransferredSize [%s] rb=%" PRIu64 " "
+       "sb=%" PRIu64 " [this=%p]\n",
+       gKeyName[aCategory].get(), aBytesRead, aBytesSent, this));
+
+  return NS_OK;
+}
+
+}  // namespace net
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/HttpTrafficAnalyzer.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 mozilla_netwerk_protocol_http_HttpTrafficAnalyzer_h
+#define mozilla_netwerk_protocol_http_HttpTrafficAnalyzer_h
+
+namespace mozilla {
+namespace net {
+
+#define DEFINE_CATEGORY(_name, _idx) e##_name = _idx##u,
+enum HttpTrafficCategory : uint8_t {
+#include "HttpTrafficAnalyzer.inc"
+  eInvalid = 255,
+};
+#undef DEFINE_CATEGORY
+
+class HttpTrafficAnalyzer final {
+ public:
+  enum ClassOfService : uint8_t {
+    eLeader = 0,
+    eBackground = 1,
+    eOther = 255,
+  };
+
+  enum TrackingClassification : uint8_t {
+    eNone = 0,
+    eBasic = 1,
+    eContent = 2,
+    eFingerprinting = 3,
+  };
+
+  static HttpTrafficCategory CreateTrafficCategory(
+      bool aIsPrivateMode, bool aIsThirdParty, ClassOfService aClassOfService,
+      TrackingClassification aClassification);
+
+  nsresult IncrementHttpTransaction(HttpTrafficCategory aCategory);
+  nsresult IncrementHttpConnection(HttpTrafficCategory aCategory);
+  nsresult IncrementHttpConnection(nsTArray<HttpTrafficCategory> &&aCategories);
+  nsresult AccumulateHttpTransferredSize(HttpTrafficCategory aCategory,
+                                         uint64_t aBytesRead,
+                                         uint64_t aBytesSent);
+};
+
+}  // namespace net
+}  // namespace mozilla
+
+#endif  // mozilla_netwerk_protocol_http_HttpTrafficAnalyzer_h
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/HttpTrafficAnalyzer.inc
@@ -0,0 +1,100 @@
+// Type A) Parser blocking script loads will have an mContentPolicyType
+//         indicating a script load and a nsIClassOfService::Leader class of service.
+// Type B) I couldn’t find a class flag that corresponds to synchronous loads
+//         that block the load event, but I think a simple way to determine
+//         which ones they are is to examine whether the channel belongs to a
+//         load group and do not have a LOAD_BACKGROUND load flag.
+//         If that condition holds, then it is blocking the load event of some document.
+// Type C) I think a simple way to find channels that don’t block document loads
+//         (e.g. XHR and fetch) is to look for those which are in a load group
+//         and have a LOAD_BACKGROUND load flag.
+
+
+// Y=0 - all requests/connections/bytes that are first party.
+DEFINE_CATEGORY(NormalFirstParty, 0)
+
+// Y=1 - all requests/connections/bytes that are third party
+// but don’t fall into other categories
+DEFINE_CATEGORY(NormalThirdPartyOther, 1)
+
+// Y=2 - all requests/connections/bytes associated with third party loads that
+// match the Content Category and have a load of type (A)
+DEFINE_CATEGORY(NormalThirdPartyContentLeader, 2)
+
+// Y=3 - all requests/connections/bytes associated with third party loads that
+// match the Content Category and have a load of type (B)
+DEFINE_CATEGORY(NormalThirdPartyContentBackground, 3)
+
+// Y=4 - all requests/connections/bytes associated with third party loads that
+// match the Content Category and have a load of type (C)
+DEFINE_CATEGORY(NormalThirdPartyContentOther, 4)
+
+// Y=5 - all requests/connections/bytes associated with third party loads that
+// match the Analytics/Social/Advertising (Basic) Category and have a load of type (A)
+DEFINE_CATEGORY(NormalThirdPartyBasicLeader, 5)
+
+// Y=6 - all requests/connections/bytes associated with third party loads that
+// match the Analytics/Social/Advertising (Basic) Category and have a load of type (B)
+DEFINE_CATEGORY(NormalThirdPartyBasicBackground, 6)
+
+// Y=7 - all requests/connections/bytes associated with third party loads that
+// match the Analytics/Social/Advertising (Basic) Category and have a load of type (C)
+DEFINE_CATEGORY(NormalThirdPartyBasicOther, 7)
+
+// Y=8 - all requests/connections/bytes associated with third party loads that
+// match the Fingerprinting Category and have a load of type (A)
+DEFINE_CATEGORY(NormalThirdPartyFingerprintingLeader, 8)
+
+// Y=9 - all requests/connections/bytes associated with third party loads that
+// match the Fingerprinting Category and have a load of type (B)
+DEFINE_CATEGORY(NormalThirdPartyFingerprintingBackground, 9)
+
+// Y=10 - all requests/connections/bytes associated with third party loads that
+// match the Fingerprinting Category and have a load of type (C)
+DEFINE_CATEGORY(NormalThirdPartyFingerprintingOther, 10)
+
+// Y=11 - private mode and all requests/connections/bytes that are first party.
+DEFINE_CATEGORY(PrivateFirstParty, 11)
+
+// Y=12 - private mode and all requests/connections/bytes that are third party
+// but don’t fall into other categories
+DEFINE_CATEGORY(PrivateThirdPartyOther, 12)
+
+// Y=13 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Content Category and have a load of type (A)
+DEFINE_CATEGORY(PrivateThirdPartyContentLeader, 13)
+
+// Y=14 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Content Category and have a load of type (B)
+DEFINE_CATEGORY(PrivateThirdPartyContentBackground, 14)
+
+// Y=15 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Content Category and have a load of type (C)
+DEFINE_CATEGORY(PrivateThirdPartyContentOther, 15)
+
+// Y=16 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Analytics/Social/Advertising (Basic) Category
+// and have a load of type (A)
+DEFINE_CATEGORY(PrivateThirdPartyBasicLeader, 16)
+
+// Y=17 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Analytics/Social/Advertising (Basic) Category
+// and have a load of type (B)
+DEFINE_CATEGORY(PrivateThirdPartyBasicBackground, 17)
+
+// Y=18 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Analytics/Social/Advertising (Basic) Category
+// and have a load of type (C)
+DEFINE_CATEGORY(PrivateThirdPartyBasicOther, 18)
+
+// Y=19 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Fingerprinting Category and have a load of type (A)
+DEFINE_CATEGORY(PrivateThirdPartyFingerprintingLeader, 19)
+
+// Y=20 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Fingerprinting Category and have a load of type (B)
+DEFINE_CATEGORY(PrivateThirdPartyFingerprintingBackground, 20)
+
+// Y=21 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Fingerprinting Category and have a load of type (C)
+DEFINE_CATEGORY(PrivateThirdPartyFingerprintingOther, 21)
--- a/netwerk/protocol/http/moz.build
+++ b/netwerk/protocol/http/moz.build
@@ -79,16 +79,17 @@ UNIFIED_SOURCES += [
     'HttpAuthUtils.cpp',
     'HttpBackgroundChannelChild.cpp',
     'HttpBackgroundChannelParent.cpp',
     'HttpBaseChannel.cpp',
     'HttpChannelChild.cpp',
     'HttpChannelParent.cpp',
     'HttpChannelParentListener.cpp',
     'HttpInfo.cpp',
+    'HttpTrafficAnalyzer.cpp',
     'InterceptedChannel.cpp',
     'InterceptedHttpChannel.cpp',
     'nsCORSListenerProxy.cpp',
     'nsHttp.cpp',
     'nsHttpActivityDistributor.cpp',
     'nsHttpAuthCache.cpp',
     'nsHttpAuthManager.cpp',
     'nsHttpBasicAuth.cpp',
--- a/netwerk/protocol/http/nsAHttpConnection.h
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -144,16 +144,20 @@ class nsAHttpConnection : public nsISupp
   // any thread.
   virtual void SetSecurityCallbacks(nsIInterfaceRequestor *aCallbacks) = 0;
 
   // nsHttp.h version
   virtual HttpVersion Version() = 0;
 
   // A notification of the current active tab id change.
   virtual void TopLevelOuterContentWindowIdChanged(uint64_t windowId) = 0;
+
+  // categories set by nsHttpTransaction to identify how this connection is
+  // being used.
+  virtual void SetTrafficCategory(HttpTrafficCategory) = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpConnection, NS_AHTTPCONNECTION_IID)
 
 #define NS_DECL_NSAHTTPCONNECTION(fwdObject)                                  \
   MOZ_MUST_USE nsresult OnHeadersAvailable(                                   \
       nsAHttpTransaction *, nsHttpRequestHead *, nsHttpResponseHead *,        \
       bool *reset) override;                                                  \
@@ -220,16 +224,19 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpCon
   void SetLastTransactionExpectedNoContent(bool val) override {               \
     if (fwdObject) (fwdObject)->SetLastTransactionExpectedNoContent(val);     \
   }                                                                           \
   int64_t BytesWritten() override {                                           \
     return fwdObject ? (fwdObject)->BytesWritten() : 0;                       \
   }                                                                           \
   void SetSecurityCallbacks(nsIInterfaceRequestor *aCallbacks) override {     \
     if (fwdObject) (fwdObject)->SetSecurityCallbacks(aCallbacks);             \
+  }                                                                           \
+  void SetTrafficCategory(HttpTrafficCategory aCategory) override {           \
+    if (fwdObject) (fwdObject)->SetTrafficCategory(aCategory);                \
   }
 
 // ThrottleResponse deliberately ommited since we want different implementation
 // for h1 and h2 connections.
 
 }  // namespace net
 }  // namespace mozilla
 
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -120,16 +120,17 @@
 #include "nsINetworkLinkService.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ServiceWorkerUtils.h"
 #include "mozilla/net/AsyncUrlChannelClassifier.h"
 #include "mozilla/net/NeckoChannelParams.h"
 #include "mozilla/net/UrlClassifierFeatureFactory.h"
 #include "nsIWebNavigation.h"
+#include "HttpTrafficAnalyzer.h"
 
 #ifdef MOZ_TASK_TRACER
 #  include "GeckoTaskTracer.h"
 #endif
 
 #ifdef MOZ_GECKO_PROFILER
 #  include "ProfilerMarkerPayload.h"
 #endif
@@ -1238,36 +1239,83 @@ nsresult nsHttpChannel::SetupTransaction
                                 NS_GET_IID(nsIHttpPushListener),
                                 getter_AddRefs(pushListener));
   if (pushListener) {
     mCaps |= NS_HTTP_ONPUSH_LISTENER;
   }
 
   EnsureTopLevelOuterContentWindowId();
 
+  HttpTrafficCategory category = CreateTrafficCategory();
+
   nsCOMPtr<nsIAsyncInputStream> responseStream;
   rv = mTransaction->Init(
       mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mReqContentLength,
       mUploadStreamHasHeaders, GetCurrentThreadEventTarget(), callbacks, this,
-      mTopLevelOuterContentWindowId, getter_AddRefs(responseStream));
+      mTopLevelOuterContentWindowId, category, getter_AddRefs(responseStream));
   if (NS_FAILED(rv)) {
     mTransaction = nullptr;
     return rv;
   }
 
   mTransaction->SetClassOfService(mClassOfService);
   if (EnsureRequestContext()) {
     mTransaction->SetRequestContext(mRequestContext);
   }
 
   rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
                                  responseStream);
   return rv;
 }
 
+HttpTrafficCategory nsHttpChannel::CreateTrafficCategory() {
+  MOZ_ASSERT(!mFirstPartyClassificationFlags ||
+             !mThirdPartyClassificationFlags);
+
+  if (!StaticPrefs::network_traffic_analyzer_enabled()) {
+    return HttpTrafficCategory::eInvalid;
+  }
+
+  HttpTrafficAnalyzer::ClassOfService cos;
+  {
+    if (mClassOfService & nsIClassOfService::Leader &&
+        mLoadInfo->GetExternalContentPolicyType() ==
+            nsIContentPolicy::TYPE_SCRIPT) {
+      cos = HttpTrafficAnalyzer::ClassOfService::eLeader;
+    } else if (mLoadFlags & nsIRequest::LOAD_BACKGROUND) {
+      cos = HttpTrafficAnalyzer::ClassOfService::eBackground;
+    } else {
+      cos = HttpTrafficAnalyzer::ClassOfService::eOther;
+    }
+  }
+
+  bool isThirdParty = !!mThirdPartyClassificationFlags;
+  HttpTrafficAnalyzer::TrackingClassification tc;
+  {
+    uint32_t flags = isThirdParty ? mThirdPartyClassificationFlags
+                                  : mFirstPartyClassificationFlags;
+
+    using CF = nsIHttpChannel::ClassificationFlags;
+    using TC = HttpTrafficAnalyzer::TrackingClassification;
+
+    if (flags & CF::CLASSIFIED_TRACKING_CONTENT) {
+      tc = TC::eContent;
+    } else if (flags & CF::CLASSIFIED_FINGERPRINTING) {
+      tc = TC::eFingerprinting;
+    } else if (flags & CF::CLASSIFIED_ANY_BASIC_TRACKING) {
+      tc = TC::eBasic;
+    } else {
+      tc = TC::eNone;
+    }
+  }
+
+  return HttpTrafficAnalyzer::CreateTrafficCategory(NS_UsePrivateBrowsing(this),
+                                                    isThirdParty, cos, tc);
+}
+
 enum class Report { Error, Warning };
 
 // Helper Function to report messages to the console when the loaded
 // script had a wrong MIME type.
 void ReportMimeTypeMismatch(nsHttpChannel *aChannel, const char *aMessageName,
                             nsIURI *aURI, const nsACString &aContentType,
                             Report report) {
   NS_ConvertUTF8toUTF16 spec(aURI->GetSpecOrDefault());
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -445,16 +445,18 @@ class nsHttpChannel final : public HttpB
   MOZ_MUST_USE nsresult ContinueOnStopRequest(nsresult status, bool aIsFromNet,
                                               bool aContentComplete);
 
   void HandleAsyncRedirectChannelToHttps();
   MOZ_MUST_USE nsresult StartRedirectChannelToHttps();
   MOZ_MUST_USE nsresult ContinueAsyncRedirectChannelToURI(nsresult rv);
   MOZ_MUST_USE nsresult OpenRedirectChannel(nsresult rv);
 
+  HttpTrafficCategory CreateTrafficCategory();
+
   /**
    * A function that takes care of reading STS and PKP headers and enforcing
    * STS and PKP load rules. After a secure channel is erected, STS and PKP
    * requires the channel to be trusted or any STS or PKP header data on
    * the channel is ignored. This is called from ProcessResponse.
    */
   MOZ_MUST_USE nsresult ProcessSecurityHeaders();
 
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -967,16 +967,24 @@ void nsHttpConnection::Close(nsresult re
     mTCPKeepaliveTransitionTimer->Cancel();
     mTCPKeepaliveTransitionTimer = nullptr;
   }
   if (mForceSendTimer) {
     mForceSendTimer->Cancel();
     mForceSendTimer = nullptr;
   }
 
+  if (!mTrafficCategory.IsEmpty()) {
+    HttpTrafficAnalyzer *hta = gHttpHandler->GetHttpTrafficAnalyzer();
+    if (hta) {
+      hta->IncrementHttpConnection(std::move(mTrafficCategory));
+      MOZ_ASSERT(mTrafficCategory.IsEmpty());
+    }
+  }
+
   if (NS_FAILED(reason)) {
     if (mIdleMonitoring) EndIdleMonitoring();
 
     mTLSFilter = nullptr;
 
     // The connection and security errors clear out alt-svc mappings
     // in case any previously validated ones are now invalid
     if (((reason == NS_ERROR_NET_RESET) ||
@@ -2651,10 +2659,19 @@ bool nsHttpConnection::NoClientCertAuth(
 bool nsHttpConnection::CanAcceptWebsocket() {
   if (!UsingSpdy()) {
     return true;
   }
 
   return mSpdySession->CanAcceptWebsocket();
 }
 
+void nsHttpConnection::SetTrafficCategory(HttpTrafficCategory aCategory) {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  if (aCategory == HttpTrafficCategory::eInvalid ||
+      mTrafficCategory.Contains(aCategory)) {
+    return;
+  }
+  Unused << mTrafficCategory.AppendElement(aCategory);
+}
+
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -12,16 +12,17 @@
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsProxyRelease.h"
 #include "prinrval.h"
 #include "TunnelUtils.h"
 #include "mozilla/Mutex.h"
 #include "ARefBase.h"
 #include "TimingStruct.h"
+#include "HttpTrafficAnalyzer.h"
 
 #include "nsIAsyncInputStream.h"
 #include "nsIAsyncOutputStream.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsITimer.h"
 
 class nsISocketTransport;
 class nsISSLSocketControl;
@@ -246,16 +247,18 @@ class nsHttpConnection final : public ns
   // Return true when the socket this connection is using has not been
   // authenticated using a client certificate.  Before SSL negotiation
   // has finished this returns false.
   bool NoClientCertAuth() const;
 
   // HTTP/2 websocket support
   bool CanAcceptWebsocket();
 
+  void SetTrafficCategory(HttpTrafficCategory aCategory);
+
  private:
   // Value (set in mTCPKeepaliveConfig) indicates which set of prefs to use.
   enum TCPKeepaliveConfig {
     kTCPKeepaliveDisabled = 0,
     kTCPKeepaliveShortLivedConfig,
     kTCPKeepaliveLongLivedConfig
   };
 
@@ -431,16 +434,18 @@ class nsHttpConnection final : public ns
   PRIntervalTime mLastRequestBytesSentTime;
 
  public:
   void BootstrapTimings(TimingStruct times);
 
  private:
   TimingStruct mBootstrappedTimings;
   bool mBootstrappedTimingsSet;
+
+  nsTArray<HttpTrafficCategory> mTrafficCategory;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpConnection, NS_HTTPCONNECTION_IID)
 
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // nsHttpConnection_h__
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -2640,10 +2640,20 @@ void nsHttpHandler::BlacklistSpdy(const 
   mConnMgr->BlacklistSpdy(ci);
   mBlacklistedSpdyOrigins.PutEntry(ci->GetOrigin());
 }
 
 bool nsHttpHandler::IsSpdyBlacklisted(const nsHttpConnectionInfo *ci) {
   return mBlacklistedSpdyOrigins.Contains(ci->GetOrigin());
 }
 
+HttpTrafficAnalyzer *nsHttpHandler::GetHttpTrafficAnalyzer() {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+  if (!StaticPrefs::network_traffic_analyzer_enabled()) {
+    return nullptr;
+  }
+
+  return &mHttpTrafficAnalyzer;
+}
+
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -5,16 +5,17 @@
 
 #ifndef nsHttpHandler_h__
 #define nsHttpHandler_h__
 
 #include "nsHttp.h"
 #include "nsHttpAuthCache.h"
 #include "nsHttpConnectionMgr.h"
 #include "ASpdySession.h"
+#include "HttpTrafficAnalyzer.h"
 
 #include "mozilla/Mutex.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsWeakReference.h"
 
@@ -407,16 +408,18 @@ class nsHttpHandler final : public nsIHt
   // mLastActiveTabLoadOptimizationHit timestamp to now.
   void NotifyActiveTabLoadOptimization();
   TimeStamp const GetLastActiveTabLoadOptimizationHit();
   void SetLastActiveTabLoadOptimizationHit(TimeStamp const &when);
   bool IsBeforeLastActiveTabLoadOptimization(TimeStamp const &when);
 
   bool DumpHpackTables() { return mDumpHpackTables; }
 
+  HttpTrafficAnalyzer *GetHttpTrafficAnalyzer();
+
  private:
   nsHttpHandler();
 
   virtual ~nsHttpHandler();
 
   MOZ_MUST_USE nsresult Init();
 
   //
@@ -656,16 +659,18 @@ class nsHttpHandler final : public nsIHt
   uint32_t mFastOpenStallsLimit;
   uint32_t mFastOpenStallsCounter;
   uint32_t mFastOpenStallsIdleTime;
   uint32_t mFastOpenStallsTimeout;
 
   // If true, the transactions from active tab will be dispatched first.
   bool mActiveTabPriority;
 
+  HttpTrafficAnalyzer mHttpTrafficAnalyzer;
+
  private:
   // For Rate Pacing Certain Network Events. Only assign this pointer on
   // socket thread.
   void MakeNewRequestTokenBucket();
   RefPtr<EventTokenBucket> mRequestTokenBucket;
 
  public:
   // Socket thread only
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -141,17 +141,18 @@ nsHttpTransaction::nsHttpTransaction()
       mTopLevelOuterContentWindowId(0),
       mSubmittedRatePacing(false),
       mPassedRatePacing(false),
       mSynchronousRatePaceRequest(false),
       mClassOfService(0),
       m0RTTInProgress(false),
       mDoNotTryEarlyData(false),
       mEarlyDataDisposition(EARLY_NONE),
-      mFastOpenStatus(TFO_NOT_TRIED) {
+      mFastOpenStatus(TFO_NOT_TRIED),
+      mTrafficCategory(HttpTrafficCategory::eInvalid) {
   this->mSelfAddr.inet = {};
   this->mPeerAddr.inet = {};
   LOG(("Creating nsHttpTransaction @%p\n", this));
 
 #ifdef MOZ_VALGRIND
   memset(&mSelfAddr, 0, sizeof(NetAddr));
   memset(&mPeerAddr, 0, sizeof(NetAddr));
 #endif
@@ -256,29 +257,32 @@ nsHttpTransaction::~nsHttpTransaction() 
   }
 }
 
 nsresult nsHttpTransaction::Init(
     uint32_t caps, nsHttpConnectionInfo *cinfo, nsHttpRequestHead *requestHead,
     nsIInputStream *requestBody, uint64_t requestContentLength,
     bool requestBodyHasHeaders, nsIEventTarget *target,
     nsIInterfaceRequestor *callbacks, nsITransportEventSink *eventsink,
-    uint64_t topLevelOuterContentWindowId, nsIAsyncInputStream **responseBody) {
+    uint64_t topLevelOuterContentWindowId, HttpTrafficCategory trafficCategory,
+    nsIAsyncInputStream **responseBody) {
   nsresult rv;
 
   LOG1(("nsHttpTransaction::Init [this=%p caps=%x]\n", this, caps));
 
   MOZ_ASSERT(cinfo);
   MOZ_ASSERT(requestHead);
   MOZ_ASSERT(target);
   MOZ_ASSERT(NS_IsMainThread());
 
   mTopLevelOuterContentWindowId = topLevelOuterContentWindowId;
   LOG(("  window-id = %" PRIx64, mTopLevelOuterContentWindowId));
 
+  mTrafficCategory = trafficCategory;
+
   mActivityDistributor = services::GetActivityDistributor();
   if (!mActivityDistributor) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   bool activityDistributorActive;
   rv = mActivityDistributor->GetIsActive(&activityDistributorActive);
   if (NS_SUCCEEDED(rv) && activityDistributorActive) {
@@ -519,16 +523,26 @@ void nsHttpTransaction::SetConnection(ns
 
 void nsHttpTransaction::OnActivated() {
   MOZ_ASSERT(OnSocketThread());
 
   if (mActivated) {
     return;
   }
 
+  if (mTrafficCategory != HttpTrafficCategory::eInvalid) {
+    HttpTrafficAnalyzer *hta = gHttpHandler->GetHttpTrafficAnalyzer();
+    if (hta) {
+      hta->IncrementHttpTransaction(mTrafficCategory);
+    }
+    if (mConnection) {
+      mConnection->SetTrafficCategory(mTrafficCategory);
+    }
+  }
+
   if (mConnection && mRequestHead &&
       mConnection->Version() >= HttpVersion::v2_0) {
     // So this is fun. On http/2, we want to send TE: Trailers, to be
     // spec-compliant. So we add it to the request head here. The fun part
     // is that adding a header to the request head at this point has no
     // effect on what we send on the wire, as the headers are already
     // flattened (in Init()) by the time we get here. So the *real* adding
     // of the header happens in the h2 compression code. We still have to
@@ -1168,16 +1182,24 @@ void nsHttpTransaction::Close(nsresult r
   // EOF or an error still require an end time be recorded.
   if (TimingEnabled()) {
     const TimingStruct timings = Timings();
     if (timings.responseEnd.IsNull() && !timings.responseStart.IsNull()) {
       SetResponseEnd(TimeStamp::Now());
     }
   }
 
+  if (mTrafficCategory != HttpTrafficCategory::eInvalid) {
+    HttpTrafficAnalyzer *hta = gHttpHandler->GetHttpTrafficAnalyzer();
+    if (hta) {
+      hta->AccumulateHttpTransferredSize(mTrafficCategory, mTransferSize,
+                                         mContentRead);
+    }
+  }
+
   if (relConn && mConnection) {
     MutexAutoLock lock(mLock);
     mConnection = nullptr;
   }
 
   mStatus = reason;
   mTransactionDone = true;  // forcibly flag the transaction as complete
   mClosed = true;
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -75,25 +75,23 @@ class nsHttpTransaction final : public n
   // @param topLevelOuterContentWindowId
   //        indicate the top level outer content window in which
   //        this transaction is being loaded.
   // @param responseBody
   //        the input stream that will contain the response data.  async
   //        wait on this input stream for data.  on first notification,
   //        headers should be available (check transaction status).
   //
-  MOZ_MUST_USE nsresult Init(uint32_t caps, nsHttpConnectionInfo *connInfo,
-                             nsHttpRequestHead *reqHeaders,
-                             nsIInputStream *reqBody, uint64_t reqContentLength,
-                             bool reqBodyIncludesHeaders,
-                             nsIEventTarget *consumerTarget,
-                             nsIInterfaceRequestor *callbacks,
-                             nsITransportEventSink *eventsink,
-                             uint64_t topLevelOuterContentWindowId,
-                             nsIAsyncInputStream **responseBody);
+  MOZ_MUST_USE nsresult
+  Init(uint32_t caps, nsHttpConnectionInfo *connInfo,
+       nsHttpRequestHead *reqHeaders, nsIInputStream *reqBody,
+       uint64_t reqContentLength, bool reqBodyIncludesHeaders,
+       nsIEventTarget *consumerTarget, nsIInterfaceRequestor *callbacks,
+       nsITransportEventSink *eventsink, uint64_t topLevelOuterContentWindowId,
+       HttpTrafficCategory trafficCategory, nsIAsyncInputStream **responseBody);
 
   void OnActivated() override;
 
   // attributes
   nsHttpResponseHead *ResponseHead() {
     return mHaveAllHeaders ? mResponseHead : nullptr;
   }
   nsISupports *SecurityInfo() { return mSecurityInfo; }
@@ -479,14 +477,16 @@ class nsHttpTransaction final : public n
     EARLY_ACCEPTED,
     EARLY_425
   } mEarlyDataDisposition;
 
   uint8_t mFastOpenStatus;
 
   // H2 websocket support
   RefPtr<SpdyConnectTransaction> mH2WSTransaction;
+
+  HttpTrafficCategory mTrafficCategory;
 };
 
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // nsHttpTransaction_h__