From e040ed925030f899f845d458c226f7e439f4ec8b Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 29 Apr 2018 11:49:50 +0200 Subject: moebius#158: The Performance Resource Timing (added support for "workerStart") https://github.com/MoonchildProductions/moebius/pull/158 --- devtools/server/actors/webconsole.js | 6 +- dom/performance/PerformanceResourceTiming.cpp | 18 ++- dom/performance/PerformanceResourceTiming.h | 6 + dom/performance/PerformanceTiming.cpp | 71 ++++++--- dom/performance/PerformanceTiming.h | 10 +- dom/webidl/PerformanceResourceTiming.webidl | 8 +- dom/workers/ServiceWorkerEvents.cpp | 10 ++ dom/workers/ServiceWorkerPrivate.cpp | 45 +++++- dom/workers/ServiceWorkerPrivate.h | 1 + dom/workers/test/serviceworkers/chrome.ini | 3 + .../test_devtools_serviceworker_interception.html | 168 +++++++++++++++++++++ netwerk/base/nsINetworkInterceptController.idl | 28 ++++ netwerk/base/nsITimedChannel.idl | 18 ++- netwerk/ipc/NeckoChannelParams.ipdlh | 7 + netwerk/protocol/http/HttpBaseChannel.cpp | 156 ++++++++++++++++--- netwerk/protocol/http/HttpBaseChannel.h | 10 +- netwerk/protocol/http/HttpChannelChild.cpp | 7 + netwerk/protocol/http/HttpChannelParent.cpp | 25 ++- netwerk/protocol/http/HttpChannelParent.h | 8 +- netwerk/protocol/http/InterceptedChannel.cpp | 35 +++++ netwerk/protocol/http/InterceptedChannel.h | 51 +++++++ netwerk/protocol/http/NullHttpChannel.cpp | 106 ++++++++++++- netwerk/protocol/http/nsHttpChannel.cpp | 2 +- .../service-worker/resource-timing.https.html.ini | 7 - .../service-worker/resource-timing.https.html | 4 + .../resources/resource-timing-worker.js | 6 +- 26 files changed, 746 insertions(+), 70 deletions(-) create mode 100644 dom/workers/test/serviceworkers/test_devtools_serviceworker_interception.html diff --git a/devtools/server/actors/webconsole.js b/devtools/server/actors/webconsole.js index 9712ff32d..a1eba84ed 100644 --- a/devtools/server/actors/webconsole.js +++ b/devtools/server/actors/webconsole.js @@ -778,8 +778,8 @@ WebConsoleActor.prototype = } // See `window` definition. It isn't always a DOM Window. - let requestStartTime = this.window && this.window.performance ? - this.window.performance.timing.requestStart : 0; + let winStartTime = this.window && this.window.performance ? + this.window.performance.timing.navigationStart : 0; let cache = this.consoleAPIListener .getCachedMessages(!this.parentActor.isRootActor); @@ -787,7 +787,7 @@ WebConsoleActor.prototype = // Filter out messages that came from a ServiceWorker but happened // before the page was requested. if (aMessage.innerID === "ServiceWorker" && - requestStartTime > aMessage.timeStamp) { + winStartTime > aMessage.timeStamp) { return; } diff --git a/dom/performance/PerformanceResourceTiming.cpp b/dom/performance/PerformanceResourceTiming.cpp index 60a20ca28..94df27408 100644 --- a/dom/performance/PerformanceResourceTiming.cpp +++ b/dom/performance/PerformanceResourceTiming.cpp @@ -42,8 +42,22 @@ PerformanceResourceTiming::~PerformanceResourceTiming() DOMHighResTimeStamp PerformanceResourceTiming::StartTime() const { - DOMHighResTimeStamp startTime = mTiming->RedirectStartHighRes(); - return startTime ? startTime : mTiming->FetchStartHighRes(); + // Force the start time to be the earliest of: + // - RedirectStart + // - WorkerStart + // - AsyncOpen + // Ignore zero values. The RedirectStart and WorkerStart values + // can come from earlier redirected channels prior to the AsyncOpen + // time being recorded. + DOMHighResTimeStamp redirect = mTiming->RedirectStartHighRes(); + redirect = redirect ? redirect : DBL_MAX; + + DOMHighResTimeStamp worker = mTiming->WorkerStartHighRes(); + worker = worker ? worker : DBL_MAX; + + DOMHighResTimeStamp asyncOpen = mTiming->AsyncOpenHighRes(); + + return std::min(asyncOpen, std::min(redirect, worker)); } JSObject* diff --git a/dom/performance/PerformanceResourceTiming.h b/dom/performance/PerformanceResourceTiming.h index 2dd6b4a06..c2e6c0972 100644 --- a/dom/performance/PerformanceResourceTiming.h +++ b/dom/performance/PerformanceResourceTiming.h @@ -62,6 +62,12 @@ public: mNextHopProtocol = aNextHopProtocol; } + DOMHighResTimeStamp WorkerStart() const { + return mTiming && mTiming->TimingAllowed() + ? mTiming->WorkerStartHighRes() + : 0; + } + DOMHighResTimeStamp FetchStart() const { return mTiming ? mTiming->FetchStartHighRes() diff --git a/dom/performance/PerformanceTiming.cpp b/dom/performance/PerformanceTiming.cpp index e2f76a21f..887a23938 100755 --- a/dom/performance/PerformanceTiming.cpp +++ b/dom/performance/PerformanceTiming.cpp @@ -73,6 +73,7 @@ PerformanceTiming::InitializeTimingInfo(nsITimedChannel* aChannel) { if (aChannel) { aChannel->GetAsyncOpen(&mAsyncOpen); + aChannel->GetDispatchFetchEventStart(&mWorkerStart); aChannel->GetAllRedirectsSameOrigin(&mAllRedirectsSameOrigin); aChannel->GetRedirectCount(&mRedirectCount); aChannel->GetRedirectStart(&mRedirectStart); @@ -88,31 +89,39 @@ PerformanceTiming::InitializeTimingInfo(nsITimedChannel* aChannel) 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. + // The performance timing api essentially requires that the event timestamps + // have a strict relation with each other. The truth, however, is the browser + // engages in a number of speculative activities that sometimes mean connections + // and lookups begin at different times. Workaround that here by clamping + // these values to what we expect FetchStart to be. This means the later of + // AsyncOpen or WorkerStart times. if (!mAsyncOpen.IsNull()) { - if (!mDomainLookupStart.IsNull() && mDomainLookupStart < mAsyncOpen) { - mDomainLookupStart = mAsyncOpen; + // We want to clamp to the expected FetchStart value. This is later of + // the AsyncOpen and WorkerStart values. + const TimeStamp* clampTime = &mAsyncOpen; + if (!mWorkerStart.IsNull() && mWorkerStart > mAsyncOpen) { + clampTime = &mWorkerStart; } - if (!mDomainLookupEnd.IsNull() && mDomainLookupEnd < mAsyncOpen) { - mDomainLookupEnd = mAsyncOpen; + if (!mDomainLookupStart.IsNull() && mDomainLookupStart < *clampTime) { + mDomainLookupStart = *clampTime; } - if (!mConnectStart.IsNull() && mConnectStart < mAsyncOpen) { - mConnectStart = mAsyncOpen; + if (!mDomainLookupEnd.IsNull() && mDomainLookupEnd < *clampTime) { + mDomainLookupEnd = *clampTime; + } + + if (!mConnectStart.IsNull() && mConnectStart < *clampTime) { + mConnectStart = *clampTime; } if (mSecureConnection && !mSecureConnectionStart.IsNull() && - mSecureConnectionStart < mAsyncOpen) { - mSecureConnectionStart = mAsyncOpen; + mSecureConnectionStart < *clampTime) { + mSecureConnectionStart = *clampTime; } - if (!mConnectEnd.IsNull() && mConnectEnd < mAsyncOpen) { - mConnectEnd = mAsyncOpen; + if (!mConnectEnd.IsNull() && mConnectEnd < *clampTime) { + mConnectEnd = *clampTime; } } } @@ -131,9 +140,13 @@ PerformanceTiming::FetchStartHighRes() } MOZ_ASSERT(!mAsyncOpen.IsNull(), "The fetch start time stamp should always be " "valid if the performance timing is enabled"); - mFetchStart = (!mAsyncOpen.IsNull()) - ? TimeStampToDOMHighRes(mAsyncOpen) - : 0.0; + if (!mAsyncOpen.IsNull()) { + if (!mWorkerStart.IsNull() && mWorkerStart > mAsyncOpen) { + mFetchStart = TimeStampToDOMHighRes(mWorkerStart); + } else { + mFetchStart = TimeStampToDOMHighRes(mAsyncOpen); + } + } } return TimerClamping::ReduceMsTimeValue(mFetchStart); } @@ -180,7 +193,7 @@ PerformanceTiming::TimingAllowed() const return mTimingAllowed; } -uint16_t +uint8_t PerformanceTiming::GetRedirectCount() const { if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { @@ -205,6 +218,26 @@ PerformanceTiming::ShouldReportCrossOriginRedirect() const return (mRedirectCount != 0) && mReportCrossOriginRedirect; } +DOMHighResTimeStamp +PerformanceTiming::AsyncOpenHighRes() +{ + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() || + mAsyncOpen.IsNull()) { + return mZeroTime; + } + return TimeStampToReducedDOMHighResOrFetchStart(mAsyncOpen); +} + +DOMHighResTimeStamp +PerformanceTiming::WorkerStartHighRes() +{ + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() || + mWorkerStart.IsNull()) { + return mZeroTime; + } + return TimeStampToReducedDOMHighResOrFetchStart(mWorkerStart); +} + /** * RedirectStartHighRes() is used by both the navigation timing and the * resource timing. Since, navigation timing and resource timing check and diff --git a/dom/performance/PerformanceTiming.h b/dom/performance/PerformanceTiming.h index fc7e7d5bd..584ae0816 100755 --- a/dom/performance/PerformanceTiming.h +++ b/dom/performance/PerformanceTiming.h @@ -139,7 +139,7 @@ public: return TimerClamping::ReduceMsTimeValue(GetDOMTiming()->GetUnloadEventEnd()); } - uint16_t GetRedirectCount() const; + uint8_t GetRedirectCount() const; // Checks if the resource is either same origin as the page that started // the load, or if the response contains the Timing-Allow-Origin header @@ -155,7 +155,12 @@ public: // the timing-allow-origin check in HttpBaseChannel::TimingAllowCheck bool ShouldReportCrossOriginRedirect() const; + // The last channel's AsyncOpen time. This may occur before the FetchStart + // in some cases. + DOMHighResTimeStamp AsyncOpenHighRes(); + // High resolution (used by resource timing) + DOMHighResTimeStamp WorkerStartHighRes(); DOMHighResTimeStamp FetchStartHighRes(); DOMHighResTimeStamp RedirectStartHighRes(); DOMHighResTimeStamp RedirectEndHighRes(); @@ -253,6 +258,7 @@ private: DOMHighResTimeStamp mZeroTime; TimeStamp mAsyncOpen; + TimeStamp mWorkerStart; TimeStamp mRedirectStart; TimeStamp mRedirectEnd; TimeStamp mDomainLookupStart; @@ -265,7 +271,7 @@ private: TimeStamp mCacheReadStart; TimeStamp mResponseEnd; TimeStamp mCacheReadEnd; - uint16_t mRedirectCount; + uint8_t mRedirectCount; bool mTimingAllowed; bool mAllRedirectsSameOrigin; bool mInitialized; diff --git a/dom/webidl/PerformanceResourceTiming.webidl b/dom/webidl/PerformanceResourceTiming.webidl index 021b84ae2..112228325 100644 --- a/dom/webidl/PerformanceResourceTiming.webidl +++ b/dom/webidl/PerformanceResourceTiming.webidl @@ -4,7 +4,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. * * The origin of this IDL file is - * http://w3c-test.org/webperf/specs/ResourceTiming/#performanceresourcetiming + * https://w3c.github.io/resource-timing/#performanceresourcetiming * * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C * liability, trademark and document use rules apply. @@ -12,14 +12,10 @@ interface PerformanceResourceTiming : PerformanceEntry { - // A string with the name of that element that initiated the load. - // If the initiator is a CSS resource, the initiatorType attribute must return - // the string "css". - // If the initiator is an XMLHttpRequest object, the initiatorType attribute - // must return the string "xmlhttprequest". readonly attribute DOMString initiatorType; readonly attribute DOMString nextHopProtocol; + readonly attribute DOMHighResTimeStamp workerStart; readonly attribute DOMHighResTimeStamp redirectStart; readonly attribute DOMHighResTimeStamp redirectEnd; readonly attribute DOMHighResTimeStamp fetchStart; diff --git a/dom/workers/ServiceWorkerEvents.cpp b/dom/workers/ServiceWorkerEvents.cpp index 780b2f5f8..1f79e2c92 100644 --- a/dom/workers/ServiceWorkerEvents.cpp +++ b/dom/workers/ServiceWorkerEvents.cpp @@ -12,6 +12,7 @@ #include "nsINetworkInterceptController.h" #include "nsIOutputStream.h" #include "nsIScriptError.h" +#include "nsITimedChannel.h" #include "nsIUnicodeDecoder.h" #include "nsIUnicodeEncoder.h" #include "nsContentPolicyUtils.h" @@ -108,6 +109,12 @@ NS_IMETHODIMP CancelChannelRunnable::Run() { MOZ_ASSERT(NS_IsMainThread()); + + // TODO: When bug 1204254 is implemented, this time marker should be moved to + // the point where the body of the network request is complete. + mChannel->SetHandleFetchEventEnd(TimeStamp::Now()); + mChannel->SaveTimeStampsToUnderlyingChannel(); + mChannel->Cancel(mStatus); mRegistration->MaybeScheduleUpdate(); return NS_OK; @@ -230,6 +237,9 @@ public: return NS_OK; } + mChannel->SetHandleFetchEventEnd(TimeStamp::Now()); + mChannel->SaveTimeStampsToUnderlyingChannel(); + nsCOMPtr obsService = services::GetObserverService(); if (obsService) { obsService->NotifyObservers(underlyingChannel, "service-worker-synthesized-response", nullptr); diff --git a/dom/workers/ServiceWorkerPrivate.cpp b/dom/workers/ServiceWorkerPrivate.cpp index eaa548f95..24b2e11e6 100644 --- a/dom/workers/ServiceWorkerPrivate.cpp +++ b/dom/workers/ServiceWorkerPrivate.cpp @@ -13,6 +13,7 @@ #include "nsINetworkInterceptController.h" #include "nsIPushErrorReporter.h" #include "nsISupportsImpl.h" +#include "nsITimedChannel.h" #include "nsIUploadChannel2.h" #include "nsNetUtil.h" #include "nsProxyRelease.h" @@ -1255,6 +1256,7 @@ class FetchEventRunnable : public ExtendableFunctionalEventWorkerRunnable nsCString mMethod; nsString mClientId; bool mIsReload; + bool mMarkLaunchServiceWorkerEnd; RequestCache mCacheMode; RequestMode mRequestMode; RequestRedirect mRequestRedirect; @@ -1273,13 +1275,15 @@ public: const nsACString& aScriptSpec, nsMainThreadPtrHandle& aRegistration, const nsAString& aDocumentId, - bool aIsReload) + bool aIsReload, + bool aMarkLaunchServiceWorkerEnd) : ExtendableFunctionalEventWorkerRunnable( aWorkerPrivate, aKeepAliveToken, aRegistration) , mInterceptedChannel(aChannel) , mScriptSpec(aScriptSpec) , mClientId(aDocumentId) , mIsReload(aIsReload) + , mMarkLaunchServiceWorkerEnd(aMarkLaunchServiceWorkerEnd) , mCacheMode(RequestCache::Default) , mRequestMode(RequestMode::No_cors) , mRequestRedirect(RequestRedirect::Follow) @@ -1417,6 +1421,12 @@ public: WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { MOZ_ASSERT(aWorkerPrivate); + + if (mMarkLaunchServiceWorkerEnd) { + mInterceptedChannel->SetLaunchServiceWorkerEnd(TimeStamp::Now()); + } + + mInterceptedChannel->SetDispatchFetchEventEnd(TimeStamp::Now()); return DispatchFetchEvent(aCx, aWorkerPrivate); } @@ -1445,6 +1455,10 @@ private: NS_IMETHOD Run() override { AssertIsOnMainThread(); + + mChannel->SetHandleFetchEventEnd(TimeStamp::Now()); + mChannel->SaveTimeStampsToUnderlyingChannel(); + nsresult rv = mChannel->ResetInterception(); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to resume intercepted network request"); @@ -1520,6 +1534,8 @@ private: event->PostInit(mInterceptedChannel, mRegistration, mScriptSpec); event->SetTrusted(true); + mInterceptedChannel->SetHandleFetchEventStart(TimeStamp::Now()); + RefPtr target = do_QueryObject(aWorkerPrivate->GlobalScope()); nsresult rv2 = target->DispatchDOMEvent(nullptr, event, nullptr, nullptr); if (NS_WARN_IF(NS_FAILED(rv2)) || !event->WaitToRespond()) { @@ -1614,9 +1630,21 @@ ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel, nsCOMPtr failRunnable = NewRunnableMethod(aChannel, &nsIInterceptedChannel::ResetInterception); - nsresult rv = SpawnWorkerIfNeeded(FetchEvent, failRunnable, aLoadGroup); + aChannel->SetLaunchServiceWorkerStart(TimeStamp::Now()); + aChannel->SetDispatchFetchEventStart(TimeStamp::Now()); + + bool newWorkerCreated = false; + nsresult rv = SpawnWorkerIfNeeded(FetchEvent, + failRunnable, + &newWorkerCreated, + aLoadGroup); + NS_ENSURE_SUCCESS(rv, rv); + if (!newWorkerCreated) { + aChannel->SetLaunchServiceWorkerEnd(TimeStamp::Now()); + } + nsMainThreadPtrHandle handle( new nsMainThreadPtrHolder(aChannel, false)); @@ -1646,7 +1674,7 @@ ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel, RefPtr r = new FetchEventRunnable(mWorkerPrivate, token, handle, mInfo->ScriptSpec(), regInfo, - aDocumentId, aIsReload); + aDocumentId, aIsReload, newWorkerCreated); rv = r->Init(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; @@ -1669,6 +1697,7 @@ ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel, nsresult ServiceWorkerPrivate::SpawnWorkerIfNeeded(WakeUpReason aWhy, nsIRunnable* aLoadFailedRunnable, + bool* aNewWorkerCreated, nsILoadGroup* aLoadGroup) { AssertIsOnMainThread(); @@ -1679,6 +1708,12 @@ ServiceWorkerPrivate::SpawnWorkerIfNeeded(WakeUpReason aWhy, // the overriden load group when intercepting a fetch. MOZ_ASSERT_IF(aWhy == FetchEvent, aLoadGroup); + // Defaults to no new worker created, but if there is one, we'll set the value + // to true at the end of this function. + if (aNewWorkerCreated) { + *aNewWorkerCreated = false; + } + if (mWorkerPrivate) { mWorkerPrivate->UpdateOverridenLoadGroup(aLoadGroup); RenewKeepAliveToken(aWhy); @@ -1762,6 +1797,10 @@ ServiceWorkerPrivate::SpawnWorkerIfNeeded(WakeUpReason aWhy, RenewKeepAliveToken(aWhy); + if (aNewWorkerCreated) { + *aNewWorkerCreated = true; + } + return NS_OK; } diff --git a/dom/workers/ServiceWorkerPrivate.h b/dom/workers/ServiceWorkerPrivate.h index 8d59ea1d0..911b07a11 100644 --- a/dom/workers/ServiceWorkerPrivate.h +++ b/dom/workers/ServiceWorkerPrivate.h @@ -189,6 +189,7 @@ private: nsresult SpawnWorkerIfNeeded(WakeUpReason aWhy, nsIRunnable* aLoadFailedRunnable, + bool* aNewWorkerCreated = nullptr, nsILoadGroup* aLoadGroup = nullptr); ~ServiceWorkerPrivate(); diff --git a/dom/workers/test/serviceworkers/chrome.ini b/dom/workers/test/serviceworkers/chrome.ini index e064e7fd0..6d7dbebd0 100644 --- a/dom/workers/test/serviceworkers/chrome.ini +++ b/dom/workers/test/serviceworkers/chrome.ini @@ -3,6 +3,8 @@ skip-if = os == 'android' support-files = chrome_helpers.js empty.js + fetch.js + hello.html serviceworker.html serviceworkerinfo_iframe.html serviceworkermanager_iframe.html @@ -10,6 +12,7 @@ support-files = worker.js worker2.js +[test_devtools_serviceworker_interception.html] [test_privateBrowsing.html] [test_serviceworkerinfo.xul] [test_serviceworkermanager.xul] diff --git a/dom/workers/test/serviceworkers/test_devtools_serviceworker_interception.html b/dom/workers/test/serviceworkers/test_devtools_serviceworker_interception.html new file mode 100644 index 000000000..d49ebb2c9 --- /dev/null +++ b/dom/workers/test/serviceworkers/test_devtools_serviceworker_interception.html @@ -0,0 +1,168 @@ + + + + + Bug 1168875 - test devtools serviceworker interception. + + + + +

+ +

+
+
+
+
+
diff --git a/netwerk/base/nsINetworkInterceptController.idl b/netwerk/base/nsINetworkInterceptController.idl
index 17d27de42..721b7a334 100644
--- a/netwerk/base/nsINetworkInterceptController.idl
+++ b/netwerk/base/nsINetworkInterceptController.idl
@@ -14,12 +14,16 @@ interface nsIURI;
 %{C++
 #include "nsIConsoleReportCollector.h"
 namespace mozilla {
+class TimeStamp;
+
 namespace dom {
 class ChannelInfo;
 }
 }
 %}
 
+native TimeStamp(mozilla::TimeStamp);
+
 [ptr] native ChannelInfo(mozilla::dom::ChannelInfo);
 
 /**
@@ -97,6 +101,30 @@ interface nsIInterceptedChannel : nsISupports
     [noscript]
     readonly attribute nsIConsoleReportCollector consoleReportCollector;
 
+    /**
+     * Save the timestamps of various service worker interception phases.
+     */
+    [noscript]
+    void SetLaunchServiceWorkerStart(in TimeStamp aTimeStamp);
+
+    [noscript]
+    void SetLaunchServiceWorkerEnd(in TimeStamp aTimeStamp);
+
+    [noscript]
+    void SetDispatchFetchEventStart(in TimeStamp aTimeStamp);
+
+    [noscript]
+    void SetDispatchFetchEventEnd(in TimeStamp aTimeStamp);
+
+    [noscript]
+    void SetHandleFetchEventStart(in TimeStamp aTimeStamp);
+
+    [noscript]
+    void SetHandleFetchEventEnd(in TimeStamp aTimeStamp);
+
+    [noscript]
+    void SaveTimeStampsToUnderlyingChannel();
+
 %{C++
     already_AddRefed
     GetConsoleReportCollector()
diff --git a/netwerk/base/nsITimedChannel.idl b/netwerk/base/nsITimedChannel.idl
index 13b65e7b8..83670a11e 100644
--- a/netwerk/base/nsITimedChannel.idl
+++ b/netwerk/base/nsITimedChannel.idl
@@ -21,7 +21,8 @@ interface nsITimedChannel : nsISupports {
   attribute boolean timingEnabled;
 
   // The number of redirects
-  attribute uint16_t redirectCount;
+  attribute uint8_t redirectCount;
+  attribute uint8_t internalRedirectCount;
 
   [noscript] readonly attribute TimeStamp channelCreation;
   [noscript] readonly attribute TimeStamp asyncOpen;
@@ -37,6 +38,15 @@ interface nsITimedChannel : nsISupports {
   [noscript] readonly attribute TimeStamp responseStart;
   [noscript] readonly attribute TimeStamp responseEnd;
 
+  // The following are only set when the request is intercepted by a service
+  // worker no matter the response is synthesized.
+  [noscript] attribute TimeStamp launchServiceWorkerStart;
+  [noscript] attribute TimeStamp launchServiceWorkerEnd;
+  [noscript] attribute TimeStamp dispatchFetchEventStart;
+  [noscript] attribute TimeStamp dispatchFetchEventEnd;
+  [noscript] attribute TimeStamp handleFetchEventStart;
+  [noscript] attribute TimeStamp handleFetchEventEnd;
+
   // The redirect attributes timings must be writeble, se we can transfer
   // the data from one channel to the redirected channel.
   [noscript] attribute TimeStamp redirectStart;
@@ -67,6 +77,12 @@ interface nsITimedChannel : nsISupports {
   // All following are PRTime versions of the above.
   readonly attribute PRTime channelCreationTime;
   readonly attribute PRTime asyncOpenTime;
+  readonly attribute PRTime launchServiceWorkerStartTime;
+  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;
diff --git a/netwerk/ipc/NeckoChannelParams.ipdlh b/netwerk/ipc/NeckoChannelParams.ipdlh
index 4f4dcf6a9..bb7562c64 100644
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -20,6 +20,7 @@ using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 using RequestHeaderTuples from "mozilla/net/PHttpChannelParams.h";
 using struct nsHttpAtom from "nsHttp.h";
 using class nsHttpResponseHead from "nsHttpResponseHead.h";
+using class mozilla::TimeStamp from "mozilla/TimeStamp.h";
 
 namespace mozilla {
 namespace net {
@@ -134,6 +135,12 @@ struct HttpChannelOpenArgs
   nsCString                   channelId;
   uint64_t                    contentWindowId;
   nsCString                   preferredAlternativeType;
+  TimeStamp                   launchServiceWorkerStart;
+  TimeStamp                   launchServiceWorkerEnd;
+  TimeStamp                   dispatchFetchEventStart;
+  TimeStamp                   dispatchFetchEventEnd;
+  TimeStamp                   handleFetchEventStart;
+  TimeStamp                   handleFetchEventEnd;
 };
 
 struct HttpChannelConnectArgs
diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp
index 278c94db0..d161f9a43 100644
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -105,6 +105,7 @@ HttpBaseChannel::HttpBaseChannel()
   , mHttpHandler(gHttpHandler)
   , mReferrerPolicy(REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE)
   , mRedirectCount(0)
+  , mInternalRedirectCount(0)
   , mForcePending(false)
   , mCorsIncludeCredentials(false)
   , mCorsMode(nsIHttpChannelInternal::CORS_MODE_NO_CORS)
@@ -3128,12 +3129,6 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI       *newURI,
   // convey the mAllowPipelining and mAllowSTS flags
   httpChannel->SetAllowPipelining(mAllowPipelining);
   httpChannel->SetAllowSTS(mAllowSTS);
-  // convey the new redirection limit
-  // make sure we don't underflow
-  uint32_t redirectionLimit = mRedirectionLimit
-    ? mRedirectionLimit - 1
-    : 0;
-  httpChannel->SetRedirectionLimit(redirectionLimit);
 
   // convey the Accept header value
   {
@@ -3215,23 +3210,40 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI       *newURI,
       do_QueryInterface(static_cast(this)));
   if (oldTimedChannel && newTimedChannel) {
     newTimedChannel->SetTimingEnabled(mTimingEnabled);
-    newTimedChannel->SetRedirectCount(mRedirectCount + 1);
+
+    if (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
+      int8_t newCount = mInternalRedirectCount + 1;
+      newTimedChannel->SetInternalRedirectCount(
+        std::max(newCount, mInternalRedirectCount));
+    } else {
+      int8_t newCount = mRedirectCount + 1;
+      newTimedChannel->SetRedirectCount(
+        std::max(newCount, mRedirectCount));
+    }
 
     // If the RedirectStart is null, we will use the AsyncOpen value of the
     // previous channel (this is the first redirect in the redirects chain).
     if (mRedirectStartTimeStamp.IsNull()) {
-      TimeStamp asyncOpen;
-      oldTimedChannel->GetAsyncOpen(&asyncOpen);
-      newTimedChannel->SetRedirectStart(asyncOpen);
-    }
-    else {
+      // Only do this for real redirects.  Internal redirects should be hidden.
+      if (!(redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
+        TimeStamp asyncOpen;
+        oldTimedChannel->GetAsyncOpen(&asyncOpen);
+        newTimedChannel->SetRedirectStart(asyncOpen);
+      }
+    } else {
       newTimedChannel->SetRedirectStart(mRedirectStartTimeStamp);
     }
 
-    // The RedirectEnd timestamp is equal to the previous channel response end.
-    TimeStamp prevResponseEnd;
-    oldTimedChannel->GetResponseEnd(&prevResponseEnd);
-    newTimedChannel->SetRedirectEnd(prevResponseEnd);
+    // For internal redirects just propagate the last redirect end time
+    // forward.  Otherwise the new redirect end time is the last response
+    // end time.
+    TimeStamp newRedirectEnd;
+    if (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
+      oldTimedChannel->GetRedirectEnd(&newRedirectEnd);
+    } else {
+      oldTimedChannel->GetResponseEnd(&newRedirectEnd);
+    }
+    newTimedChannel->SetRedirectEnd(newRedirectEnd);
 
     nsAutoString initiatorType;
     oldTimedChannel->GetInitiatorType(initiatorType);
@@ -3253,6 +3265,16 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI       *newURI,
         mAllRedirectsPassTimingAllowCheck &&
         oldTimedChannel->TimingAllowCheck(principal));
     }
+
+    // Propagate service worker measurements across redirects.  The
+    // PeformanceResourceTiming.workerStart API expects to see the
+    // worker start time after a redirect.
+    newTimedChannel->SetLaunchServiceWorkerStart(mLaunchServiceWorkerStart);
+    newTimedChannel->SetLaunchServiceWorkerEnd(mLaunchServiceWorkerEnd);
+    newTimedChannel->SetDispatchFetchEventStart(mDispatchFetchEventStart);
+    newTimedChannel->SetDispatchFetchEventEnd(mDispatchFetchEventEnd);
+    newTimedChannel->SetHandleFetchEventStart(mHandleFetchEventStart);
+    newTimedChannel->SetHandleFetchEventEnd(mHandleFetchEventEnd);
   }
 
   // Pass the preferred alt-data type on to the new channel.
@@ -3318,19 +3340,33 @@ HttpBaseChannel::GetAsyncOpen(TimeStamp* _retval) {
  * redirects. This check must be done by the consumers.
  */
 NS_IMETHODIMP
-HttpBaseChannel::GetRedirectCount(uint16_t *aRedirectCount)
+HttpBaseChannel::GetRedirectCount(uint8_t *aRedirectCount)
 {
   *aRedirectCount = mRedirectCount;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-HttpBaseChannel::SetRedirectCount(uint16_t aRedirectCount)
+HttpBaseChannel::SetRedirectCount(uint8_t aRedirectCount)
 {
   mRedirectCount = aRedirectCount;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+HttpBaseChannel::GetInternalRedirectCount(uint8_t *aRedirectCount)
+{
+  *aRedirectCount = mInternalRedirectCount;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetInternalRedirectCount(uint8_t aRedirectCount)
+{
+  mInternalRedirectCount = aRedirectCount;
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 HttpBaseChannel::GetRedirectStart(TimeStamp* _retval)
 {
@@ -3430,6 +3466,84 @@ HttpBaseChannel::TimingAllowCheck(nsIPrincipal *aOrigin, bool *_retval)
   return NS_OK;
 }
 
+NS_IMETHODIMP
+HttpBaseChannel::GetLaunchServiceWorkerStart(TimeStamp* _retval) {
+  MOZ_ASSERT(_retval);
+  *_retval = mLaunchServiceWorkerStart;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) {
+  mLaunchServiceWorkerStart = aTimeStamp;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLaunchServiceWorkerEnd(TimeStamp* _retval) {
+  MOZ_ASSERT(_retval);
+  *_retval = mLaunchServiceWorkerEnd;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) {
+  mLaunchServiceWorkerEnd = aTimeStamp;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDispatchFetchEventStart(TimeStamp* _retval) {
+  MOZ_ASSERT(_retval);
+  *_retval = mDispatchFetchEventStart;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDispatchFetchEventStart(TimeStamp aTimeStamp) {
+  mDispatchFetchEventStart = aTimeStamp;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDispatchFetchEventEnd(TimeStamp* _retval) {
+  MOZ_ASSERT(_retval);
+  *_retval = mDispatchFetchEventEnd;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDispatchFetchEventEnd(TimeStamp aTimeStamp) {
+  mDispatchFetchEventEnd = aTimeStamp;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetHandleFetchEventStart(TimeStamp* _retval) {
+  MOZ_ASSERT(_retval);
+  *_retval = mHandleFetchEventStart;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetHandleFetchEventStart(TimeStamp aTimeStamp) {
+  mHandleFetchEventStart = aTimeStamp;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetHandleFetchEventEnd(TimeStamp* _retval) {
+  MOZ_ASSERT(_retval);
+  *_retval = mHandleFetchEventEnd;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetHandleFetchEventEnd(TimeStamp aTimeStamp) {
+  mHandleFetchEventEnd = aTimeStamp;
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 HttpBaseChannel::GetDomainLookupStart(TimeStamp* _retval) {
   *_retval = mTransactionTimings.domainLookupStart;
@@ -3520,6 +3634,12 @@ HttpBaseChannel::Get##name##Time(PRTime* _retval) {            \
 
 IMPL_TIMING_ATTR(ChannelCreation)
 IMPL_TIMING_ATTR(AsyncOpen)
+IMPL_TIMING_ATTR(LaunchServiceWorkerStart)
+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)
diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h
index c8184a601..9aa696a70 100644
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -506,7 +506,9 @@ protected:
   // the HTML file.
   nsString                          mInitiatorType;
   // Number of redirects that has occurred.
-  int16_t                           mRedirectCount;
+  int8_t                            mRedirectCount;
+  // Number of internal redirects that has occurred.
+  int8_t                            mInternalRedirectCount;
   // A time value equal to the starting time of the fetch that initiates the
   // redirect.
   mozilla::TimeStamp                mRedirectStartTimeStamp;
@@ -519,6 +521,12 @@ protected:
   TimeStamp                         mAsyncOpenTime;
   TimeStamp                         mCacheReadStart;
   TimeStamp                         mCacheReadEnd;
+  TimeStamp                         mLaunchServiceWorkerStart;
+  TimeStamp                         mLaunchServiceWorkerEnd;
+  TimeStamp                         mDispatchFetchEventStart;
+  TimeStamp                         mDispatchFetchEventEnd;
+  TimeStamp                         mHandleFetchEventStart;
+  TimeStamp                         mHandleFetchEventEnd;
   // copied from the transaction before we null out mTransaction
   // so that the timing can still be queried from OnStopRequest
   TimingStruct                      mTransactionTimings;
diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp
index f0b9e2136..6d09135c4 100644
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -2132,6 +2132,13 @@ HttpChannelChild::ContinueAsyncOpen()
     return NS_ERROR_FAILURE;
   }
 
+  openArgs.launchServiceWorkerStart() = mLaunchServiceWorkerStart;
+  openArgs.launchServiceWorkerEnd()   = mLaunchServiceWorkerEnd;
+  openArgs.dispatchFetchEventStart()  = mDispatchFetchEventStart;
+  openArgs.dispatchFetchEventEnd()    = mDispatchFetchEventEnd;
+  openArgs.handleFetchEventStart()    = mHandleFetchEventStart;
+  openArgs.handleFetchEventEnd()      = mHandleFetchEventEnd;
+
   // The socket transport in the chrome process now holds a logical ref to us
   // until OnStopRequest, or we do a redirect, or we hit an IPDL error.
   AddIPDLReference();
diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp
index 5f0859f28..90ed597a6 100644
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -130,7 +130,13 @@ HttpChannelParent::Init(const HttpChannelCreationArgs& aArgs)
                        a.initialRwin(), a.blockAuthPrompt(),
                        a.suspendAfterSynthesizeResponse(),
                        a.allowStaleCacheContent(), a.contentTypeHint(),
-                       a.channelId(), a.contentWindowId(), a.preferredAlternativeType());
+                       a.channelId(), a.contentWindowId(), a.preferredAlternativeType(),
+                       a.launchServiceWorkerStart(),
+                       a.launchServiceWorkerEnd(),
+                       a.dispatchFetchEventStart(),
+                       a.dispatchFetchEventEnd(),
+                       a.handleFetchEventStart(),
+                       a.handleFetchEventEnd());
   }
   case HttpChannelCreationArgs::THttpChannelConnectArgs:
   {
@@ -329,7 +335,13 @@ HttpChannelParent::DoAsyncOpen(  const URIParams&           aURI,
                                  const nsCString&           aContentTypeHint,
                                  const nsCString&           aChannelId,
                                  const uint64_t&            aContentWindowId,
-                                 const nsCString&           aPreferredAlternativeType)
+                                 const nsCString&           aPreferredAlternativeType,
+                                 const TimeStamp&           aLaunchServiceWorkerStart,
+                                 const TimeStamp&           aLaunchServiceWorkerEnd,
+                                 const TimeStamp&           aDispatchFetchEventStart,
+                                 const TimeStamp&           aDispatchFetchEventEnd,
+                                 const TimeStamp&           aHandleFetchEventStart,
+                                 const TimeStamp&           aHandleFetchEventEnd)
 {
   nsCOMPtr uri = DeserializeURI(aURI);
   if (!uri) {
@@ -534,6 +546,13 @@ HttpChannelParent::DoAsyncOpen(  const URIParams&           aURI,
   mChannel->SetInitialRwin(aInitialRwin);
   mChannel->SetBlockAuthPrompt(aBlockAuthPrompt);
 
+  mChannel->SetLaunchServiceWorkerStart(aLaunchServiceWorkerStart);
+  mChannel->SetLaunchServiceWorkerEnd(aLaunchServiceWorkerEnd);
+  mChannel->SetDispatchFetchEventStart(aDispatchFetchEventStart);
+  mChannel->SetDispatchFetchEventEnd(aDispatchFetchEventEnd);
+  mChannel->SetHandleFetchEventStart(aHandleFetchEventStart);
+  mChannel->SetHandleFetchEventEnd(aHandleFetchEventEnd);
+
   nsCOMPtr appCacheChan =
     do_QueryObject(mChannel);
   nsCOMPtr appCacheService =
@@ -1159,7 +1178,7 @@ HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
   nsCString secInfoSerialization;
   UpdateAndSerializeSecurityInfo(secInfoSerialization);
 
-  uint16_t redirectCount = 0;
+  uint8_t redirectCount = 0;
   chan->GetRedirectCount(&redirectCount);
 
   nsCOMPtr cacheKey;
diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h
index a3b377d49..56854bb55 100644
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -143,7 +143,13 @@ protected:
                    const nsCString&           aContentTypeHint,
                    const nsCString&           aChannelId,
                    const uint64_t&            aContentWindowId,
-                   const nsCString&           aPreferredAlternativeType);
+                   const nsCString&           aPreferredAlternativeType,
+                   const TimeStamp&           aLaunchServiceWorkerStart,
+                   const TimeStamp&           aLaunchServiceWorkerEnd,
+                   const TimeStamp&           aDispatchFetchEventStart,
+                   const TimeStamp&           aDispatchFetchEventEnd,
+                   const TimeStamp&           aHandleFetchEventStart,
+                   const TimeStamp&           aHandleFetchEventEnd);
 
   virtual bool RecvSetPriority(const uint16_t& priority) override;
   virtual bool RecvSetClassOfService(const uint32_t& cos) override;
diff --git a/netwerk/protocol/http/InterceptedChannel.cpp b/netwerk/protocol/http/InterceptedChannel.cpp
index 9e38e2734..2dadbe760 100644
--- a/netwerk/protocol/http/InterceptedChannel.cpp
+++ b/netwerk/protocol/http/InterceptedChannel.cpp
@@ -10,6 +10,7 @@
 #include "nsInputStreamPump.h"
 #include "nsIPipe.h"
 #include "nsIStreamListener.h"
+#include "nsITimedChannel.h"
 #include "nsHttpChannel.h"
 #include "HttpChannelChild.h"
 #include "nsHttpResponseHead.h"
@@ -134,6 +135,40 @@ InterceptedChannelBase::SetReleaseHandle(nsISupports* aHandle)
   return NS_OK;
 }
 
+NS_IMETHODIMP
+InterceptedChannelBase::SaveTimeStampsToUnderlyingChannel()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr underlyingChannel;
+  nsresult rv = GetChannel(getter_AddRefs(underlyingChannel));
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  nsCOMPtr timedChannel =
+    do_QueryInterface(underlyingChannel);
+  MOZ_ASSERT(timedChannel);
+
+  rv = timedChannel->SetLaunchServiceWorkerStart(mLaunchServiceWorkerStart);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  rv = timedChannel->SetLaunchServiceWorkerEnd(mLaunchServiceWorkerEnd);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  rv = timedChannel->SetDispatchFetchEventStart(mDispatchFetchEventStart);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  rv = timedChannel->SetDispatchFetchEventEnd(mDispatchFetchEventEnd);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  rv = timedChannel->SetHandleFetchEventStart(mHandleFetchEventStart);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  rv = timedChannel->SetHandleFetchEventEnd(mHandleFetchEventEnd);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  return rv;
+}
+
 /* static */
 already_AddRefed
 InterceptedChannelBase::SecureUpgradeChannelURI(nsIChannel* aChannel)
diff --git a/netwerk/protocol/http/InterceptedChannel.h b/netwerk/protocol/http/InterceptedChannel.h
index 688f211de..efab7abca 100644
--- a/netwerk/protocol/http/InterceptedChannel.h
+++ b/netwerk/protocol/http/InterceptedChannel.h
@@ -46,6 +46,13 @@ protected:
   nsresult DoSynthesizeStatus(uint16_t aStatus, const nsACString& aReason);
   nsresult DoSynthesizeHeader(const nsACString& aName, const nsACString& aValue);
 
+  TimeStamp mLaunchServiceWorkerStart;
+  TimeStamp mLaunchServiceWorkerEnd;
+  TimeStamp mDispatchFetchEventStart;
+  TimeStamp mDispatchFetchEventEnd;
+  TimeStamp mHandleFetchEventStart;
+  TimeStamp mHandleFetchEventEnd;
+
   virtual ~InterceptedChannelBase();
 public:
   explicit InterceptedChannelBase(nsINetworkInterceptController* aController);
@@ -60,6 +67,50 @@ public:
   NS_IMETHOD GetConsoleReportCollector(nsIConsoleReportCollector** aCollectorOut) override;
   NS_IMETHOD SetReleaseHandle(nsISupports* aHandle) override;
 
+  NS_IMETHODIMP
+  SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) override
+  {
+    mLaunchServiceWorkerStart = aTimeStamp;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP
+  SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) override
+  {
+    mLaunchServiceWorkerEnd = aTimeStamp;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP
+  SetDispatchFetchEventStart(TimeStamp aTimeStamp) override
+  {
+    mDispatchFetchEventStart = aTimeStamp;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP
+  SetDispatchFetchEventEnd(TimeStamp aTimeStamp) override
+  {
+    mDispatchFetchEventEnd = aTimeStamp;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP
+  SetHandleFetchEventStart(TimeStamp aTimeStamp) override
+  {
+    mHandleFetchEventStart = aTimeStamp;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP
+  SetHandleFetchEventEnd(TimeStamp aTimeStamp) override
+  {
+    mHandleFetchEventEnd = aTimeStamp;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP SaveTimeStampsToUnderlyingChannel() override;
+
   static already_AddRefed
   SecureUpgradeChannelURI(nsIChannel* aChannel);
 };
diff --git a/netwerk/protocol/http/NullHttpChannel.cpp b/netwerk/protocol/http/NullHttpChannel.cpp
index 61efe3956..2954006ad 100644
--- a/netwerk/protocol/http/NullHttpChannel.cpp
+++ b/netwerk/protocol/http/NullHttpChannel.cpp
@@ -539,13 +539,25 @@ NullHttpChannel::SetTimingEnabled(bool aTimingEnabled)
 }
 
 NS_IMETHODIMP
-NullHttpChannel::GetRedirectCount(uint16_t *aRedirectCount)
+NullHttpChannel::GetRedirectCount(uint8_t *aRedirectCount)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
-NullHttpChannel::SetRedirectCount(uint16_t aRedirectCount)
+NullHttpChannel::SetRedirectCount(uint8_t aRedirectCount)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetInternalRedirectCount(uint8_t *aRedirectCount)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetInternalRedirectCount(uint8_t aRedirectCount)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
@@ -564,6 +576,90 @@ NullHttpChannel::GetAsyncOpen(mozilla::TimeStamp *aAsyncOpen)
   return NS_OK;
 }
 
+NS_IMETHODIMP
+NullHttpChannel::GetLaunchServiceWorkerStart(mozilla::TimeStamp *_retval)
+{
+  MOZ_ASSERT(_retval);
+  *_retval = mAsyncOpenTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetLaunchServiceWorkerStart(mozilla::TimeStamp aTimeStamp)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetLaunchServiceWorkerEnd(mozilla::TimeStamp *_retval)
+{
+  MOZ_ASSERT(_retval);
+  *_retval = mAsyncOpenTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetLaunchServiceWorkerEnd(mozilla::TimeStamp aTimeStamp)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetDispatchFetchEventStart(mozilla::TimeStamp *_retval)
+{
+  MOZ_ASSERT(_retval);
+  *_retval = mAsyncOpenTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetDispatchFetchEventStart(mozilla::TimeStamp aTimeStamp)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetDispatchFetchEventEnd(mozilla::TimeStamp *_retval)
+{
+  MOZ_ASSERT(_retval);
+  *_retval = mAsyncOpenTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetDispatchFetchEventEnd(mozilla::TimeStamp aTimeStamp)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetHandleFetchEventStart(mozilla::TimeStamp *_retval)
+{
+  MOZ_ASSERT(_retval);
+  *_retval = mAsyncOpenTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetHandleFetchEventStart(mozilla::TimeStamp aTimeStamp)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetHandleFetchEventEnd(mozilla::TimeStamp *_retval)
+{
+  MOZ_ASSERT(_retval);
+  *_retval = mAsyncOpenTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetHandleFetchEventEnd(mozilla::TimeStamp aTimeStamp)
+{
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 NullHttpChannel::GetDomainLookupStart(mozilla::TimeStamp *aDomainLookupStart)
 {
@@ -761,6 +857,12 @@ NullHttpChannel::Get##name##Time(PRTime* _retval) {            \
 
 IMPL_TIMING_ATTR(ChannelCreation)
 IMPL_TIMING_ATTR(AsyncOpen)
+IMPL_TIMING_ATTR(LaunchServiceWorkerStart)
+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)
diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp
index 94b0d9bf9..05699df62 100644
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -5402,7 +5402,7 @@ nsHttpChannel::AsyncProcessRedirection(uint32_t redirectType)
     if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII, locationBuf))
         location = locationBuf;
 
-    if (mRedirectionLimit == 0) {
+    if (mRedirectCount >= mRedirectionLimit || mInternalRedirectCount >= mRedirectionLimit) {
         LOG(("redirection limit reached!\n"));
         return NS_ERROR_REDIRECT_LOOP;
     }
diff --git a/testing/web-platform/meta/service-workers/service-worker/resource-timing.https.html.ini b/testing/web-platform/meta/service-workers/service-worker/resource-timing.https.html.ini
index b399d5f38..b6f02262b 100644
--- a/testing/web-platform/meta/service-workers/service-worker/resource-timing.https.html.ini
+++ b/testing/web-platform/meta/service-workers/service-worker/resource-timing.https.html.ini
@@ -1,9 +1,2 @@
 [resource-timing.https.html]
   type: testharness
-  disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1178713
-  [Controlled resource loads]
-    expected: FAIL
-
-  [Non-controlled resource loads]
-    expected: FAIL
-
diff --git a/testing/web-platform/tests/service-workers/service-worker/resource-timing.https.html b/testing/web-platform/tests/service-workers/service-worker/resource-timing.https.html
index f33c41d71..3a1adfa48 100644
--- a/testing/web-platform/tests/service-workers/service-worker/resource-timing.https.html
+++ b/testing/web-platform/tests/service-workers/service-worker/resource-timing.https.html
@@ -13,6 +13,10 @@ function verify(performance, resource, description) {
     assert_greater_than(entry.workerStart, 0, description);
     assert_greater_than_equal(entry.workerStart, entry.startTime, description);
     assert_less_than_equal(entry.workerStart, entry.fetchStart, description);
+    assert_greater_than_equal(entry.responseStart, entry.fetchStart, description);
+    assert_greater_than_equal(entry.responseEnd, entry.responseStart, description);
+    assert_greater_than(entry.responseEnd, entry.fetchStart, description);
+    assert_greater_than(entry.duration, 0, description);
     if (resource.indexOf('redirect.py') != -1) {
         assert_less_than_equal(entry.workerStart, entry.redirectStart,
                                description);
diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-worker.js
index 481a6536a..452876838 100644
--- a/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-worker.js
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-worker.js
@@ -1,5 +1,9 @@
 self.addEventListener('fetch', function(event) {
     if (event.request.url.indexOf('dummy.js') != -1) {
-      event.respondWith(new Response());
+      event.respondWith(new Promise(resolve => {
+        // Slightly delay the response so we ensure we get a non-zero
+        // duration.
+        setTimeout(_ => resolve(new Response()), 100);
+      }));
     }
   });
-- 
cgit v1.2.3