Bug 1231213 - Implement PFetchEventOp(Proxy) IPDL protocols and FetchEventOp(Proxy){Parent,Child}. r=asuth
☠☠ backed out by 3cf55b7f12f2 ☠ ☠
authorPerry Jiang <perry@mozilla.com>
Wed, 14 Aug 2019 16:20:08 +0000
changeset 488072 85df1959eb98a95bf7aa59332d366ebb3f45d1eb
parent 488071 666bf42600469276c778a9ab9a4e1324eccd02ea
child 488073 1b9a8b022fce17c513ec0af3dff3ffd6db88c33d
push id113900
push usercbrindusan@mozilla.com
push dateThu, 15 Aug 2019 09:53:50 +0000
treeherdermozilla-inbound@0db07ff50ab5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1231213
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1231213 - Implement PFetchEventOp(Proxy) IPDL protocols and FetchEventOp(Proxy){Parent,Child}. r=asuth Differential Revision: https://phabricator.services.mozilla.com/D26171
dom/serviceworkers/FetchEventOpChild.cpp
dom/serviceworkers/FetchEventOpChild.h
dom/serviceworkers/FetchEventOpParent.cpp
dom/serviceworkers/FetchEventOpParent.h
dom/serviceworkers/FetchEventOpProxyChild.cpp
dom/serviceworkers/FetchEventOpProxyChild.h
dom/serviceworkers/FetchEventOpProxyParent.cpp
dom/serviceworkers/FetchEventOpProxyParent.h
dom/serviceworkers/PFetchEventOp.ipdl
dom/serviceworkers/PFetchEventOpProxy.ipdl
dom/serviceworkers/ServiceWorkerPrivate.cpp
dom/serviceworkers/ServiceWorkerPrivate.h
dom/serviceworkers/moz.build
dom/workers/remoteworkers/PRemoteWorker.ipdl
dom/workers/remoteworkers/PRemoteWorkerController.ipdl
dom/workers/remoteworkers/RemoteWorkerChild.cpp
dom/workers/remoteworkers/RemoteWorkerChild.h
dom/workers/remoteworkers/RemoteWorkerControllerChild.cpp
dom/workers/remoteworkers/RemoteWorkerControllerChild.h
dom/workers/remoteworkers/RemoteWorkerControllerParent.cpp
dom/workers/remoteworkers/RemoteWorkerControllerParent.h
dom/workers/remoteworkers/RemoteWorkerParent.cpp
dom/workers/remoteworkers/RemoteWorkerParent.h
new file mode 100644
--- /dev/null
+++ b/dom/serviceworkers/FetchEventOpChild.cpp
@@ -0,0 +1,487 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FetchEventOpChild.h"
+
+#include <utility>
+
+#include "MainThreadUtils.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsIChannel.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsIContentPolicy.h"
+#include "nsIInputStream.h"
+#include "nsILoadInfo.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIObserverService.h"
+#include "nsIScriptError.h"
+#include "nsISupportsImpl.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+#include "ServiceWorkerPrivate.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/InternalHeaders.h"
+#include "mozilla/dom/InternalResponse.h"
+#include "mozilla/dom/PRemoteWorkerControllerChild.h"
+#include "mozilla/dom/ServiceWorkerRegistrationInfo.h"
+#include "mozilla/net/NeckoChannelParams.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+bool CSPPermitsResponse(nsILoadInfo* aLoadInfo, InternalResponse* aResponse,
+                        const nsACString& aWorkerScriptSpec) {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aLoadInfo);
+
+  nsCString url = aResponse->GetUnfilteredURL();
+  if (url.IsEmpty()) {
+    // Synthetic response.
+    url = aWorkerScriptSpec;
+  }
+
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  int16_t decision = nsIContentPolicy::ACCEPT;
+  rv = NS_CheckContentLoadPolicy(uri, aLoadInfo, EmptyCString(), &decision);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  return decision == nsIContentPolicy::ACCEPT;
+}
+
+void AsyncLog(nsIInterceptedChannel* aChannel, const nsACString& aScriptSpec,
+              uint32_t aLineNumber, uint32_t aColumnNumber,
+              const nsACString& aMessageName, nsTArray<nsString>&& aParams) {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aChannel);
+
+  nsCOMPtr<nsIConsoleReportCollector> reporter =
+      aChannel->GetConsoleReportCollector();
+
+  if (reporter) {
+    // NOTE: is appears that `const nsTArray<nsString>&` is required for
+    // nsIConsoleReportCollector::AddConsoleReport to resolve to the correct
+    // overload.
+    const nsTArray<nsString> params = std::move(aParams);
+
+    reporter->AddConsoleReport(
+        nsIScriptError::errorFlag,
+        NS_LITERAL_CSTRING("Service Worker Interception"),
+        nsContentUtils::eDOM_PROPERTIES, aScriptSpec, aLineNumber,
+        aColumnNumber, aMessageName, params);
+  }
+}
+
+class SynthesizeResponseWatcher final : public nsIInterceptedBodyCallback {
+ public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  SynthesizeResponseWatcher(
+      const nsMainThreadPtrHandle<nsIInterceptedChannel>& aInterceptedChannel,
+      const nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
+      const bool aIsNonSubresourceRequest,
+      FetchEventRespondWithClosure&& aClosure, nsAString&& aRequestURL)
+      : mInterceptedChannel(aInterceptedChannel),
+        mRegistration(aRegistration),
+        mIsNonSubresourceRequest(aIsNonSubresourceRequest),
+        mClosure(std::move(aClosure)),
+        mRequestURL(std::move(aRequestURL)) {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(mInterceptedChannel);
+    MOZ_ASSERT(mRegistration);
+  }
+
+  NS_IMETHOD
+  BodyComplete(nsresult aRv) override {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(mInterceptedChannel);
+
+    if (NS_WARN_IF(NS_FAILED(aRv))) {
+      AsyncLog(mInterceptedChannel, mClosure.respondWithScriptSpec(),
+               mClosure.respondWithLineNumber(),
+               mClosure.respondWithColumnNumber(),
+               NS_LITERAL_CSTRING("InterceptionFailedWithURL"), {mRequestURL});
+
+      CancelInterception(NS_ERROR_INTERCEPTION_FAILED);
+
+      return NS_OK;
+    }
+
+    nsresult rv = mInterceptedChannel->FinishSynthesizedResponse();
+
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      CancelInterception(rv);
+    }
+
+    mInterceptedChannel = nullptr;
+
+    return NS_OK;
+  }
+
+  // See FetchEventOpChild::MaybeScheduleRegistrationUpdate() for comments.
+  void CancelInterception(nsresult aStatus) {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(mInterceptedChannel);
+    MOZ_ASSERT(mRegistration);
+
+    mInterceptedChannel->CancelInterception(aStatus);
+
+    if (mIsNonSubresourceRequest) {
+      mRegistration->MaybeScheduleUpdate();
+    } else {
+      mRegistration->MaybeScheduleTimeCheckAndUpdate();
+    }
+
+    mInterceptedChannel = nullptr;
+    mRegistration = nullptr;
+  }
+
+ private:
+  ~SynthesizeResponseWatcher() {
+    if (NS_WARN_IF(mInterceptedChannel)) {
+      CancelInterception(NS_ERROR_DOM_ABORT_ERR);
+    }
+  }
+
+  nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
+  nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
+  const bool mIsNonSubresourceRequest;
+  const FetchEventRespondWithClosure mClosure;
+  const nsString mRequestURL;
+};
+
+NS_IMPL_ISUPPORTS(SynthesizeResponseWatcher, nsIInterceptedBodyCallback)
+
+}  // anonymous namespace
+
+/* static */ RefPtr<GenericPromise> FetchEventOpChild::Create(
+    PRemoteWorkerControllerChild* aManager,
+    ServiceWorkerFetchEventOpArgs&& aArgs,
+    nsCOMPtr<nsIInterceptedChannel> aInterceptedChannel,
+    RefPtr<ServiceWorkerRegistrationInfo> aRegistration,
+    RefPtr<KeepAliveToken>&& aKeepAliveToken) {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aManager);
+  MOZ_ASSERT(aInterceptedChannel);
+  MOZ_ASSERT(aKeepAliveToken);
+
+  FetchEventOpChild* actor = new FetchEventOpChild(
+      std::move(aArgs), std::move(aInterceptedChannel),
+      std::move(aRegistration), std::move(aKeepAliveToken));
+  Unused << aManager->SendPFetchEventOpConstructor(actor, actor->mArgs);
+  return actor->mPromiseHolder.Ensure(__func__);
+}
+
+FetchEventOpChild::~FetchEventOpChild() {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mInterceptedChannelHandled);
+  MOZ_DIAGNOSTIC_ASSERT(mPromiseHolder.IsEmpty());
+}
+
+FetchEventOpChild::FetchEventOpChild(
+    ServiceWorkerFetchEventOpArgs&& aArgs,
+    nsCOMPtr<nsIInterceptedChannel>&& aInterceptedChannel,
+    RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration,
+    RefPtr<KeepAliveToken>&& aKeepAliveToken)
+    : mArgs(std::move(aArgs)),
+      mInterceptedChannel(std::move(aInterceptedChannel)),
+      mRegistration(std::move(aRegistration)),
+      mKeepAliveToken(std::move(aKeepAliveToken)) {}
+
+mozilla::ipc::IPCResult FetchEventOpChild::RecvAsyncLog(
+    const nsCString& aScriptSpec, const uint32_t& aLineNumber,
+    const uint32_t& aColumnNumber, const nsCString& aMessageName,
+    nsTArray<nsString>&& aParams) {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mInterceptedChannel);
+
+  AsyncLog(mInterceptedChannel, aScriptSpec, aLineNumber, aColumnNumber,
+           aMessageName, std::move(aParams));
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FetchEventOpChild::RecvRespondWith(
+    IPCFetchEventRespondWithResult&& aResult) {
+  AssertIsOnMainThread();
+
+  switch (aResult.type()) {
+    case IPCFetchEventRespondWithResult::TIPCSynthesizeResponseArgs:
+      SynthesizeResponse(std::move(aResult.get_IPCSynthesizeResponseArgs()));
+      break;
+    case IPCFetchEventRespondWithResult::TResetInterceptionArgs:
+      ResetInterception();
+      break;
+    case IPCFetchEventRespondWithResult::TCancelInterceptionArgs:
+      CancelInterception(aResult.get_CancelInterceptionArgs().status());
+      break;
+    default:
+      MOZ_CRASH("Unknown IPCFetchEventRespondWithResult type!");
+      break;
+  }
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FetchEventOpChild::Recv__delete__(
+    const ServiceWorkerFetchEventOpResult& aResult) {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mRegistration);
+
+  if (NS_WARN_IF(!mInterceptedChannelHandled)) {
+    MOZ_ASSERT(NS_FAILED(aResult.rv()));
+    NS_WARNING(
+        "Failed to handle intercepted network request; canceling "
+        "interception!");
+
+    CancelInterception(aResult.rv());
+  }
+
+  mPromiseHolder.ResolveIfExists(true, __func__);
+
+  /**
+   * This corresponds to the "Fire Functional Event" algorithm's step 9:
+   *
+   * "If the time difference in seconds calculated by the current time minus
+   * registration's last update check time is greater than 84600, invoke Soft
+   * Update algorithm with registration."
+   *
+   * TODO: this is probably being called later than it should be; it should be
+   * called ASAP after dispatching the FetchEvent.
+   */
+  mRegistration->MaybeScheduleTimeCheckAndUpdate();
+
+  return IPC_OK();
+}
+
+void FetchEventOpChild::ActorDestroy(ActorDestroyReason) {
+  AssertIsOnMainThread();
+
+  // If `Recv__delete__` was called, it would have resolved the promise already.
+  mPromiseHolder.RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__);
+
+  if (NS_WARN_IF(!mInterceptedChannelHandled)) {
+    Unused << Recv__delete__(NS_ERROR_DOM_ABORT_ERR);
+  }
+}
+
+nsresult FetchEventOpChild::StartSynthesizedResponse(
+    IPCSynthesizeResponseArgs&& aArgs) {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mInterceptedChannel);
+  MOZ_ASSERT(!mInterceptedChannelHandled);
+  MOZ_ASSERT(mRegistration);
+
+  /**
+   * TODO: moving the IPCInternalResponse won't do anything right now because
+   * there isn't a prefect-forwarding or rvalue-ref-parameter overload of
+   * `InternalResponse::FromIPC().`
+   */
+  RefPtr<InternalResponse> response =
+      InternalResponse::FromIPC(aArgs.internalResponse());
+  if (NS_WARN_IF(!response)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIChannel> underlyingChannel;
+  nsresult rv =
+      mInterceptedChannel->GetChannel(getter_AddRefs(underlyingChannel));
+  if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!underlyingChannel)) {
+    return NS_FAILED(rv) ? rv : NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsILoadInfo> loadInfo = underlyingChannel->LoadInfo();
+  if (!CSPPermitsResponse(loadInfo, response, mArgs.workerScriptSpec())) {
+    return NS_ERROR_CONTENT_BLOCKED;
+  }
+
+  MOZ_ASSERT(response->GetChannelInfo().IsInitialized());
+  ChannelInfo channelInfo = response->GetChannelInfo();
+  rv = mInterceptedChannel->SetChannelInfo(&channelInfo);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_INTERCEPTION_FAILED;
+  }
+
+  rv = mInterceptedChannel->SynthesizeStatus(
+      response->GetUnfilteredStatus(), response->GetUnfilteredStatusText());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  AutoTArray<InternalHeaders::Entry, 5> entries;
+  response->UnfilteredHeaders()->GetEntries(entries);
+  for (auto& entry : entries) {
+    mInterceptedChannel->SynthesizeHeader(entry.mName, entry.mValue);
+  }
+
+  auto castLoadInfo = static_cast<mozilla::net::LoadInfo*>(loadInfo.get());
+  castLoadInfo->SynthesizeServiceWorkerTainting(response->GetTainting());
+
+  // Get the preferred alternative data type of the outer channel
+  nsAutoCString preferredAltDataType(EmptyCString());
+  nsCOMPtr<nsICacheInfoChannel> outerChannel =
+      do_QueryInterface(underlyingChannel);
+  if (outerChannel &&
+      !outerChannel->PreferredAlternativeDataTypes().IsEmpty()) {
+    preferredAltDataType.Assign(
+        outerChannel->PreferredAlternativeDataTypes()[0].type());
+  }
+
+  nsCOMPtr<nsIInputStream> body;
+  if (preferredAltDataType.Equals(response->GetAlternativeDataType())) {
+    body = response->TakeAlternativeBody();
+  }
+  if (!body) {
+    response->GetUnfilteredBody(getter_AddRefs(body));
+  } else {
+    Telemetry::ScalarAdd(Telemetry::ScalarID::SW_ALTERNATIVE_BODY_USED_COUNT,
+                         1);
+  }
+
+  // Propagate the URL to the content if the request mode is not "navigate".
+  // Note that, we only reflect the final URL if the response.redirected is
+  // false. We propagate all the URLs if the response.redirected is true.
+  const IPCInternalRequest& request = mArgs.internalRequest();
+  nsAutoCString responseURL;
+  if (request.requestMode() != RequestMode::Navigate) {
+    responseURL = response->GetUnfilteredURL();
+
+    // Similar to how we apply the request fragment to redirects automatically
+    // we also want to apply it automatically when propagating the response
+    // URL from a service worker interception.  Currently response.url strips
+    // the fragment, so this will never conflict with an existing fragment
+    // on the response.  In the future we will have to check for a response
+    // fragment and avoid overriding in that case.
+    if (!request.fragment().IsEmpty() && !responseURL.IsEmpty()) {
+      MOZ_ASSERT(!responseURL.Contains('#'));
+      responseURL.AppendLiteral("#");
+      responseURL.Append(request.fragment());
+    }
+  }
+
+  nsMainThreadPtrHandle<nsIInterceptedChannel> interceptedChannel(
+      new nsMainThreadPtrHolder<nsIInterceptedChannel>(
+          "nsIInterceptedChannel", mInterceptedChannel, false));
+
+  nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> registration(
+      new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(
+          "ServiceWorkerRegistrationInfo", mRegistration, false));
+
+  nsCString requestURL = request.urlList().LastElement();
+  if (!request.fragment().IsEmpty()) {
+    requestURL.AppendLiteral("#");
+    requestURL.Append(request.fragment());
+  }
+
+  RefPtr<SynthesizeResponseWatcher> watcher = new SynthesizeResponseWatcher(
+      interceptedChannel, registration, mArgs.isNonSubresourceRequest(),
+      std::move(aArgs.closure()), NS_ConvertUTF8toUTF16(responseURL));
+
+  rv = mInterceptedChannel->StartSynthesizedResponse(
+      body, watcher, nullptr /* TODO */, responseURL, response->IsRedirected());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+  if (obsService) {
+    obsService->NotifyObservers(underlyingChannel,
+                                "service-worker-synthesized-response", nullptr);
+  }
+
+  return rv;
+}
+
+void FetchEventOpChild::SynthesizeResponse(IPCSynthesizeResponseArgs&& aArgs) {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mInterceptedChannel);
+  MOZ_ASSERT(!mInterceptedChannelHandled);
+
+  nsresult rv = StartSynthesizedResponse(std::move(aArgs));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    NS_WARNING("Failed to synthesize response!");
+
+    mInterceptedChannel->CancelInterception(rv);
+  }
+
+  mInterceptedChannelHandled = true;
+
+  MaybeScheduleRegistrationUpdate();
+}
+
+void FetchEventOpChild::ResetInterception() {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mInterceptedChannel);
+  MOZ_ASSERT(!mInterceptedChannelHandled);
+
+  nsresult rv = mInterceptedChannel->ResetInterception();
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    NS_WARNING("Failed to resume intercepted network request!");
+
+    mInterceptedChannel->CancelInterception(rv);
+  }
+
+  mInterceptedChannelHandled = true;
+
+  MaybeScheduleRegistrationUpdate();
+}
+
+void FetchEventOpChild::CancelInterception(nsresult aStatus) {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mInterceptedChannel);
+  MOZ_ASSERT(!mInterceptedChannelHandled);
+  MOZ_ASSERT(NS_FAILED(aStatus));
+
+  mInterceptedChannel->CancelInterception(aStatus);
+  mInterceptedChannelHandled = true;
+
+  MaybeScheduleRegistrationUpdate();
+}
+
+/**
+ * This corresponds to the "Handle Fetch" algorithm's steps 20.3, 21.2, and
+ * 22.2:
+ *
+ * "If request is a non-subresource request, or request is a subresource
+ * request and the time difference in seconds calculated by the current time
+ * minus registration's last update check time is greater than 86400, invoke
+ * Soft Update algorithm with registration."
+ */
+void FetchEventOpChild::MaybeScheduleRegistrationUpdate() const {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mRegistration);
+  MOZ_ASSERT(mInterceptedChannelHandled);
+
+  if (mArgs.isNonSubresourceRequest()) {
+    mRegistration->MaybeScheduleUpdate();
+  } else {
+    mRegistration->MaybeScheduleTimeCheckAndUpdate();
+  }
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/serviceworkers/FetchEventOpChild.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_fetcheventopchild_h__
+#define mozilla_dom_fetcheventopchild_h__
+
+#include "nsCOMPtr.h"
+
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/PFetchEventOpChild.h"
+#include "mozilla/dom/ServiceWorkerOpArgs.h"
+
+class nsIInterceptedChannel;
+
+namespace mozilla {
+namespace dom {
+
+class KeepAliveToken;
+class PRemoteWorkerControllerChild;
+class ServiceWorkerRegistrationInfo;
+
+/**
+ * FetchEventOpChild represents an in-flight FetchEvent operation.
+ */
+class FetchEventOpChild final : public PFetchEventOpChild {
+  friend class PFetchEventOpChild;
+
+ public:
+  static RefPtr<GenericPromise> Create(
+      PRemoteWorkerControllerChild* aManager,
+      ServiceWorkerFetchEventOpArgs&& aArgs,
+      nsCOMPtr<nsIInterceptedChannel> aInterceptedChannel,
+      RefPtr<ServiceWorkerRegistrationInfo> aRegistrationInfo,
+      RefPtr<KeepAliveToken>&& aKeepAliveToken);
+
+  ~FetchEventOpChild();
+
+ private:
+  FetchEventOpChild(ServiceWorkerFetchEventOpArgs&& aArgs,
+                    nsCOMPtr<nsIInterceptedChannel>&& aInterceptedChannel,
+                    RefPtr<ServiceWorkerRegistrationInfo>&& aRegistrationInfo,
+                    RefPtr<KeepAliveToken>&& aKeepAliveToken);
+
+  mozilla::ipc::IPCResult RecvAsyncLog(const nsCString& aScriptSpec,
+                                       const uint32_t& aLineNumber,
+                                       const uint32_t& aColumnNumber,
+                                       const nsCString& aMessageName,
+                                       nsTArray<nsString>&& aParams);
+
+  mozilla::ipc::IPCResult RecvRespondWith(
+      IPCFetchEventRespondWithResult&& aResult);
+
+  mozilla::ipc::IPCResult Recv__delete__(
+      const ServiceWorkerFetchEventOpResult& aResult) override;
+
+  void ActorDestroy(ActorDestroyReason) override;
+
+  nsresult StartSynthesizedResponse(IPCSynthesizeResponseArgs&& aArgs);
+
+  void SynthesizeResponse(IPCSynthesizeResponseArgs&& aArgs);
+
+  void ResetInterception();
+
+  void CancelInterception(nsresult aStatus);
+
+  void MaybeScheduleRegistrationUpdate() const;
+
+  const ServiceWorkerFetchEventOpArgs mArgs;
+  nsCOMPtr<nsIInterceptedChannel> mInterceptedChannel;
+  RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
+  RefPtr<KeepAliveToken> mKeepAliveToken;
+  bool mInterceptedChannelHandled = false;
+  MozPromiseHolder<GenericPromise> mPromiseHolder;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_fetcheventopchild_h__
new file mode 100644
--- /dev/null
+++ b/dom/serviceworkers/FetchEventOpParent.cpp
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FetchEventOpParent.h"
+
+#include "nsDebug.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/FetchEventOpProxyParent.h"
+#include "mozilla/dom/RemoteWorkerControllerParent.h"
+#include "mozilla/dom/RemoteWorkerParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+void FetchEventOpParent::Initialize(
+    const ServiceWorkerFetchEventOpArgs& aArgs) {
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+
+  RemoteWorkerControllerParent* manager =
+      static_cast<RemoteWorkerControllerParent*>(Manager());
+  MOZ_ASSERT(manager);
+
+  // This will be null when the manager's RemoteWorkerController has shutdown.
+  RefPtr<RemoteWorkerParent> proxyManager = manager->GetRemoteWorkerParent();
+  if (NS_WARN_IF(!proxyManager)) {
+    Unused << Send__delete__(this, NS_ERROR_DOM_ABORT_ERR);
+
+    return;
+  }
+
+  FetchEventOpProxyParent::Create(proxyManager.get(), aArgs, this);
+}
+
+void FetchEventOpParent::ActorDestroy(ActorDestroyReason) {
+  AssertIsOnBackgroundThread();
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/serviceworkers/FetchEventOpParent.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_fetcheventopparent_h__
+#define mozilla_dom_fetcheventopparent_h__
+
+#include "nsISupports.h"
+
+#include "mozilla/dom/PFetchEventOpParent.h"
+
+namespace mozilla {
+namespace dom {
+
+class ServiceWorkerFetchEventOpArgs;
+
+class FetchEventOpParent final : public PFetchEventOpParent {
+  friend class PFetchEventOpParent;
+
+ public:
+  NS_INLINE_DECL_REFCOUNTING(FetchEventOpParent)
+
+  FetchEventOpParent() = default;
+
+  void Initialize(const ServiceWorkerFetchEventOpArgs& aArgs);
+
+ private:
+  ~FetchEventOpParent() = default;
+
+  void ActorDestroy(ActorDestroyReason) override;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_fetcheventopparent_h__
new file mode 100644
--- /dev/null
+++ b/dom/serviceworkers/FetchEventOpProxyChild.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FetchEventOpProxyChild.h"
+
+#include <utility>
+
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/RemoteWorkerChild.h"
+#include "mozilla/dom/RemoteWorkerService.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+namespace {
+
+nsresult GetIPCSynthesizeResponseArgs(
+    IPCSynthesizeResponseArgs* aIPCArgs, SynthesizeResponseArgs&& aArgs,
+    UniquePtr<AutoIPCStream>& aAutoBodyStream,
+    UniquePtr<AutoIPCStream>& aAutoAlternativeBodyStream) {
+  MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread());
+
+  PBackgroundChild* bgChild = BackgroundChild::GetOrCreateForCurrentThread();
+
+  if (NS_WARN_IF(!bgChild)) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  aArgs.first()->ToIPC(&aIPCArgs->internalResponse(), bgChild, aAutoBodyStream,
+                       aAutoAlternativeBodyStream);
+  aIPCArgs->closure() = std::move(aArgs.second());
+
+  return NS_OK;
+}
+
+}  // anonymous namespace
+
+void FetchEventOpProxyChild::Initialize(
+    const ServiceWorkerFetchEventOpArgs& aArgs) {
+  MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread());
+
+  mInternalRequest = new InternalRequest(aArgs.internalRequest());
+
+  RemoteWorkerChild* manager = static_cast<RemoteWorkerChild*>(Manager());
+  MOZ_ASSERT(manager);
+
+  RefPtr<FetchEventOpProxyChild> self = this;
+
+  auto callback = [self](const ServiceWorkerOpResult& aResult) {
+    if (!self->CanSend()) {
+      return;
+    }
+
+    if (NS_WARN_IF(aResult.type() == ServiceWorkerOpResult::Tnsresult)) {
+      Unused << self->Send__delete__(self, aResult.get_nsresult());
+      return;
+    }
+
+    MOZ_ASSERT(aResult.type() ==
+               ServiceWorkerOpResult::TServiceWorkerFetchEventOpResult);
+
+    Unused << self->Send__delete__(self, aResult);
+  };
+}
+
+RefPtr<InternalRequest> FetchEventOpProxyChild::ExtractInternalRequest() {
+  MOZ_ASSERT(IsCurrentThreadRunningWorker());
+  MOZ_ASSERT(mInternalRequest);
+
+  return RefPtr<InternalRequest>(std::move(mInternalRequest));
+}
+
+void FetchEventOpProxyChild::ActorDestroy(ActorDestroyReason) {
+  Unused << NS_WARN_IF(mRespondWithPromiseRequestHolder.Exists());
+  mRespondWithPromiseRequestHolder.DisconnectIfExists();
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/serviceworkers/FetchEventOpProxyChild.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_fetcheventopproxychild_h__
+#define mozilla_dom_fetcheventopproxychild_h__
+
+#include "nsISupportsImpl.h"
+
+#include "ServiceWorkerOpPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/InternalRequest.h"
+#include "mozilla/dom/PFetchEventOpProxyChild.h"
+
+namespace mozilla {
+namespace dom {
+
+class InternalRequest;
+class ServiceWorkerFetchEventOpArgs;
+
+class FetchEventOpProxyChild final : public PFetchEventOpProxyChild {
+  friend class PFetchEventOpProxyChild;
+
+ public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FetchEventOpProxyChild)
+
+  FetchEventOpProxyChild() = default;
+
+  void Initialize(const ServiceWorkerFetchEventOpArgs& aArgs);
+
+  // Must only be called once and on a worker thread.
+  RefPtr<InternalRequest> ExtractInternalRequest();
+
+ private:
+  ~FetchEventOpProxyChild() = default;
+
+  void ActorDestroy(ActorDestroyReason) override;
+
+  MozPromiseRequestHolder<FetchEventRespondWithPromise>
+      mRespondWithPromiseRequestHolder;
+
+  // Initialized on RemoteWorkerService::Thread, read on a worker thread.
+  RefPtr<InternalRequest> mInternalRequest;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_fetcheventopproxychild_h__
new file mode 100644
--- /dev/null
+++ b/dom/serviceworkers/FetchEventOpProxyParent.cpp
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FetchEventOpProxyParent.h"
+
+#include <utility>
+
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/FetchEventOpParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+namespace {
+
+void MaybeDeserializeAndReserialize(const Maybe<IPCStream>& aDeserialize,
+                                    Maybe<IPCStream>& aReserialize,
+                                    UniquePtr<AutoIPCStream>& aAutoStream,
+                                    PBackgroundParent* aManager) {
+  nsCOMPtr<nsIInputStream> maybeDeserialized =
+      DeserializeIPCStream(aDeserialize);
+
+  if (!maybeDeserialized) {
+    return;
+  }
+
+  aAutoStream.reset(new AutoIPCStream(aReserialize));
+  DebugOnly<bool> ok = aAutoStream->Serialize(maybeDeserialized, aManager);
+  MOZ_ASSERT(ok);
+}
+
+}  // anonymous namespace
+
+/* static */ void FetchEventOpProxyParent::Create(
+    PRemoteWorkerParent* aManager, const ServiceWorkerFetchEventOpArgs& aArgs,
+    RefPtr<FetchEventOpParent> aReal) {
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aManager);
+  MOZ_ASSERT(aReal);
+
+  FetchEventOpProxyParent* actor =
+      new FetchEventOpProxyParent(std::move(aReal));
+
+  if (aArgs.internalRequest().body().isNothing()) {
+    Unused << aManager->SendPFetchEventOpProxyConstructor(actor, aArgs);
+    return;
+  }
+
+  ServiceWorkerFetchEventOpArgs copyArgs = aArgs;
+  IPCInternalRequest& copyRequest = copyArgs.internalRequest();
+
+  PBackgroundParent* bgParent = aManager->Manager();
+  MOZ_ASSERT(bgParent);
+
+  UniquePtr<AutoIPCStream> autoBodyStream = MakeUnique<AutoIPCStream>();
+  MaybeDeserializeAndReserialize(aArgs.internalRequest().body(),
+                                 copyRequest.body(), autoBodyStream, bgParent);
+
+  Unused << aManager->SendPFetchEventOpProxyConstructor(actor, copyArgs);
+  autoBodyStream->TakeOptionalValue();
+}
+
+FetchEventOpProxyParent::~FetchEventOpProxyParent() {
+  AssertIsOnBackgroundThread();
+}
+
+FetchEventOpProxyParent::FetchEventOpProxyParent(
+    RefPtr<FetchEventOpParent>&& aReal)
+    : mReal(std::move(aReal)) {}
+
+mozilla::ipc::IPCResult FetchEventOpProxyParent::RecvAsyncLog(
+    const nsCString& aScriptSpec, const uint32_t& aLineNumber,
+    const uint32_t& aColumnNumber, const nsCString& aMessageName,
+    nsTArray<nsString>&& aParams) {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mReal);
+
+  Unused << mReal->SendAsyncLog(aScriptSpec, aLineNumber, aColumnNumber,
+                                aMessageName, aParams);
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FetchEventOpProxyParent::RecvRespondWith(
+    const IPCFetchEventRespondWithResult& aResult) {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mReal);
+
+  // IPCSynthesizeResponseArgs possibly contains an IPCStream. If so,
+  // deserialize it and reserialize it before forwarding it to the main thread.
+  if (aResult.type() ==
+      IPCFetchEventRespondWithResult::TIPCSynthesizeResponseArgs) {
+    const IPCSynthesizeResponseArgs& originalArgs =
+        aResult.get_IPCSynthesizeResponseArgs();
+    const IPCInternalResponse& originalResponse =
+        originalArgs.internalResponse();
+
+    // Do nothing if neither the body nor the alt. body can be deserialized.
+    if (!originalResponse.body() && !originalResponse.alternativeBody()) {
+      Unused << mReal->SendRespondWith(aResult);
+      return IPC_OK();
+    }
+
+    IPCSynthesizeResponseArgs copyArgs = originalArgs;
+    IPCInternalResponse& copyResponse = copyArgs.internalResponse();
+
+    PRemoteWorkerControllerParent* manager = mReal->Manager();
+    MOZ_ASSERT(manager);
+
+    PBackgroundParent* bgParent = manager->Manager();
+    MOZ_ASSERT(bgParent);
+
+    UniquePtr<AutoIPCStream> autoBodyStream = MakeUnique<AutoIPCStream>();
+    UniquePtr<AutoIPCStream> autoAlternativeBodyStream =
+        MakeUnique<AutoIPCStream>();
+
+    MaybeDeserializeAndReserialize(originalResponse.body(), copyResponse.body(),
+                                   autoBodyStream, bgParent);
+    MaybeDeserializeAndReserialize(originalResponse.alternativeBody(),
+                                   copyResponse.alternativeBody(),
+                                   autoAlternativeBodyStream, bgParent);
+
+    Unused << mReal->SendRespondWith(copyArgs);
+
+    autoBodyStream->TakeOptionalValue();
+    autoAlternativeBodyStream->TakeOptionalValue();
+  } else {
+    Unused << mReal->SendRespondWith(aResult);
+  }
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FetchEventOpProxyParent::Recv__delete__(
+    const ServiceWorkerFetchEventOpResult& aResult) {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mReal);
+
+  Unused << mReal->Send__delete__(mReal, aResult);
+  mReal = nullptr;
+
+  return IPC_OK();
+}
+
+void FetchEventOpProxyParent::ActorDestroy(ActorDestroyReason) {
+  AssertIsOnBackgroundThread();
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/serviceworkers/FetchEventOpProxyParent.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_fetcheventopproxyparent_h__
+#define mozilla_dom_fetcheventopproxyparent_h__
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/PFetchEventOpProxyParent.h"
+
+namespace mozilla {
+namespace dom {
+
+class FetchEventOpParent;
+class PRemoteWorkerParent;
+class ServiceWorkerFetchEventOpArgs;
+
+/**
+ * FetchEventOpProxyParent owns a FetchEventOpParent and is responsible for
+ * calling PFetchEventOpParent::Send__delete__.
+ */
+class FetchEventOpProxyParent final : public PFetchEventOpProxyParent {
+  friend class PFetchEventOpProxyParent;
+
+ public:
+  static void Create(PRemoteWorkerParent* aManager,
+                     const ServiceWorkerFetchEventOpArgs& aArgs,
+                     RefPtr<FetchEventOpParent> aReal);
+
+  ~FetchEventOpProxyParent();
+
+ private:
+  explicit FetchEventOpProxyParent(RefPtr<FetchEventOpParent>&& aReal);
+
+  mozilla::ipc::IPCResult RecvAsyncLog(const nsCString& aScriptSpec,
+                                       const uint32_t& aLineNumber,
+                                       const uint32_t& aColumnNumber,
+                                       const nsCString& aMessageName,
+                                       nsTArray<nsString>&& aParams);
+
+  mozilla::ipc::IPCResult RecvRespondWith(
+      const IPCFetchEventRespondWithResult& aResult);
+
+  mozilla::ipc::IPCResult Recv__delete__(
+      const ServiceWorkerFetchEventOpResult& aResult) override;
+
+  void ActorDestroy(ActorDestroyReason) override;
+
+  RefPtr<FetchEventOpParent> mReal;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_fetcheventopproxyparent_h__
new file mode 100644
--- /dev/null
+++ b/dom/serviceworkers/PFetchEventOp.ipdl
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PRemoteWorkerController;
+
+include ServiceWorkerOpArgs;
+
+namespace mozilla {
+namespace dom {
+
+protocol PFetchEventOp {
+  manager PRemoteWorkerController;
+
+ child:
+  async AsyncLog(nsCString aScriptSpec, uint32_t aLineNumber,
+                 uint32_t aColumnNumber, nsCString aMessageName,
+                 nsString[] aParams);
+
+  async RespondWith(IPCFetchEventRespondWithResult aResult);
+
+  async __delete__(ServiceWorkerFetchEventOpResult aResult);
+};
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/serviceworkers/PFetchEventOpProxy.ipdl
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PRemoteWorker;
+
+include ServiceWorkerOpArgs;
+
+namespace mozilla {
+namespace dom {
+
+protocol PFetchEventOpProxy {
+  manager PRemoteWorker;
+
+ parent:
+  async AsyncLog(nsCString aScriptSpec, uint32_t aLineNumber,
+                 uint32_t aColumnNumber, nsCString aMessageName,
+                 nsString[] aParams);
+
+  async RespondWith(IPCFetchEventRespondWithResult aResult);
+
+  async __delete__(ServiceWorkerFetchEventOpResult aResult);
+};
+
+}  // namespace dom
+}  // namespace mozilla
--- a/dom/serviceworkers/ServiceWorkerPrivate.cpp
+++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp
@@ -63,36 +63,27 @@ NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(S
 // Tracks the "dom.serviceWorkers.disable_open_click_delay" preference. Modified
 // on main thread, read on worker threads.
 // It is updated every time a "notificationclick" event is dispatched. While
 // this is done without synchronization, at the worst, the thread will just get
 // an older value within which a popup is allowed to be displayed, which will
 // still be a valid value since it was set prior to dispatching the runnable.
 Atomic<uint32_t> gDOMDisableOpenClickDelay(0);
 
-// Used to keep track of pending waitUntil as well as in-flight extendable
-// events. When the last token is released, we attempt to terminate the worker.
-class KeepAliveToken final : public nsISupports {
- public:
-  NS_DECL_ISUPPORTS
+KeepAliveToken::KeepAliveToken(ServiceWorkerPrivate* aPrivate)
+    : mPrivate(aPrivate) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aPrivate);
+  mPrivate->AddToken();
+}
 
-  explicit KeepAliveToken(ServiceWorkerPrivate* aPrivate) : mPrivate(aPrivate) {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(aPrivate);
-    mPrivate->AddToken();
-  }
-
- private:
-  ~KeepAliveToken() {
-    MOZ_ASSERT(NS_IsMainThread());
-    mPrivate->ReleaseToken();
-  }
-
-  RefPtr<ServiceWorkerPrivate> mPrivate;
-};
+KeepAliveToken::~KeepAliveToken() {
+  MOZ_ASSERT(NS_IsMainThread());
+  mPrivate->ReleaseToken();
+}
 
 NS_IMPL_ISUPPORTS0(KeepAliveToken)
 
 ServiceWorkerPrivate::ServiceWorkerPrivate(ServiceWorkerInfo* aInfo)
     : mInfo(aInfo), mDebuggerCount(0), mTokenCount(0) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aInfo);
 
--- a/dom/serviceworkers/ServiceWorkerPrivate.h
+++ b/dom/serviceworkers/ServiceWorkerPrivate.h
@@ -19,33 +19,47 @@ class nsIWorkerDebugger;
 
 namespace mozilla {
 
 class JSObjectHolder;
 
 namespace dom {
 
 class ClientInfoAndState;
-class KeepAliveToken;
 class ServiceWorkerCloneData;
 class ServiceWorkerInfo;
+class ServiceWorkerPrivate;
 class ServiceWorkerRegistrationInfo;
 
 namespace ipc {
 class StructuredCloneData;
 }  // namespace ipc
 
 class LifeCycleEventCallback : public Runnable {
  public:
   LifeCycleEventCallback() : Runnable("dom::LifeCycleEventCallback") {}
 
   // Called on the worker thread.
   virtual void SetResult(bool aResult) = 0;
 };
 
+// Used to keep track of pending waitUntil as well as in-flight extendable
+// events. When the last token is released, we attempt to terminate the worker.
+class KeepAliveToken final : public nsISupports {
+ public:
+  NS_DECL_ISUPPORTS
+
+  explicit KeepAliveToken(ServiceWorkerPrivate* aPrivate);
+
+ private:
+  ~KeepAliveToken();
+
+  RefPtr<ServiceWorkerPrivate> mPrivate;
+};
+
 // ServiceWorkerPrivate is a wrapper for managing the on-demand aspect of
 // service workers. It handles all event dispatching to the worker and ensures
 // the worker thread is running when needed.
 //
 // Lifetime management: To spin up the worker thread we own a |WorkerPrivate|
 // object which can be cancelled if no events are received for a certain
 // amount of time. The worker is kept alive by holding a |KeepAliveToken|
 // reference.
--- a/dom/serviceworkers/moz.build
+++ b/dom/serviceworkers/moz.build
@@ -4,16 +4,20 @@
 # 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/.
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "DOM: Service Workers")
 
 # Public stuff.
 EXPORTS.mozilla.dom += [
+    'FetchEventOpChild.h',
+    'FetchEventOpParent.h',
+    'FetchEventOpProxyChild.h',
+    'FetchEventOpProxyParent.h',
     'ServiceWorker.h',
     'ServiceWorkerActors.h',
     'ServiceWorkerCloneData.h',
     'ServiceWorkerContainer.h',
     'ServiceWorkerDescriptor.h',
     'ServiceWorkerEvents.h',
     'ServiceWorkerInfo.h',
     'ServiceWorkerInterceptController.h',
@@ -25,16 +29,20 @@ EXPORTS.mozilla.dom += [
     'ServiceWorkerRegistrar.h',
     'ServiceWorkerRegistration.h',
     'ServiceWorkerRegistrationDescriptor.h',
     'ServiceWorkerRegistrationInfo.h',
     'ServiceWorkerUtils.h',
 ]
 
 UNIFIED_SOURCES += [
+    'FetchEventOpChild.cpp',
+    'FetchEventOpParent.cpp',
+    'FetchEventOpProxyChild.cpp',
+    'FetchEventOpProxyParent.cpp',
     'RemoteServiceWorkerContainerImpl.cpp',
     'RemoteServiceWorkerImpl.cpp',
     'RemoteServiceWorkerRegistrationImpl.cpp',
     'ServiceWorker.cpp',
     'ServiceWorkerActors.cpp',
     'ServiceWorkerChild.cpp',
     'ServiceWorkerCloneData.cpp',
     'ServiceWorkerContainer.cpp',
@@ -73,16 +81,18 @@ UNIFIED_SOURCES += [
     'ServiceWorkerUpdaterChild.cpp',
     'ServiceWorkerUpdaterParent.cpp',
     'ServiceWorkerUtils.cpp',
 ]
 
 IPDL_SOURCES += [
     'IPCServiceWorkerDescriptor.ipdlh',
     'IPCServiceWorkerRegistrationDescriptor.ipdlh',
+    'PFetchEventOp.ipdl',
+    'PFetchEventOpProxy.ipdl',
     'PServiceWorker.ipdl',
     'PServiceWorkerContainer.ipdl',
     'PServiceWorkerManager.ipdl',
     'PServiceWorkerRegistration.ipdl',
     'PServiceWorkerUpdater.ipdl',
     'ServiceWorkerOpArgs.ipdlh',
     'ServiceWorkerRegistrarTypes.ipdlh',
 ]
--- a/dom/workers/remoteworkers/PRemoteWorker.ipdl
+++ b/dom/workers/remoteworkers/PRemoteWorker.ipdl
@@ -1,15 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PBackground;
+include protocol PFetchEventOpProxy;
 
 include DOMTypes;
+include ServiceWorkerOpArgs;
 include RemoteWorkerTypes;
 
 namespace mozilla {
 namespace dom {
 
 struct RemoteWorkerSuspendOp
 {};
 
@@ -53,23 +55,27 @@ union RemoteWorkerOp {
 
 // This protocol is used to make a remote worker controllable from the parent
 // process. The parent process will receive operations from the
 // PRemoteWorkerController protocol.
 protocol PRemoteWorker
 {
   manager PBackground;
 
+  manages PFetchEventOpProxy;
+
 parent:
   async Created(bool aStatus);
 
   async Error(ErrorValue aValue);
 
   async Close();
 
 child:
+  async PFetchEventOpProxy(ServiceWorkerFetchEventOpArgs aArgs);
+
   async __delete__();
 
   async ExecOp(RemoteWorkerOp op);
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/workers/remoteworkers/PRemoteWorkerController.ipdl
+++ b/dom/workers/remoteworkers/PRemoteWorkerController.ipdl
@@ -1,33 +1,38 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PBackground;
+include protocol PFetchEventOp;
 
 include RemoteWorkerTypes;
 include ServiceWorkerOpArgs;
 
 namespace mozilla {
 namespace dom {
 
 protocol PRemoteWorkerController {
   manager PBackground;
 
+  manages PFetchEventOp;
+
  child:
   async CreationFailed();
 
   async CreationSucceeded();
 
   async ErrorReceived(ErrorValue aError);
 
   async Terminated();
 
  parent:
+  async PFetchEventOp(ServiceWorkerFetchEventOpArgs aArgs);
+
   async __delete__();
 
   async Shutdown() returns (bool aOk);
 
 };
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/workers/remoteworkers/RemoteWorkerChild.cpp
+++ b/dom/workers/remoteworkers/RemoteWorkerChild.cpp
@@ -23,16 +23,17 @@
 #include "RemoteWorkerService.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/Services.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Unused.h"
+#include "mozilla/dom/FetchEventOpProxyChild.h"
 #include "mozilla/dom/IndexedDatabaseManager.h"
 #include "mozilla/dom/MessagePort.h"
 #include "mozilla/dom/RemoteWorkerTypes.h"
 #include "mozilla/dom/ServiceWorkerDescriptor.h"
 #include "mozilla/dom/ServiceWorkerInterceptController.h"
 #include "mozilla/dom/ServiceWorkerRegistrationDescriptor.h"
 #include "mozilla/dom/ServiceWorkerUtils.h"
 #include "mozilla/dom/workerinternals/ScriptLoader.h"
@@ -917,10 +918,40 @@ void RemoteWorkerChild::MaybeStartOp(Ref
 IPCResult RemoteWorkerChild::RecvExecOp(RemoteWorkerOp&& aOp) {
   MOZ_ASSERT(!mIsServiceWorker);
 
   MaybeStartOp(new SharedWorkerOp(std::move(aOp)));
 
   return IPC_OK();
 }
 
+/**
+ * PFetchEventOpProxy methods
+ */
+PFetchEventOpProxyChild* RemoteWorkerChild::AllocPFetchEventOpProxyChild(
+    const ServiceWorkerFetchEventOpArgs& aArgs) {
+  RefPtr<FetchEventOpProxyChild> actor = new FetchEventOpProxyChild();
+
+  return actor.forget().take();
+}
+
+IPCResult RemoteWorkerChild::RecvPFetchEventOpProxyConstructor(
+    PFetchEventOpProxyChild* aActor,
+    const ServiceWorkerFetchEventOpArgs& aArgs) {
+  MOZ_ASSERT(aActor);
+
+  (static_cast<FetchEventOpProxyChild*>(aActor))->Initialize(aArgs);
+
+  return IPC_OK();
+}
+
+bool RemoteWorkerChild::DeallocPFetchEventOpProxyChild(
+    PFetchEventOpProxyChild* aActor) {
+  MOZ_ASSERT(aActor);
+
+  RefPtr<FetchEventOpProxyChild> actor =
+      dont_AddRef(static_cast<FetchEventOpProxyChild*>(aActor));
+
+  return true;
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/workers/remoteworkers/RemoteWorkerChild.h
+++ b/dom/workers/remoteworkers/RemoteWorkerChild.h
@@ -96,16 +96,25 @@ class RemoteWorkerChild final
 
     virtual void Cancel() = 0;
   };
 
   void ActorDestroy(ActorDestroyReason) override;
 
   mozilla::ipc::IPCResult RecvExecOp(RemoteWorkerOp&& aOp);
 
+  PFetchEventOpProxyChild* AllocPFetchEventOpProxyChild(
+      const ServiceWorkerFetchEventOpArgs& aArgs);
+
+  mozilla::ipc::IPCResult RecvPFetchEventOpProxyConstructor(
+      PFetchEventOpProxyChild* aActor,
+      const ServiceWorkerFetchEventOpArgs& aArgs) override;
+
+  bool DeallocPFetchEventOpProxyChild(PFetchEventOpProxyChild* aActor);
+
   nsresult ExecWorkerOnMainThread(RemoteWorkerData&& aData);
 
   void InitializeOnWorker(already_AddRefed<WorkerPrivate> aWorkerPrivate);
 
   void ShutdownOnWorker();
 
   void CreationSucceededOnAnyThread();
 
--- a/dom/workers/remoteworkers/RemoteWorkerControllerChild.cpp
+++ b/dom/workers/remoteworkers/RemoteWorkerControllerChild.cpp
@@ -10,30 +10,46 @@
 
 #include "MainThreadUtils.h"
 #include "nsError.h"
 #include "nsThreadUtils.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/Unused.h"
+#include "mozilla/dom/PFetchEventOpChild.h"
 
 namespace mozilla {
 
 using ipc::IPCResult;
 
 namespace dom {
 
 RemoteWorkerControllerChild::RemoteWorkerControllerChild(
     RefPtr<RemoteWorkerObserver> aObserver)
     : mObserver(std::move(aObserver)) {
   AssertIsOnMainThread();
   MOZ_ASSERT(mObserver);
 }
 
+PFetchEventOpChild* RemoteWorkerControllerChild::AllocPFetchEventOpChild(
+    const ServiceWorkerFetchEventOpArgs& aArgs) {
+  MOZ_CRASH("PFetchEventOpChild actors must be manually constructed!");
+  return nullptr;
+}
+
+bool RemoteWorkerControllerChild::DeallocPFetchEventOpChild(
+    PFetchEventOpChild* aActor) {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aActor);
+
+  delete aActor;
+  return true;
+}
+
 void RemoteWorkerControllerChild::ActorDestroy(ActorDestroyReason aReason) {
   AssertIsOnMainThread();
 
   mIPCActive = false;
 
   if (NS_WARN_IF(mObserver)) {
     mObserver->ErrorReceived(NS_ERROR_DOM_ABORT_ERR);
   }
--- a/dom/workers/remoteworkers/RemoteWorkerControllerChild.h
+++ b/dom/workers/remoteworkers/RemoteWorkerControllerChild.h
@@ -28,16 +28,21 @@ class RemoteWorkerControllerChild final 
 
   void RevokeObserver(RemoteWorkerObserver* aObserver);
 
   void MaybeSendDelete();
 
  private:
   ~RemoteWorkerControllerChild() = default;
 
+  PFetchEventOpChild* AllocPFetchEventOpChild(
+      const ServiceWorkerFetchEventOpArgs& aArgs);
+
+  bool DeallocPFetchEventOpChild(PFetchEventOpChild* aActor);
+
   void ActorDestroy(ActorDestroyReason aReason) override;
 
   mozilla::ipc::IPCResult RecvCreationFailed();
 
   mozilla::ipc::IPCResult RecvCreationSucceeded();
 
   mozilla::ipc::IPCResult RecvErrorReceived(const ErrorValue& aError);
 
--- a/dom/workers/remoteworkers/RemoteWorkerControllerParent.cpp
+++ b/dom/workers/remoteworkers/RemoteWorkerControllerParent.cpp
@@ -10,16 +10,17 @@
 
 #include "nsCOMPtr.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsThreadUtils.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Unused.h"
+#include "mozilla/dom/FetchEventOpParent.h"
 #include "mozilla/ipc/BackgroundParent.h"
 
 namespace mozilla {
 
 using namespace ipc;
 
 namespace dom {
 
@@ -41,16 +42,44 @@ RefPtr<RemoteWorkerParent> RemoteWorkerC
 }
 
 RemoteWorkerControllerParent::~RemoteWorkerControllerParent() {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mIPCActive);
   MOZ_ASSERT(!mRemoteWorkerController);
 }
 
+PFetchEventOpParent* RemoteWorkerControllerParent::AllocPFetchEventOpParent(
+    const ServiceWorkerFetchEventOpArgs& aArgs) {
+  AssertIsOnBackgroundThread();
+
+  RefPtr<FetchEventOpParent> actor = new FetchEventOpParent();
+  return actor.forget().take();
+}
+
+IPCResult RemoteWorkerControllerParent::RecvPFetchEventOpConstructor(
+    PFetchEventOpParent* aActor, const ServiceWorkerFetchEventOpArgs& aArgs) {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  (static_cast<FetchEventOpParent*>(aActor))->Initialize(aArgs);
+
+  return IPC_OK();
+}
+
+bool RemoteWorkerControllerParent::DeallocPFetchEventOpParent(
+    PFetchEventOpParent* aActor) {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  RefPtr<FetchEventOpParent> actor =
+      dont_AddRef(static_cast<FetchEventOpParent*>(aActor));
+  return true;
+}
+
 IPCResult RemoteWorkerControllerParent::RecvShutdown(
     ShutdownResolver&& aResolve) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mIPCActive);
   MOZ_ASSERT(mRemoteWorkerController);
 
   mIPCActive = false;
 
--- a/dom/workers/remoteworkers/RemoteWorkerControllerParent.h
+++ b/dom/workers/remoteworkers/RemoteWorkerControllerParent.h
@@ -29,16 +29,25 @@ class RemoteWorkerControllerParent final
       const RemoteWorkerData& aRemoteWorkerData);
 
   // Returns the corresponding RemoteWorkerParent (if any).
   RefPtr<RemoteWorkerParent> GetRemoteWorkerParent() const;
 
  private:
   ~RemoteWorkerControllerParent();
 
+  PFetchEventOpParent* AllocPFetchEventOpParent(
+      const ServiceWorkerFetchEventOpArgs& aArgs);
+
+  mozilla::ipc::IPCResult RecvPFetchEventOpConstructor(
+      PFetchEventOpParent* aActor,
+      const ServiceWorkerFetchEventOpArgs& aArgs) override;
+
+  bool DeallocPFetchEventOpParent(PFetchEventOpParent* aActor);
+
   mozilla::ipc::IPCResult RecvShutdown(ShutdownResolver&& aResolve);
 
   mozilla::ipc::IPCResult Recv__delete__() override;
 
   void ActorDestroy(ActorDestroyReason aReason) override;
 
   void CreationFailed() override;
 
--- a/dom/workers/remoteworkers/RemoteWorkerParent.cpp
+++ b/dom/workers/remoteworkers/RemoteWorkerParent.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "RemoteWorkerParent.h"
 #include "RemoteWorkerController.h"
 #include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/PFetchEventOpProxyParent.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/Unused.h"
 #include "nsProxyRelease.h"
 
 namespace mozilla {
 
 using namespace ipc;
 
@@ -62,16 +63,32 @@ void RemoteWorkerParent::Initialize() {
     nsCOMPtr<nsIEventTarget> target =
         SystemGroup::EventTargetFor(TaskCategory::Other);
 
     NS_ProxyRelease("RemoteWorkerParent::Initialize ContentParent", target,
                     parent.forget());
   }
 }
 
+PFetchEventOpProxyParent* RemoteWorkerParent::AllocPFetchEventOpProxyParent(
+    const ServiceWorkerFetchEventOpArgs& aArgs) {
+  MOZ_CRASH("PFetchEventOpProxyParent actors must be manually constructed!");
+  return nullptr;
+}
+
+bool RemoteWorkerParent::DeallocPFetchEventOpProxyParent(
+    PFetchEventOpProxyParent* aActor) {
+  MOZ_ASSERT(XRE_IsParentProcess());
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  delete aActor;
+  return true;
+}
+
 void RemoteWorkerParent::ActorDestroy(IProtocol::ActorDestroyReason) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(XRE_IsParentProcess());
 
   RefPtr<ContentParent> parent = BackgroundParent::GetContentParent(Manager());
 
   // Parent is null if the child actor runs on the parent process.
   if (parent) {
--- a/dom/workers/remoteworkers/RemoteWorkerParent.h
+++ b/dom/workers/remoteworkers/RemoteWorkerParent.h
@@ -26,16 +26,21 @@ class RemoteWorkerParent final : public 
 
   void SetController(RemoteWorkerController* aController);
 
   void MaybeSendDelete();
 
  private:
   ~RemoteWorkerParent();
 
+  PFetchEventOpProxyParent* AllocPFetchEventOpProxyParent(
+      const ServiceWorkerFetchEventOpArgs& aArgs);
+
+  bool DeallocPFetchEventOpProxyParent(PFetchEventOpProxyParent* aActor);
+
   void ActorDestroy(mozilla::ipc::IProtocol::ActorDestroyReason) override;
 
   mozilla::ipc::IPCResult RecvError(const ErrorValue& aValue);
 
   mozilla::ipc::IPCResult RecvClose();
 
   mozilla::ipc::IPCResult RecvCreated(const bool& aStatus);