Bug 772589 - Implement the secureConnectionStart property for the PerformanceTiming interface draft
authorPatrick McManus <mcmanus@ducksong.com>
Mon, 10 Jul 2017 15:01:35 -0400
changeset 610856 055ffebfbcce67ecaf1558ab607fd6ce259048e4
parent 610234 5e73b9798464c3f7106f0161dc9a49b234f42f9c
child 637967 db2ffe0765fbf29d870feecce63c37886c847c27
push id69012
push userbmo:mcmanus@ducksong.com
push dateTue, 18 Jul 2017 20:10:24 +0000
bugs772589
milestone56.0a1
Bug 772589 - Implement the secureConnectionStart property for the PerformanceTiming interface Implements PerformanceTiming, nsITimedChannel, and devtools 'tls setup' Also captures telemetry on this as we do for all other attributes of timedChannel Also propogates some null transaction timings onto first real transaction of a connection MozReview-Commit-ID: 47TQJYVHnKC
devtools/client/locales/en-US/netmonitor.properties
devtools/client/netmonitor/src/assets/styles/netmonitor.css
devtools/client/netmonitor/src/components/request-list-column-waterfall.js
devtools/client/netmonitor/src/components/timings-panel.js
devtools/client/netmonitor/test/browser_net_simple-request-data.js
devtools/shared/webconsole/network-monitor.js
dom/performance/PerformanceMainThread.cpp
dom/performance/PerformanceResourceTiming.h
dom/performance/PerformanceTiming.cpp
dom/performance/PerformanceTiming.h
dom/webidl/PerformanceTiming.webidl
netwerk/base/nsISocketTransport.idl
netwerk/base/nsITimedChannel.idl
netwerk/base/nsLoadGroup.cpp
netwerk/ipc/NeckoMessageUtils.h
netwerk/protocol/http/Http2Session.cpp
netwerk/protocol/http/HttpBaseChannel.cpp
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/http/NullHttpChannel.cpp
netwerk/protocol/http/NullHttpTransaction.cpp
netwerk/protocol/http/NullHttpTransaction.h
netwerk/protocol/http/TimingStruct.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnection.h
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
testing/web-platform/meta/navigation-timing/idlharness.html.ini
testing/web-platform/meta/user-timing/invoke_with_timing_attributes.html.ini
testing/web-platform/meta/user-timing/mark_exceptions.html.ini
toolkit/components/telemetry/Histograms.json
toolkit/components/telemetry/histogram-whitelists.json
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -232,16 +232,20 @@ netmonitor.waterfall.tooltip.total=Total
 # LOCALIZATION NOTE (netmonitor.waterfall.tooltip.blocked): This is part of the tooltip
 # displayed in the requests waterfall for blocked time (in milliseconds).
 netmonitor.waterfall.tooltip.blocked=Blocked %S ms
 
 # LOCALIZATION NOTE (netmonitor.waterfall.tooltip.dns): This is part of the tooltip
 # displayed in the requests waterfall for dns time (in milliseconds).
 netmonitor.waterfall.tooltip.dns=DNS %S ms
 
+# LOCALIZATION NOTE (netmonitor.waterfall.tooltip.ssl): This is part of the tooltip
+# displayed in the requests waterfall for tls setup time (in milliseconds).
+netmonitor.waterfall.tooltip.ssl=TLS %S ms
+
 # LOCALIZATION NOTE (netmonitor.waterfall.tooltip.connect): This is part of the tooltip
 # displayed in the requests waterfall for connect time (in milliseconds).
 netmonitor.waterfall.tooltip.connect=Connect %S ms
 
 # LOCALIZATION NOTE (netmonitor.waterfall.tooltip.send): This is part of the tooltip
 # displayed in the requests waterfall for send time (in milliseconds).
 netmonitor.waterfall.tooltip.send=Send %S ms
 
@@ -704,16 +708,21 @@ netmonitor.response.mime=MIME Type:
 # in a "blocked" state.
 netmonitor.timings.blocked=Blocked:
 
 # LOCALIZATION NOTE (netmonitor.timings.dns): This is the label displayed
 # in the network details timings tab identifying the amount of time spent
 # in a "dns" state.
 netmonitor.timings.dns=DNS resolution:
 
+# LOCALIZATION NOTE (netmonitor.timings.ssl): This is the label displayed
+# in the network details timings tab identifying the amount of time spent
+# in a "tls" handshake state.
+netmonitor.timings.ssl=TLS setup:
+
 # LOCALIZATION NOTE (netmonitor.timings.connect): This is the label displayed
 # in the network details timings tab identifying the amount of time spent
 # in a "connect" state.
 netmonitor.timings.connect=Connecting:
 
 # LOCALIZATION NOTE (netmonitor.timings.send): This is the label displayed
 # in the network details timings tab identifying the amount of time spent
 # in a "send" state.
--- a/devtools/client/netmonitor/src/assets/styles/netmonitor.css
+++ b/devtools/client/netmonitor/src/assets/styles/netmonitor.css
@@ -14,31 +14,33 @@
 @import "chrome://devtools/content/sourceeditor/codemirror/mozilla.css";
 
 :root.theme-dark {
   --table-splitter-color: rgba(255,255,255,0.15);
   --table-zebra-background: rgba(255,255,255,0.05);
 
   --timing-blocked-color: rgba(235, 83, 104, 0.8);
   --timing-dns-color: rgba(223, 128, 255, 0.8); /* pink */
+  --timing-ssl-color: rgba(217, 102, 41, 0.8); /* orange */
   --timing-connect-color: rgba(217, 102, 41, 0.8); /* orange */
   --timing-send-color: rgba(70, 175, 227, 0.8); /* light blue */
   --timing-wait-color: rgba(94, 136, 176, 0.8); /* blue grey */
   --timing-receive-color: rgba(112, 191, 83, 0.8); /* green */
 
   --sort-ascending-image: url(chrome://devtools/skin/images/sort-ascending-arrow.svg);
   --sort-descending-image: url(chrome://devtools/skin/images/sort-descending-arrow.svg);
 }
 
 :root.theme-light {
   --table-splitter-color: rgba(0,0,0,0.15);
   --table-zebra-background: rgba(0,0,0,0.05);
 
   --timing-blocked-color: rgba(235, 83, 104, 0.8);
   --timing-dns-color: rgba(223, 128, 255, 0.8); /* pink */
+  --timing-ssl-color: rgba(217, 102, 41, 0.8); /* orange */
   --timing-connect-color: rgba(217, 102, 41, 0.8); /* orange */
   --timing-send-color: rgba(0, 136, 204, 0.8); /* blue */
   --timing-wait-color: rgba(95, 136, 176, 0.8); /* blue grey */
   --timing-receive-color: rgba(44, 187, 15, 0.8); /* green */
 
   --sort-ascending-image: url(chrome://devtools/skin/images/sort-ascending-arrow.svg);
   --sort-descending-image: url(chrome://devtools/skin/images/sort-descending-arrow.svg);
 }
@@ -676,16 +678,20 @@ body,
 .requests-list-timings-box.dns {
   background-color: var(--timing-dns-color);
 }
 
 .requests-list-timings-box.connect {
   background-color: var(--timing-connect-color);
 }
 
+.requests-list-timings-box.ssl {
+  background-color: var(--timing-ssl-color);
+}
+
 .requests-list-timings-box.send {
   background-color: var(--timing-send-color);
 }
 
 .requests-list-timings-box.wait {
   background-color: var(--timing-wait-color);
 }
 
--- a/devtools/client/netmonitor/src/components/request-list-column-waterfall.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-waterfall.js
@@ -16,17 +16,17 @@ const { div } = DOM;
 
 const UPDATED_WATERFALL_PROPS = [
   "eventTimings",
   "fromCache",
   "fromServiceWorker",
   "totalTime",
 ];
 // List of properties of the timing info we want to create boxes for
-const TIMING_KEYS = ["blocked", "dns", "connect", "send", "wait", "receive"];
+const TIMING_KEYS = ["blocked", "dns", "connect", "ssl", "send", "wait", "receive"];
 
 const RequestListColumnWaterfall = createClass({
   displayName: "RequestListColumnWaterfall",
 
   propTypes: {
     firstRequestStartedMillis: PropTypes.number.isRequired,
     item: PropTypes.object.isRequired,
     onWaterfallMouseDown: PropTypes.func.isRequired,
--- a/devtools/client/netmonitor/src/components/timings-panel.js
+++ b/devtools/client/netmonitor/src/components/timings-panel.js
@@ -7,17 +7,17 @@
 const { DOM, PropTypes } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../utils/l10n");
 const { getNetMonitorTimingsURL } = require("../utils/mdn-utils");
 
 // Components
 const MDNLink = require("./mdn-link");
 
 const { div, span } = DOM;
-const types = ["blocked", "dns", "connect", "send", "wait", "receive"];
+const types = ["blocked", "dns", "connect", "ssl", "send", "wait", "receive"];
 const TIMINGS_END_PADDING = "80px";
 
 /*
  * Timings panel component
  * Display timeline bars that shows the total wait time for various stages
  */
 function TimingsPanel({ request }) {
   if (!request.eventTimings) {
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -322,16 +322,18 @@ function test() {
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       ok(requestItem.eventTimings,
         "There should be a eventTimings data available.");
       is(typeof requestItem.eventTimings.timings.blocked, "number",
         "The eventTimings data has an incorrect |timings.blocked| property.");
       is(typeof requestItem.eventTimings.timings.dns, "number",
         "The eventTimings data has an incorrect |timings.dns| property.");
+      is(typeof requestItem.eventTimings.timings.ssl, "number",
+        "The eventTimings data has an incorrect |timings.ssl| property.");
       is(typeof requestItem.eventTimings.timings.connect, "number",
         "The eventTimings data has an incorrect |timings.connect| property.");
       is(typeof requestItem.eventTimings.timings.send, "number",
         "The eventTimings data has an incorrect |timings.send| property.");
       is(typeof requestItem.eventTimings.timings.wait, "number",
         "The eventTimings data has an incorrect |timings.wait| property.");
       is(typeof requestItem.eventTimings.timings.receive, "number",
         "The eventTimings data has an incorrect |timings.receive| property.");
--- a/devtools/shared/webconsole/network-monitor.js
+++ b/devtools/shared/webconsole/network-monitor.js
@@ -726,17 +726,19 @@ NetworkMonitor.prototype = {
     0x5006: "TRANSACTION_CLOSE",
 
     0x804b0003: "STATUS_RESOLVING",
     0x804b000b: "STATUS_RESOLVED",
     0x804b0007: "STATUS_CONNECTING_TO",
     0x804b0004: "STATUS_CONNECTED_TO",
     0x804b0005: "STATUS_SENDING_TO",
     0x804b000a: "STATUS_WAITING_FOR",
-    0x804b0006: "STATUS_RECEIVING_FROM"
+    0x804b0006: "STATUS_RECEIVING_FROM",
+    0x804b000c: "STATUS_TLS_STARTING",
+    0x804b000d: "STATUS_TLS_ENDING"
   },
 
   httpDownloadActivities: [
     gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_START,
     gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER,
     gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
     gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE
   ],
@@ -1386,16 +1388,17 @@ NetworkMonitor.prototype = {
     if (fromCache) {
       // If it came from the browser cache, we have no timing
       // information and these should all be 0
       return {
         total: 0,
         timings: {
           blocked: 0,
           dns: 0,
+          ssl: 0,
           connect: 0,
           send: 0,
           wait: 0,
           receive: 0
         }
       };
     }
 
@@ -1420,16 +1423,46 @@ NetworkMonitor.prototype = {
 
     if (timings.STATUS_CONNECTING_TO && timings.STATUS_CONNECTED_TO) {
       harTimings.connect = timings.STATUS_CONNECTED_TO.last -
                            timings.STATUS_CONNECTING_TO.first;
     } else {
       harTimings.connect = -1;
     }
 
+    if (timings.STATUS_TLS_STARTING && timings.STATUS_TLS_ENDING) {
+      harTimings.ssl = timings.STATUS_TLS_ENDING.last -
+                           timings.STATUS_TLS_STARTING.first;
+    } else {
+      harTimings.ssl = -1;
+    }
+
+    // sometimes the connection information events are attached to a speculative
+    // channel instead of this one, but necko might glue them back together in the
+    // nsITimedChannel interface used by Resource and Navigation Timing
+    let timedChannel = httpActivity.channel.QueryInterface(Ci.nsITimedChannel);
+
+    if ((harTimings.connect <= 0) && timedChannel) {
+      if (timedChannel.secureConnectionStartTime > timedChannel.connectStartTime) {
+        harTimings.connect =
+          timedChannel.secureConnectionStartTime - timedChannel.connectStartTime;
+        harTimings.ssl =
+          timedChannel.connectEndTime - timedChannel.secureConnectionStartTime;
+      } else {
+        harTimings.connect =
+          timedChannel.connectEndTime - timedChannel.connectStartTime;
+        harTimings.ssl = -1;
+      }
+    }
+
+    if ((harTimings.dns <= 0) && timedChannel) {
+      harTimings.dns =
+        timedChannel.domainLookupEndTime - timedChannel.domainLookupStartTime;
+    }
+
     if (timings.STATUS_SENDING_TO) {
       harTimings.send = timings.STATUS_SENDING_TO.last - timings.STATUS_SENDING_TO.first;
     } else if (timings.REQUEST_HEADER && timings.REQUEST_BODY_SENT) {
       harTimings.send = timings.REQUEST_BODY_SENT.last - timings.REQUEST_HEADER.first;
     } else {
       harTimings.send = -1;
     }
 
--- a/dom/performance/PerformanceMainThread.cpp
+++ b/dom/performance/PerformanceMainThread.cpp
@@ -197,17 +197,17 @@ PerformanceMainThread::AddEntry(nsIHttpC
 // To be removed once bug 1124165 lands
 bool
 PerformanceMainThread::IsPerformanceTimingAttribute(const nsAString& aName)
 {
   // Note that toJSON is added to this list due to bug 1047848
   static const char* attributes[] =
     {"navigationStart", "unloadEventStart", "unloadEventEnd", "redirectStart",
      "redirectEnd", "fetchStart", "domainLookupStart", "domainLookupEnd",
-     "connectStart", "connectEnd", "requestStart", "responseStart",
+     "connectStart", "secureConnectionStart", "connectEnd", "requestStart", "responseStart",
      "responseEnd", "domLoading", "domInteractive",
      "domContentLoadedEventStart", "domContentLoadedEventEnd", "domComplete",
      "loadEventStart", "loadEventEnd", nullptr};
 
   for (uint32_t i = 0; attributes[i]; ++i) {
     if (aName.EqualsASCII(attributes[i])) {
       return true;
     }
@@ -246,16 +246,19 @@ PerformanceMainThread::GetPerformanceTim
     return Timing()->DomainLookupStart();
   }
   if (aProperty.EqualsLiteral("domainLookupEnd")) {
     return Timing()->DomainLookupEnd();
   }
   if (aProperty.EqualsLiteral("connectStart")) {
     return Timing()->ConnectStart();
   }
+  if (aProperty.EqualsLiteral("secureConnectionStart")) {
+    return Timing()->SecureConnectionStart();
+  }
   if (aProperty.EqualsLiteral("connectEnd")) {
     return Timing()->ConnectEnd();
   }
   if (aProperty.EqualsLiteral("requestStart")) {
     return Timing()->RequestStart();
   }
   if (aProperty.EqualsLiteral("responseStart")) {
     return Timing()->ResponseStart();
--- a/dom/performance/PerformanceResourceTiming.h
+++ b/dom/performance/PerformanceResourceTiming.h
@@ -123,19 +123,19 @@ public:
   DOMHighResTimeStamp ResponseEnd() const {
     return mTiming
         ? mTiming->ResponseEndHighRes()
         : 0;
   }
 
   DOMHighResTimeStamp SecureConnectionStart() const
   {
-    // This measurement is not available for Navigation Timing either.
-    // There is a different bug submitted for it.
-    return 0;
+    return mTiming && mTiming->TimingAllowed()
+        ? mTiming->SecureConnectionStartHighRes()
+        : 0;
   }
 
   virtual const PerformanceResourceTiming* ToResourceTiming() const override
   {
     return this;
   }
 
   uint64_t TransferSize() const
--- a/dom/performance/PerformanceTiming.cpp
+++ b/dom/performance/PerformanceTiming.cpp
@@ -73,22 +73,50 @@ PerformanceTiming::InitializeTimingInfo(
     aChannel->GetAsyncOpen(&mAsyncOpen);
     aChannel->GetAllRedirectsSameOrigin(&mAllRedirectsSameOrigin);
     aChannel->GetRedirectCount(&mRedirectCount);
     aChannel->GetRedirectStart(&mRedirectStart);
     aChannel->GetRedirectEnd(&mRedirectEnd);
     aChannel->GetDomainLookupStart(&mDomainLookupStart);
     aChannel->GetDomainLookupEnd(&mDomainLookupEnd);
     aChannel->GetConnectStart(&mConnectStart);
+    aChannel->GetSecureConnectionStart(&mSecureConnectionStart);
     aChannel->GetConnectEnd(&mConnectEnd);
     aChannel->GetRequestStart(&mRequestStart);
     aChannel->GetResponseStart(&mResponseStart);
     aChannel->GetCacheReadStart(&mCacheReadStart);
     aChannel->GetResponseEnd(&mResponseEnd);
     aChannel->GetCacheReadEnd(&mCacheReadEnd);
+
+    // the performance timing api essentially requires that the event timestamps
+    // are >= asyncOpen().. but in truth the browser engages in a number of
+    // speculative activities that sometimes mean connections and lookups begin
+    // earlier. Workaround that here by just using asyncOpen as the minimum
+    // timestamp for dns and connection info.
+    if (!mAsyncOpen.IsNull()) {
+      if (!mDomainLookupStart.IsNull() && mDomainLookupStart < mAsyncOpen) {
+        mDomainLookupStart = mAsyncOpen;
+      }
+
+      if (!mDomainLookupEnd.IsNull() && mDomainLookupEnd < mAsyncOpen) {
+        mDomainLookupEnd = mAsyncOpen;
+      }
+
+      if (!mConnectStart.IsNull() && mConnectStart < mAsyncOpen) {
+        mConnectStart = mAsyncOpen;
+      }
+
+      if (!mSecureConnectionStart.IsNull() && mSecureConnectionStart < mAsyncOpen) {
+        mSecureConnectionStart = mAsyncOpen;
+      }
+
+      if (!mConnectEnd.IsNull() && mConnectEnd < mAsyncOpen) {
+        mConnectEnd = mAsyncOpen;
+      }
+    }
   }
 }
 
 PerformanceTiming::~PerformanceTiming()
 {
 }
 
 DOMHighResTimeStamp
@@ -292,16 +320,33 @@ PerformanceTiming::ConnectStartHighRes()
 
 DOMTimeMilliSec
 PerformanceTiming::ConnectStart()
 {
   return static_cast<int64_t>(ConnectStartHighRes());
 }
 
 DOMHighResTimeStamp
+PerformanceTiming::SecureConnectionStartHighRes()
+{
+  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+      nsContentUtils::ShouldResistFingerprinting()) {
+    return mZeroTime;
+  }
+  return mSecureConnectionStart.IsNull() ? mZeroTime
+                                         : TimeStampToDOMHighRes(mSecureConnectionStart);
+}
+
+DOMTimeMilliSec
+PerformanceTiming::SecureConnectionStart()
+{
+  return static_cast<int64_t>(SecureConnectionStartHighRes());
+}
+
+DOMHighResTimeStamp
 PerformanceTiming::ConnectEndHighRes()
 {
   if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
       nsContentUtils::ShouldResistFingerprinting()) {
     return mZeroTime;
   }
   // Bug 1155008 - nsHttpTransaction is racy. Return ConnectStart when null
   return mConnectEnd.IsNull() ? ConnectStartHighRes()
--- a/dom/performance/PerformanceTiming.h
+++ b/dom/performance/PerformanceTiming.h
@@ -159,28 +159,30 @@ public:
 
   // High resolution (used by resource timing)
   DOMHighResTimeStamp FetchStartHighRes();
   DOMHighResTimeStamp RedirectStartHighRes();
   DOMHighResTimeStamp RedirectEndHighRes();
   DOMHighResTimeStamp DomainLookupStartHighRes();
   DOMHighResTimeStamp DomainLookupEndHighRes();
   DOMHighResTimeStamp ConnectStartHighRes();
+  DOMHighResTimeStamp SecureConnectionStartHighRes();
   DOMHighResTimeStamp ConnectEndHighRes();
   DOMHighResTimeStamp RequestStartHighRes();
   DOMHighResTimeStamp ResponseStartHighRes();
   DOMHighResTimeStamp ResponseEndHighRes();
 
   // Low resolution (used by navigation timing)
   DOMTimeMilliSec FetchStart();
   DOMTimeMilliSec RedirectStart();
   DOMTimeMilliSec RedirectEnd();
   DOMTimeMilliSec DomainLookupStart();
   DOMTimeMilliSec DomainLookupEnd();
   DOMTimeMilliSec ConnectStart();
+  DOMTimeMilliSec SecureConnectionStart();
   DOMTimeMilliSec ConnectEnd();
   DOMTimeMilliSec RequestStart();
   DOMTimeMilliSec ResponseStart();
   DOMTimeMilliSec ResponseEnd();
 
   DOMTimeMilliSec DomLoading()
   {
     if (!nsContentUtils::IsPerformanceTimingEnabled() ||
@@ -271,16 +273,17 @@ private:
   DOMHighResTimeStamp mZeroTime;
 
   TimeStamp mAsyncOpen;
   TimeStamp mRedirectStart;
   TimeStamp mRedirectEnd;
   TimeStamp mDomainLookupStart;
   TimeStamp mDomainLookupEnd;
   TimeStamp mConnectStart;
+  TimeStamp mSecureConnectionStart;
   TimeStamp mConnectEnd;
   TimeStamp mRequestStart;
   TimeStamp mResponseStart;
   TimeStamp mCacheReadStart;
   TimeStamp mResponseEnd;
   TimeStamp mCacheReadEnd;
   uint16_t mRedirectCount;
   bool mTimingAllowed;
--- a/dom/webidl/PerformanceTiming.webidl
+++ b/dom/webidl/PerformanceTiming.webidl
@@ -16,18 +16,17 @@ interface PerformanceTiming {
   readonly attribute unsigned long long unloadEventEnd;
   readonly attribute unsigned long long redirectStart;
   readonly attribute unsigned long long redirectEnd;
   readonly attribute unsigned long long fetchStart;
   readonly attribute unsigned long long domainLookupStart;
   readonly attribute unsigned long long domainLookupEnd;
   readonly attribute unsigned long long connectStart;
   readonly attribute unsigned long long connectEnd;
-  // secureConnectionStart will be implemneted in bug 772589
-  // readonly attribute unsigned long long secureConnectionStart;
+  readonly attribute unsigned long long secureConnectionStart;
   readonly attribute unsigned long long requestStart;
   readonly attribute unsigned long long responseStart;
   readonly attribute unsigned long long responseEnd;
   readonly attribute unsigned long long domLoading;
   readonly attribute unsigned long long domInteractive;
   readonly attribute unsigned long long domContentLoadedEventStart;
   readonly attribute unsigned long long domContentLoadedEventEnd;
   readonly attribute unsigned long long domComplete;
--- a/netwerk/base/nsISocketTransport.idl
+++ b/netwerk/base/nsISocketTransport.idl
@@ -164,16 +164,18 @@ interface nsISocketTransport : nsITransp
      */
     const unsigned long STATUS_RESOLVING      = 0x804b0003;
     const unsigned long STATUS_RESOLVED       = 0x804b000b;
     const unsigned long STATUS_CONNECTING_TO  = 0x804b0007;
     const unsigned long STATUS_CONNECTED_TO   = 0x804b0004;
     const unsigned long STATUS_SENDING_TO     = 0x804b0005;
     const unsigned long STATUS_WAITING_FOR    = 0x804b000a;
     const unsigned long STATUS_RECEIVING_FROM = 0x804b0006;
+    const unsigned long STATUS_TLS_HANDSHAKE_STARTING = 0x804b000c;
+    const unsigned long STATUS_TLS_HANDSHAKE_ENDED    = 0x804b000d;
 
     /**
      * connectionFlags is a bitmask that can be used to modify underlying 
      * behavior of the socket connection. See the flags below.
      */
     attribute unsigned long connectionFlags;
 
     /**
--- a/netwerk/base/nsITimedChannel.idl
+++ b/netwerk/base/nsITimedChannel.idl
@@ -35,16 +35,17 @@ interface nsITimedChannel : nsISupports 
   [noscript] attribute TimeStamp handleFetchEventStart;
   [noscript] attribute TimeStamp handleFetchEventEnd;
 
   // The following are only set when the document is not (only) read from the
   // cache
   [noscript] readonly attribute TimeStamp domainLookupStart;
   [noscript] readonly attribute TimeStamp domainLookupEnd;
   [noscript] readonly attribute TimeStamp connectStart;
+  [noscript] readonly attribute TimeStamp secureConnectionStart;
   [noscript] readonly attribute TimeStamp connectEnd;
   [noscript] readonly attribute TimeStamp requestStart;
   [noscript] readonly attribute TimeStamp responseStart;
   [noscript] readonly attribute TimeStamp responseEnd;
 
   // The redirect attributes timings must be writeble, se we can transfer
   // the data from one channel to the redirected channel.
   [noscript] attribute TimeStamp redirectStart;
@@ -79,16 +80,17 @@ interface nsITimedChannel : nsISupports 
   readonly attribute PRTime launchServiceWorkerEndTime;
   readonly attribute PRTime dispatchFetchEventStartTime;
   readonly attribute PRTime dispatchFetchEventEndTime;
   readonly attribute PRTime handleFetchEventStartTime;
   readonly attribute PRTime handleFetchEventEndTime;
   readonly attribute PRTime domainLookupStartTime;
   readonly attribute PRTime domainLookupEndTime;
   readonly attribute PRTime connectStartTime;
+  readonly attribute PRTime secureConnectionStartTime;
   readonly attribute PRTime connectEndTime;
   readonly attribute PRTime requestStartTime;
   readonly attribute PRTime responseStartTime;
   readonly attribute PRTime responseEndTime;
   readonly attribute PRTime cacheReadStartTime;
   readonly attribute PRTime cacheReadEndTime;
   readonly attribute PRTime redirectStartTime;
   readonly attribute PRTime redirectEndTime;
--- a/netwerk/base/nsLoadGroup.cpp
+++ b/netwerk/base/nsLoadGroup.cpp
@@ -885,16 +885,21 @@ nsLoadGroup::TelemetryReportChannel(nsIT
     if (NS_FAILED(rv))
         return;
 
     TimeStamp connectStart;
     rv = aTimedChannel->GetConnectStart(&connectStart);
     if (NS_FAILED(rv))
         return;
 
+    TimeStamp secureConnectionStart;
+    rv = aTimedChannel->GetSecureConnectionStart(&secureConnectionStart);
+    if (NS_FAILED(rv))
+        return;
+
     TimeStamp connectEnd;
     rv = aTimedChannel->GetConnectEnd(&connectEnd);
     if (NS_FAILED(rv))
         return;
 
     TimeStamp requestStart;
     rv = aTimedChannel->GetRequestStart(&requestStart);
     if (NS_FAILED(rv))
@@ -918,23 +923,28 @@ nsLoadGroup::TelemetryReportChannel(nsIT
     }                                                                          \
                                                                                \
     if (!domainLookupStart.IsNull() && !domainLookupEnd.IsNull()) {            \
         Telemetry::AccumulateTimeDelta(                                        \
             Telemetry::HTTP_##prefix##_DNS_LOOKUP_TIME,                        \
             domainLookupStart, domainLookupEnd);                               \
     }                                                                          \
                                                                                \
+    if (!secureConnectionStart.IsNull() && !connectEnd.IsNull()) {             \
+        Telemetry::AccumulateTimeDelta(                                        \
+            Telemetry::HTTP_##prefix##_TLS_HANDSHAKE,                          \
+            secureConnectionStart, connectEnd);                                \
+    }                                                                          \
+                                                                               \
     if (!connectStart.IsNull() && !connectEnd.IsNull()) {                      \
         Telemetry::AccumulateTimeDelta(                                        \
-            Telemetry::HTTP_##prefix##_TCP_CONNECTION,                         \
+            Telemetry::HTTP_##prefix##_TCP_CONNECTION_2,                       \
             connectStart, connectEnd);                                         \
     }                                                                          \
                                                                                \
-                                                                               \
     if (!requestStart.IsNull() && !responseEnd.IsNull()) {                     \
         Telemetry::AccumulateTimeDelta(                                        \
             Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_SENT,                     \
             asyncOpen, requestStart);                                          \
                                                                                \
         Telemetry::AccumulateTimeDelta(                                        \
             Telemetry::HTTP_##prefix##_FIRST_SENT_TO_LAST_RECEIVED,            \
             requestStart, responseEnd);                                        \
--- a/netwerk/ipc/NeckoMessageUtils.h
+++ b/netwerk/ipc/NeckoMessageUtils.h
@@ -142,16 +142,17 @@ struct ParamTraits<mozilla::net::NetAddr
 template<>
 struct ParamTraits<mozilla::net::ResourceTimingStruct>
 {
   static void Write(Message* aMsg, const mozilla::net::ResourceTimingStruct& aParam)
   {
     WriteParam(aMsg, aParam.domainLookupStart);
     WriteParam(aMsg, aParam.domainLookupEnd);
     WriteParam(aMsg, aParam.connectStart);
+    WriteParam(aMsg, aParam.secureConnectionStart);
     WriteParam(aMsg, aParam.connectEnd);
     WriteParam(aMsg, aParam.requestStart);
     WriteParam(aMsg, aParam.responseStart);
     WriteParam(aMsg, aParam.responseEnd);
 
     WriteParam(aMsg, aParam.fetchStart);
     WriteParam(aMsg, aParam.redirectStart);
     WriteParam(aMsg, aParam.redirectEnd);
@@ -164,16 +165,17 @@ struct ParamTraits<mozilla::net::Resourc
     WriteParam(aMsg, aParam.cacheReadEnd);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, mozilla::net::ResourceTimingStruct* aResult)
   {
     return ReadParam(aMsg, aIter, &aResult->domainLookupStart) &&
            ReadParam(aMsg, aIter, &aResult->domainLookupEnd) &&
            ReadParam(aMsg, aIter, &aResult->connectStart) &&
+           ReadParam(aMsg, aIter, &aResult->secureConnectionStart) &&
            ReadParam(aMsg, aIter, &aResult->connectEnd) &&
            ReadParam(aMsg, aIter, &aResult->requestStart) &&
            ReadParam(aMsg, aIter, &aResult->responseStart) &&
            ReadParam(aMsg, aIter, &aResult->responseEnd) &&
            ReadParam(aMsg, aIter, &aResult->fetchStart) &&
            ReadParam(aMsg, aIter, &aResult->redirectStart) &&
            ReadParam(aMsg, aIter, &aResult->redirectEnd) &&
            ReadParam(aMsg, aIter, &aResult->transferSize) &&
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -2429,16 +2429,25 @@ Http2Session::OnTransportStatus(nsITrans
   case NS_NET_STATUS_RESOLVING_HOST:
   case NS_NET_STATUS_RESOLVED_HOST:
   case NS_NET_STATUS_CONNECTING_TO:
   case NS_NET_STATUS_CONNECTED_TO:
   case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
   case NS_NET_STATUS_TLS_HANDSHAKE_ENDED:
   {
     Http2Stream *target = mStreamIDHash.Get(mUseH2Deps ? 0xF : 0x3);
+    if (!target) {
+      // any transaction will do if we can't find the low numbered one
+      // generally this happens when the initial transaction hasn't been
+      // assigned a stream id yet.
+      auto iter = mStreamTransactionHash.Iter();
+      if (!iter.Done()) {
+        target = iter.Data();
+      }
+    }
     nsAHttpTransaction *transaction = target ? target->Transaction() : nullptr;
     if (transaction)
       transaction->OnTransportStatus(aTransport, aStatus, aProgress);
     break;
   }
 
   default:
     // The other transport events are ignored here because there is no good
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -3835,16 +3835,22 @@ HttpBaseChannel::GetDomainLookupEnd(Time
 
 NS_IMETHODIMP
 HttpBaseChannel::GetConnectStart(TimeStamp* _retval) {
   *_retval = mTransactionTimings.connectStart;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+HttpBaseChannel::GetSecureConnectionStart(TimeStamp* _retval) {
+  *_retval = mTransactionTimings.secureConnectionStart;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 HttpBaseChannel::GetConnectEnd(TimeStamp* _retval) {
   *_retval = mTransactionTimings.connectEnd;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpBaseChannel::GetRequestStart(TimeStamp* _retval) {
   *_retval = mTransactionTimings.requestStart;
@@ -3909,16 +3915,17 @@ IMPL_TIMING_ATTR(LaunchServiceWorkerStar
 IMPL_TIMING_ATTR(LaunchServiceWorkerEnd)
 IMPL_TIMING_ATTR(DispatchFetchEventStart)
 IMPL_TIMING_ATTR(DispatchFetchEventEnd)
 IMPL_TIMING_ATTR(HandleFetchEventStart)
 IMPL_TIMING_ATTR(HandleFetchEventEnd)
 IMPL_TIMING_ATTR(DomainLookupStart)
 IMPL_TIMING_ATTR(DomainLookupEnd)
 IMPL_TIMING_ATTR(ConnectStart)
+IMPL_TIMING_ATTR(SecureConnectionStart)
 IMPL_TIMING_ATTR(ConnectEnd)
 IMPL_TIMING_ATTR(RequestStart)
 IMPL_TIMING_ATTR(ResponseStart)
 IMPL_TIMING_ATTR(ResponseEnd)
 IMPL_TIMING_ATTR(CacheReadStart)
 IMPL_TIMING_ATTR(CacheReadEnd)
 IMPL_TIMING_ATTR(RedirectStart)
 IMPL_TIMING_ATTR(RedirectEnd)
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -1038,16 +1038,17 @@ HttpChannelChild::OnStopRequest(const ns
   nsCOMPtr<nsICompressConvStats> conv = do_QueryInterface(mCompressListener);
   if (conv) {
       conv->GetDecodedDataLength(&mDecodedBodySize);
   }
 
   mTransactionTimings.domainLookupStart = timing.domainLookupStart;
   mTransactionTimings.domainLookupEnd = timing.domainLookupEnd;
   mTransactionTimings.connectStart = timing.connectStart;
+  mTransactionTimings.secureConnectionStart = timing.secureConnectionStart;
   mTransactionTimings.connectEnd = timing.connectEnd;
   mTransactionTimings.requestStart = timing.requestStart;
   mTransactionTimings.responseStart = timing.responseStart;
   mTransactionTimings.responseEnd = timing.responseEnd;
 
   // Do not overwrite or adjust the original mAsyncOpenTime by timing.fetchStart
   // We must use the original child process time in order to account for child
   // side work and IPC transit overhead.
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -1525,16 +1525,17 @@ HttpChannelParent::OnStopRequest(nsIRequ
   MOZ_ASSERT(NS_IsMainThread());
 
   MOZ_RELEASE_ASSERT(!mDivertingFromChild,
     "Cannot call OnStopRequest if diverting is set!");
   ResourceTimingStruct timing;
   mChannel->GetDomainLookupStart(&timing.domainLookupStart);
   mChannel->GetDomainLookupEnd(&timing.domainLookupEnd);
   mChannel->GetConnectStart(&timing.connectStart);
+  mChannel->GetSecureConnectionStart(&timing.secureConnectionStart);
   mChannel->GetConnectEnd(&timing.connectEnd);
   mChannel->GetRequestStart(&timing.requestStart);
   mChannel->GetResponseStart(&timing.responseStart);
   mChannel->GetResponseEnd(&timing.responseEnd);
   mChannel->GetAsyncOpen(&timing.fetchStart);
   mChannel->GetRedirectStart(&timing.redirectStart);
   mChannel->GetRedirectEnd(&timing.redirectEnd);
   mChannel->GetTransferSize(&timing.transferSize);
--- a/netwerk/protocol/http/NullHttpChannel.cpp
+++ b/netwerk/protocol/http/NullHttpChannel.cpp
@@ -690,16 +690,23 @@ NullHttpChannel::GetDomainLookupEnd(mozi
 NS_IMETHODIMP
 NullHttpChannel::GetConnectStart(mozilla::TimeStamp *aConnectStart)
 {
   *aConnectStart = mAsyncOpenTime;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+NullHttpChannel::GetSecureConnectionStart(mozilla::TimeStamp *aSecureConnectionStart)
+{
+  *aSecureConnectionStart = mAsyncOpenTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 NullHttpChannel::GetConnectEnd(mozilla::TimeStamp *aConnectEnd)
 {
   *aConnectEnd = mAsyncOpenTime;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 NullHttpChannel::GetRequestStart(mozilla::TimeStamp *aRequestStart)
@@ -867,16 +874,17 @@ IMPL_TIMING_ATTR(LaunchServiceWorkerStar
 IMPL_TIMING_ATTR(LaunchServiceWorkerEnd)
 IMPL_TIMING_ATTR(DispatchFetchEventStart)
 IMPL_TIMING_ATTR(DispatchFetchEventEnd)
 IMPL_TIMING_ATTR(HandleFetchEventStart)
 IMPL_TIMING_ATTR(HandleFetchEventEnd)
 IMPL_TIMING_ATTR(DomainLookupStart)
 IMPL_TIMING_ATTR(DomainLookupEnd)
 IMPL_TIMING_ATTR(ConnectStart)
+IMPL_TIMING_ATTR(SecureConnectionStart)
 IMPL_TIMING_ATTR(ConnectEnd)
 IMPL_TIMING_ATTR(RequestStart)
 IMPL_TIMING_ATTR(ResponseStart)
 IMPL_TIMING_ATTR(ResponseEnd)
 IMPL_TIMING_ATTR(CacheReadStart)
 IMPL_TIMING_ATTR(CacheReadEnd)
 IMPL_TIMING_ATTR(RedirectStart)
 IMPL_TIMING_ATTR(RedirectEnd)
--- a/netwerk/protocol/http/NullHttpTransaction.cpp
+++ b/netwerk/protocol/http/NullHttpTransaction.cpp
@@ -165,16 +165,40 @@ NullHttpTransaction::GetSecurityCallback
   nsCOMPtr<nsIInterfaceRequestor> copyCB(mCallbacks);
   *outCB = copyCB.forget().take();
 }
 
 void
 NullHttpTransaction::OnTransportStatus(nsITransport* transport,
                                        nsresult status, int64_t progress)
 {
+  if (status == NS_NET_STATUS_RESOLVING_HOST) {
+    if (mTimings.domainLookupStart.IsNull()) {
+      mTimings.domainLookupStart = TimeStamp::Now();
+    }
+  } else if (status == NS_NET_STATUS_RESOLVED_HOST) {
+    if (mTimings.domainLookupEnd.IsNull()) {
+      mTimings.domainLookupEnd = TimeStamp::Now();
+    }
+  } else if (status == NS_NET_STATUS_CONNECTING_TO) {
+    if (mTimings.connectStart.IsNull()) {
+      mTimings.connectStart = TimeStamp::Now();
+    }
+  } else if (status == NS_NET_STATUS_CONNECTED_TO) {
+    if (mTimings.connectEnd.IsNull()) {
+      mTimings.connectEnd = TimeStamp::Now();
+    }
+  } else if (status == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
+    if (mTimings.secureConnectionStart.IsNull() &&
+        !mTimings.connectEnd.IsNull()) {
+      mTimings.secureConnectionStart = mTimings.connectEnd;
+    }
+    mTimings.connectEnd = TimeStamp::Now();;
+  }
+
   if (mActivityDistributor) {
     NS_DispatchToMainThread(new CallObserveActivity(mActivityDistributor,
                                   mConnectionInfo->GetOrigin(),
                                   mConnectionInfo->OriginPort(),
                                   mConnectionInfo->EndToEndSSL(),
                                   NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,
                                   static_cast<uint32_t>(status),
                                   PR_Now(),
--- a/netwerk/protocol/http/NullHttpTransaction.h
+++ b/netwerk/protocol/http/NullHttpTransaction.h
@@ -4,16 +4,17 @@
  * 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_net_NullHttpTransaction_h
 #define mozilla_net_NullHttpTransaction_h
 
 #include "nsAHttpTransaction.h"
 #include "mozilla/Attributes.h"
+#include "TimingStruct.h"
 
 // This is the minimal nsAHttpTransaction implementation. A NullHttpTransaction
 // can be used to drive connection level semantics (such as SSL handshakes
 // tunnels) so that a nsHttpConnection becomes fully established in
 // anticipation of a real transaction needing to use it soon.
 
 class nsIHttpActivityObserver;
 
@@ -51,16 +52,18 @@ public:
   }
 
   // We have to override this function because |mTransaction| in nsHalfOpenSocket
   // could be either nsHttpTransaction or NullHttpTransaction.
   // NullHttpTransaction will be activated on the connection immediately after
   // creation and be never put in a pending queue, so it's OK to just return 0.
   uint64_t TopLevelOuterContentWindowId() override { return 0; }
 
+  TimingStruct Timings() { return mTimings; }
+
 protected:
   virtual ~NullHttpTransaction();
 
 private:
   nsresult mStatus;
 protected:
   uint32_t mCaps;
   nsHttpRequestHead *mRequestHead;
@@ -70,16 +73,17 @@ private:
   // redundant requests on the network. The member itself is atomic, but
   // access to it from the networking thread may happen either before or
   // after the main thread modifies it. To deal with raciness, only unsetting
   // bitfields should be allowed: 'lost races' will thus err on the
   // conservative side, e.g. by going ahead with a 2nd DNS refresh.
   Atomic<uint32_t> mCapsToClear;
   bool mIsDone;
   bool mClaimed;
+  TimingStruct mTimings;
 
 protected:
   RefPtr<nsAHttpConnection> mConnection;
   nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
   RefPtr<nsHttpConnectionInfo> mConnectionInfo;
   nsCOMPtr<nsIHttpActivityObserver> mActivityDistributor;
 };
 
--- a/netwerk/protocol/http/TimingStruct.h
+++ b/netwerk/protocol/http/TimingStruct.h
@@ -9,16 +9,17 @@
 #include "mozilla/TimeStamp.h"
 
 namespace mozilla { namespace net {
 
 struct TimingStruct {
   TimeStamp domainLookupStart;
   TimeStamp domainLookupEnd;
   TimeStamp connectStart;
+  TimeStamp secureConnectionStart;
   TimeStamp connectEnd;
   TimeStamp requestStart;
   TimeStamp responseStart;
   TimeStamp responseEnd;
 };
 
 struct ResourceTimingStruct : TimingStruct {
   TimeStamp fetchStart;
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -6910,16 +6910,25 @@ nsHttpChannel::GetConnectStart(TimeStamp
     if (mTransaction)
         *_retval = mTransaction->GetConnectStart();
     else
         *_retval = mTransactionTimings.connectStart;
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsHttpChannel::GetSecureConnectionStart(TimeStamp* _retval) {
+    if (mTransaction)
+        *_retval = mTransaction->GetSecureConnectionStart();
+    else
+        *_retval = mTransactionTimings.secureConnectionStart;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsHttpChannel::GetConnectEnd(TimeStamp* _retval) {
     if (mTransaction)
         *_retval = mTransaction->GetConnectEnd();
     else
         *_retval = mTransactionTimings.connectEnd;
     return NS_OK;
 }
 
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -173,16 +173,17 @@ public:
     NS_IMETHOD ResumeAt(uint64_t startPos, const nsACString& entityID) override;
 
     NS_IMETHOD SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) override;
     NS_IMETHOD SetLoadGroup(nsILoadGroup *aLoadGroup) override;
     // nsITimedChannel
     NS_IMETHOD GetDomainLookupStart(mozilla::TimeStamp *aDomainLookupStart) override;
     NS_IMETHOD GetDomainLookupEnd(mozilla::TimeStamp *aDomainLookupEnd) override;
     NS_IMETHOD GetConnectStart(mozilla::TimeStamp *aConnectStart) override;
+    NS_IMETHOD GetSecureConnectionStart(mozilla::TimeStamp *aSecureConnectionStart) override;
     NS_IMETHOD GetConnectEnd(mozilla::TimeStamp *aConnectEnd) override;
     NS_IMETHOD GetRequestStart(mozilla::TimeStamp *aRequestStart) override;
     NS_IMETHOD GetResponseStart(mozilla::TimeStamp *aResponseStart) override;
     NS_IMETHOD GetResponseEnd(mozilla::TimeStamp *aResponseEnd) override;
     // nsICorsPreflightCallback
     NS_IMETHOD OnPreflightSucceeded() override;
     NS_IMETHOD OnPreflightFailed(nsresult aError) override;
 
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -532,18 +532,25 @@ nsHttpConnection::EnsureNPNComplete(nsre
         Telemetry::Accumulate(Telemetry::SPDY_NPN_CONNECT, UsingSpdy());
     }
 
 npnComplete:
     LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] setting complete to true", this));
     mNPNComplete = true;
 
     mTransaction->OnTransportStatus(mSocketTransport,
-                                    NS_NET_STATUS_TLS_HANDSHAKE_ENDED,
-                                    0);
+                                    NS_NET_STATUS_TLS_HANDSHAKE_ENDED, 0);
+
+    // this is happening after the bootstrap was originally written to. so update it.
+    if (mBootstrappedTimings.secureConnectionStart.IsNull() &&
+        !mBootstrappedTimings.connectEnd.IsNull()) {
+        mBootstrappedTimings.secureConnectionStart = mBootstrappedTimings.connectEnd;
+        mBootstrappedTimings.connectEnd = TimeStamp::Now();
+    }
+
     if (mWaitingFor0RTTResponse) {
         // Didn't get 0RTT OK, back out of the "attempting 0RTT" state
         mWaitingFor0RTTResponse = false;
         LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] 0rtt failed", this));
         if (NS_FAILED(mTransaction->Finish0RTT(true, negotiatedNPN != mEarlyNegotiatedALPN))) {
             mTransaction->Close(NS_ERROR_NET_RESET);
         }
         mContentBytesWritten0RTT = 0;
@@ -577,18 +584,24 @@ nsHttpConnection::OnTunnelNudged(TLSFilt
 // called on the socket thread
 nsresult
 nsHttpConnection::Activate(nsAHttpTransaction *trans, uint32_t caps, int32_t pri)
 {
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
     LOG(("nsHttpConnection::Activate [this=%p trans=%p caps=%x]\n",
          this, trans, caps));
 
-    if (!trans->IsNullTransaction() && !mFastOpen)
+    if (!mExperienced && !trans->IsNullTransaction() && !mFastOpen) {
         mExperienced = true;
+        nsHttpTransaction *hTrans = trans->QueryHttpTransaction();
+        if (hTrans) {
+            hTrans->BootstrapTimings(mBootstrappedTimings);
+        }
+        mBootstrappedTimings = TimingStruct();
+    }
 
     mTransactionCaps = caps;
     mPriority = pri;
     if (mTransaction && mUsingSpdyVersion) {
         return AddTransaction(trans, pri);
     }
 
     NS_ENSURE_ARG_POINTER(trans);
@@ -2372,10 +2385,16 @@ nsHttpConnection::SetFastOpen(bool aFast
     mFastOpen = aFastOpen;
     if (!mFastOpen &&
         mTransaction &&
         !mTransaction->IsNullTransaction()) {
         mExperienced = true;
     }
 }
 
+void
+nsHttpConnection::BootstrapTimings(TimingStruct times)
+{
+    mBootstrappedTimings = times;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -11,16 +11,17 @@
 #include "nsAHttpTransaction.h"
 #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 "nsIAsyncInputStream.h"
 #include "nsIAsyncOutputStream.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsITimer.h"
 
 class nsISocketTransport;
 class nsISSLSocketControl;
@@ -394,16 +395,21 @@ private:
     nsCString                      mEarlyNegotiatedALPN;
     bool                           mDid0RTTSpdy;
 
     bool                           mFastOpen;
     uint8_t                        mFastOpenStatus;
 
     bool                           mForceSendDuringFastOpenPending;
     bool                           mReceivedSocketWouldBlockDuringFastOpen;
+
+public:
+    void BootstrapTimings(TimingStruct times);
+private:
+    TimingStruct    mBootstrappedTimings;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpConnection, NS_HTTPCONNECTION_IID)
 
 } // namespace net
 } // namespace mozilla
 
 #endif // nsHttpConnection_h__
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -4355,16 +4355,21 @@ nsHalfOpenSocket::SetupConn(nsIAsyncOutp
                             bool aFastOpen)
 {
     MOZ_ASSERT(!aFastOpen || (out == mStreamOut));
     // assign the new socket to the http connection
     RefPtr<nsHttpConnection> conn = new nsHttpConnection();
     LOG(("nsHalfOpenSocket::SetupConn "
          "Created new nshttpconnection %p\n", conn.get()));
 
+    NullHttpTransaction *nullTrans = mTransaction->QueryNullTransaction();
+    if (nullTrans) {
+        conn->BootstrapTimings(nullTrans->Timings());
+    }
+
     // Some capabilities are needed before a transaciton actually gets
     // scheduled (e.g. how to negotiate false start)
     conn->SetTransactionCaps(mTransaction->Caps());
 
     NetAddr peeraddr;
     nsCOMPtr<nsIInterfaceRequestor> callbacks;
     mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
     nsresult rv;
@@ -4567,17 +4572,17 @@ nsHttpConnectionMgr::nsHalfOpenSocket::O
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
 
     MOZ_DIAGNOSTIC_ASSERT(mEnt);
     if (mTransaction) {
         RefPtr<PendingTransactionInfo> info = FindTransactionHelper(false);
         if ((trans == mSocketTransport) ||
             ((trans == mBackupTransport) && (status == NS_NET_STATUS_CONNECTED_TO) &&
             info)) {
-            // Send this status event only if the transaction is still panding,
+            // Send this status event only if the transaction is still pending,
             // i.e. it has not found a free already connected socket.
             // Sockets in halfOpen state can only get following events:
             // NS_NET_STATUS_RESOLVING_HOST, NS_NET_STATUS_RESOLVED_HOST,
             // NS_NET_STATUS_CONNECTING_TO and NS_NET_STATUS_CONNECTED_TO.
             // mBackupTransport is only started after
             // NS_NET_STATUS_CONNECTING_TO of mSocketTransport, so ignore all
             // mBackupTransport events until NS_NET_STATUS_CONNECTED_TO.
             mTransaction->OnTransportStatus(trans, status, progress);
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -573,16 +573,24 @@ nsHttpTransaction::OnTransportStatus(nsI
             SetDomainLookupStart(TimeStamp::Now(), true);
         } else if (status == NS_NET_STATUS_RESOLVED_HOST) {
             SetDomainLookupEnd(TimeStamp::Now());
         } else if (status == NS_NET_STATUS_CONNECTING_TO) {
             SetConnectStart(TimeStamp::Now());
         } else if (status == NS_NET_STATUS_CONNECTED_TO) {
             SetConnectEnd(TimeStamp::Now(), true);
         } else if (status == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
+            {
+                // before overwriting connectEnd, copy it to secureConnectionStart
+                MutexAutoLock lock(mLock);
+                if (mTimings.secureConnectionStart.IsNull() &&
+                    !mTimings.connectEnd.IsNull()) {
+                    mTimings.secureConnectionStart = mTimings.connectEnd;
+                }
+            }
             SetConnectEnd(TimeStamp::Now(), false);
         } else if (status == NS_NET_STATUS_SENDING_TO) {
             // Set the timestamp to Now(), only if it null
             SetRequestStart(TimeStamp::Now(), true);
         }
     }
 
     if (!mTransportSink)
@@ -1943,16 +1951,23 @@ const TimingStruct
 nsHttpTransaction::Timings()
 {
     mozilla::MutexAutoLock lock(mLock);
     TimingStruct timings = mTimings;
     return timings;
 }
 
 void
+nsHttpTransaction::BootstrapTimings(TimingStruct times)
+{
+    mozilla::MutexAutoLock lock(mLock);
+    mTimings = times;
+}
+
+void
 nsHttpTransaction::SetDomainLookupStart(mozilla::TimeStamp timeStamp, bool onlyIfNull)
 {
     mozilla::MutexAutoLock lock(mLock);
     if (onlyIfNull && !mTimings.domainLookupStart.IsNull()) {
         return; // We only set the timestamp if it was previously null
     }
     mTimings.domainLookupStart = timeStamp;
 }
@@ -2034,16 +2049,23 @@ nsHttpTransaction::GetDomainLookupEnd()
 mozilla::TimeStamp
 nsHttpTransaction::GetConnectStart()
 {
     mozilla::MutexAutoLock lock(mLock);
     return mTimings.connectStart;
 }
 
 mozilla::TimeStamp
+nsHttpTransaction::GetSecureConnectionStart()
+{
+    mozilla::MutexAutoLock lock(mLock);
+    return mTimings.secureConnectionStart;
+}
+
+mozilla::TimeStamp
 nsHttpTransaction::GetConnectEnd()
 {
     mozilla::MutexAutoLock lock(mLock);
     return mTimings.connectEnd;
 }
 
 mozilla::TimeStamp
 nsHttpTransaction::GetRequestStart()
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -143,27 +143,30 @@ public:
         return r;
     }
     void SetPushedStream(Http2PushedStream *push) { mPushedStream = push; }
     uint32_t InitialRwin() const { return mInitialRwin; };
     bool ChannelPipeFull() { return mWaitingOnPipeOut; }
 
     // Locked methods to get and set timing info
     const TimingStruct Timings();
+    void BootstrapTimings(TimingStruct times);
     void SetDomainLookupStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
     void SetDomainLookupEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
     void SetConnectStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
     void SetConnectEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
     void SetRequestStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
     void SetResponseStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
     void SetResponseEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
 
     mozilla::TimeStamp GetDomainLookupStart();
     mozilla::TimeStamp GetDomainLookupEnd();
     mozilla::TimeStamp GetConnectStart();
+    mozilla::TimeStamp GetSecureConnectionStart();
+
     mozilla::TimeStamp GetConnectEnd();
     mozilla::TimeStamp GetRequestStart();
     mozilla::TimeStamp GetResponseStart();
     mozilla::TimeStamp GetResponseEnd();
 
     int64_t GetTransferSize() { return mTransferSize; }
 
     MOZ_MUST_USE bool Do0RTT() override;
--- a/testing/web-platform/meta/navigation-timing/idlharness.html.ini
+++ b/testing/web-platform/meta/navigation-timing/idlharness.html.ini
@@ -1,16 +1,10 @@
 [idlharness.html]
   type: testharness
-  [PerformanceTiming interface: attribute secureConnectionStart]
-    expected: FAIL
-
-  [PerformanceTiming interface: window.performance.timing must inherit property "secureConnectionStart" with the proper type (10)]
-    expected: FAIL
-
   [Performance interface: existence and properties of interface object]
     expected: FAIL
 
   [Performance interface: existence and properties of interface prototype object]
     expected: FAIL
 
   [EventTarget interface: window.performance must inherit property "addEventListener" with the proper type (0)]
     expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/user-timing/invoke_with_timing_attributes.html.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[invoke_with_timing_attributes.html]
-  type: testharness
-  [performance.mark should throw if used with timing attribute secureConnectionStart]
-    expected: FAIL
-
-  [performance.measure should throw if used with timing attribute secureConnectionStart]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/user-timing/mark_exceptions.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[mark_exceptions.html]
-  type: testharness
-  [window.performance.mark("secureConnectionStart") throws a SyntaxError exception.]
-    expected: FAIL
-
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -1894,23 +1894,35 @@
   "HTTP_PAGE_DNS_LOOKUP_TIME": {
     "record_in_processes": ["main", "content"],
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 30000,
     "n_buckets": 50,
     "description": "HTTP page channel: DNS lookup time (ms)"
   },
-  "HTTP_PAGE_TCP_CONNECTION": {
-    "record_in_processes": ["main", "content"],
-    "expires_in_version": "never",
+  "HTTP_PAGE_TLS_HANDSHAKE": {
+    "record_in_processes": ["main", "content"],
+    "expires_in_version": "never",
+    "alert_emails": ["necko@mozilla.com", "pmcmanus@mozilla.com"],
+    "bug_numbers": [772589],
     "kind": "exponential",
     "high": 30000,
     "n_buckets": 50,
-    "description": "HTTP page channel: TCP connection setup (ms)"
+    "description": "HTTP page channel: After TCP SYN to Ready for HTTP (ms)"
+  },
+  "HTTP_PAGE_TCP_CONNECTION_2": {
+    "record_in_processes": ["main", "content"],
+    "expires_in_version": "never",
+    "alert_emails": ["necko@mozilla.com", "pmcmanus@mozilla.com"],
+    "bug_numbers": [772589],
+    "kind": "exponential",
+    "high": 30000,
+    "n_buckets": 50,
+    "description": "HTTP page channel: TCP SYN to Ready for HTTP (ms)"
   },
   "HTTP_PAGE_OPEN_TO_FIRST_SENT": {
     "record_in_processes": ["main", "content"],
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 30000,
     "n_buckets": 50,
     "description": "HTTP page channel: Open -> first byte of request sent (ms)"
@@ -2030,23 +2042,35 @@
   "HTTP_SUB_DNS_LOOKUP_TIME": {
     "record_in_processes": ["main", "content"],
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 30000,
     "n_buckets": 50,
     "description": "HTTP subitem channel: DNS lookup time (ms)"
   },
-  "HTTP_SUB_TCP_CONNECTION": {
-    "record_in_processes": ["main", "content"],
+  "HTTP_SUB_TLS_HANDSHAKE": {
+    "record_in_processes": ["main", "content"],
+    "expires_in_version": "never",
+    "alert_emails": ["necko@mozilla.com", "pmcmanus@mozilla.com"],
+    "bug_numbers": [772589],
+    "kind": "exponential",
+    "high": 30000,
+    "n_buckets": 50,
+    "description": "HTTP subitem channel: After TCP SYN to Ready for HTTP (ms)"
+  },
+  "HTTP_SUB_TCP_CONNECTION_2": {
+    "record_in_processes": ["main", "content"],
+    "alert_emails": ["necko@mozilla.com", "pmcmanus@mozilla.com"],
+    "bug_numbers": [772589],
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 30000,
     "n_buckets": 50,
-    "description": "HTTP subitem channel: TCP connection setup (ms)"
+    "description": "HTTP subitem channel: TCP SYN to Ready for HTTP (ms)"
   },
   "HTTP_SUB_OPEN_TO_FIRST_SENT": {
     "record_in_processes": ["main", "content"],
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 30000,
     "n_buckets": 50,
     "description": "HTTP subitem channel: Open -> first byte of request sent (ms)"
--- a/toolkit/components/telemetry/histogram-whitelists.json
+++ b/toolkit/components/telemetry/histogram-whitelists.json
@@ -298,17 +298,16 @@
     "HTTP_PAGE_DNS_ISSUE_TIME",
     "HTTP_PAGE_DNS_LOOKUP_TIME",
     "HTTP_PAGE_FIRST_SENT_TO_LAST_RECEIVED",
     "HTTP_PAGE_OPEN_TO_FIRST_FROM_CACHE",
     "HTTP_PAGE_OPEN_TO_FIRST_FROM_CACHE_V2",
     "HTTP_PAGE_OPEN_TO_FIRST_RECEIVED",
     "HTTP_PAGE_OPEN_TO_FIRST_SENT",
     "HTTP_PAGE_REVALIDATION",
-    "HTTP_PAGE_TCP_CONNECTION",
     "HTTP_PROXY_TYPE",
     "HTTP_REQUEST_PER_CONN",
     "HTTP_REQUEST_PER_PAGE",
     "HTTP_REQUEST_PER_PAGE_FROM_CACHE",
     "HTTP_SUBITEM_FIRST_BYTE_LATENCY_TIME",
     "HTTP_SUBITEM_OPEN_LATENCY_TIME",
     "HTTP_SUB_CACHE_READ_TIME",
     "HTTP_SUB_CACHE_READ_TIME_V2",
@@ -321,17 +320,16 @@
     "HTTP_SUB_DNS_ISSUE_TIME",
     "HTTP_SUB_DNS_LOOKUP_TIME",
     "HTTP_SUB_FIRST_SENT_TO_LAST_RECEIVED",
     "HTTP_SUB_OPEN_TO_FIRST_FROM_CACHE",
     "HTTP_SUB_OPEN_TO_FIRST_FROM_CACHE_V2",
     "HTTP_SUB_OPEN_TO_FIRST_RECEIVED",
     "HTTP_SUB_OPEN_TO_FIRST_SENT",
     "HTTP_SUB_REVALIDATION",
-    "HTTP_SUB_TCP_CONNECTION",
     "HTTP_TRANSACTION_USE_ALTSVC",
     "HTTP_TRANSACTION_USE_ALTSVC_OE",
     "INNERWINDOWS_WITH_MUTATION_LISTENERS",
     "IPC_SAME_PROCESS_MESSAGE_COPY_OOM_KB",
     "IPV4_AND_IPV6_ADDRESS_CONNECTIVITY",
     "JS_TELEMETRY_ADDON_EXCEPTIONS",
     "LINK_ICON_SIZES_ATTR_DIMENSION",
     "LINK_ICON_SIZES_ATTR_USAGE",
@@ -1006,17 +1004,16 @@
     "HTTP_PAGE_DNS_ISSUE_TIME",
     "HTTP_PAGE_DNS_LOOKUP_TIME",
     "HTTP_PAGE_FIRST_SENT_TO_LAST_RECEIVED",
     "HTTP_PAGE_OPEN_TO_FIRST_FROM_CACHE",
     "HTTP_PAGE_OPEN_TO_FIRST_FROM_CACHE_V2",
     "HTTP_PAGE_OPEN_TO_FIRST_RECEIVED",
     "HTTP_PAGE_OPEN_TO_FIRST_SENT",
     "HTTP_PAGE_REVALIDATION",
-    "HTTP_PAGE_TCP_CONNECTION",
     "HTTP_PROXY_TYPE",
     "HTTP_REQUEST_PER_CONN",
     "HTTP_REQUEST_PER_PAGE",
     "HTTP_REQUEST_PER_PAGE_FROM_CACHE",
     "HTTP_RESPONSE_VERSION",
     "HTTP_SAW_QUIC_ALT_PROTOCOL",
     "HTTP_SUBITEM_FIRST_BYTE_LATENCY_TIME",
     "HTTP_SUBITEM_OPEN_LATENCY_TIME",
@@ -1031,17 +1028,16 @@
     "HTTP_SUB_DNS_ISSUE_TIME",
     "HTTP_SUB_DNS_LOOKUP_TIME",
     "HTTP_SUB_FIRST_SENT_TO_LAST_RECEIVED",
     "HTTP_SUB_OPEN_TO_FIRST_FROM_CACHE",
     "HTTP_SUB_OPEN_TO_FIRST_FROM_CACHE_V2",
     "HTTP_SUB_OPEN_TO_FIRST_RECEIVED",
     "HTTP_SUB_OPEN_TO_FIRST_SENT",
     "HTTP_SUB_REVALIDATION",
-    "HTTP_SUB_TCP_CONNECTION",
     "HTTP_TRANSACTION_USE_ALTSVC",
     "HTTP_TRANSACTION_USE_ALTSVC_OE",
     "IMAGE_DECODE_CHUNKS",
     "IMAGE_DECODE_COUNT",
     "IMAGE_DECODE_LATENCY_US",
     "IMAGE_DECODE_ON_DRAW_LATENCY",
     "IMAGE_DECODE_SPEED_GIF",
     "IMAGE_DECODE_SPEED_JPEG",