dom/serviceworkers/ServiceWorkerPrivate.cpp
author Csoregi Natalia <ncsoregi@mozilla.com>
Thu, 01 Mar 2018 16:29:02 +0200
changeset 458530 a416b0a21b1395dfe7dc28577a31be57c075d9b4
parent 458470 55c94c05c57f0bb34ca131c68c79e05a97aa4b6c
child 458580 a92cc56bbaa4c96f79cc3af122a1307f00ddccb9
permissions -rw-r--r--
Backed out 7 changesets (bug 1193394) for browser-chrome failures on browser_ext_popup_background.js. CLOSED TREE Backed out changeset 9683f24ff8ec (bug 1193394) Backed out changeset 0e7140a7c841 (bug 1193394) Backed out changeset a0e26f6b2784 (bug 1193394) Backed out changeset 29e1fceaf48d (bug 1193394) Backed out changeset b8632bbbd273 (bug 1193394) Backed out changeset a54ef2d8f896 (bug 1193394) Backed out changeset 55c94c05c57f (bug 1193394)

/* -*- 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 "ServiceWorkerPrivate.h"

#include "ServiceWorkerManager.h"
#include "nsContentUtils.h"
#include "nsICacheInfoChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsINamed.h"
#include "nsINetworkInterceptController.h"
#include "nsIPushErrorReporter.h"
#include "nsISupportsImpl.h"
#include "nsITimedChannel.h"
#include "nsIUploadChannel2.h"
#include "nsNetUtil.h"
#include "nsProxyRelease.h"
#include "nsQueryObject.h"
#include "nsStreamUtils.h"
#include "nsStringStream.h"
#include "mozilla/Assertions.h"
#include "mozilla/dom/Client.h"
#include "mozilla/dom/ClientIPCTypes.h"
#include "mozilla/dom/DOMPrefs.h"
#include "mozilla/dom/FetchUtil.h"
#include "mozilla/dom/IndexedDatabaseManager.h"
#include "mozilla/dom/InternalHeaders.h"
#include "mozilla/dom/NotificationEvent.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/PushEventBinding.h"
#include "mozilla/dom/RequestBinding.h"
#include "mozilla/dom/WorkerDebugger.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/Unused.h"

using namespace mozilla;
using namespace mozilla::dom;

namespace mozilla {
namespace dom {

using mozilla::ipc::PrincipalInfo;

NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(ServiceWorkerPrivate)
NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(ServiceWorkerPrivate)
NS_IMPL_CYCLE_COLLECTION(ServiceWorkerPrivate, mSupportsArray)

NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ServiceWorkerPrivate, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ServiceWorkerPrivate, Release)

// 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

  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;
};

NS_IMPL_ISUPPORTS0(KeepAliveToken)

ServiceWorkerPrivate::ServiceWorkerPrivate(ServiceWorkerInfo* aInfo)
  : mInfo(aInfo)
  , mDebuggerCount(0)
  , mTokenCount(0)
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aInfo);

  mIdleWorkerTimer = NS_NewTimer();
  MOZ_ASSERT(mIdleWorkerTimer);
}

ServiceWorkerPrivate::~ServiceWorkerPrivate()
{
  MOZ_ASSERT(!mWorkerPrivate);
  MOZ_ASSERT(!mTokenCount);
  MOZ_ASSERT(!mInfo);
  MOZ_ASSERT(mSupportsArray.IsEmpty());

  mIdleWorkerTimer->Cancel();
}

namespace {

class CheckScriptEvaluationWithCallback final : public WorkerRunnable
{
  nsMainThreadPtrHandle<ServiceWorkerPrivate> mServiceWorkerPrivate;
  nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;

  // The script evaluation result must be reported even if the runnable
  // is cancelled.
  RefPtr<LifeCycleEventCallback> mScriptEvaluationCallback;

#ifdef DEBUG
  bool mDone;
#endif

public:
  CheckScriptEvaluationWithCallback(WorkerPrivate* aWorkerPrivate,
                                    ServiceWorkerPrivate* aServiceWorkerPrivate,
                                    KeepAliveToken* aKeepAliveToken,
                                    LifeCycleEventCallback* aScriptEvaluationCallback)
    : WorkerRunnable(aWorkerPrivate)
    , mServiceWorkerPrivate(new nsMainThreadPtrHolder<ServiceWorkerPrivate>(
        "CheckScriptEvaluationWithCallback::mServiceWorkerPrivate", aServiceWorkerPrivate))
    , mKeepAliveToken(new nsMainThreadPtrHolder<KeepAliveToken>(
        "CheckScriptEvaluationWithCallback::mKeepAliveToken", aKeepAliveToken))
    , mScriptEvaluationCallback(aScriptEvaluationCallback)
#ifdef DEBUG
    , mDone(false)
#endif
  {
    MOZ_ASSERT(NS_IsMainThread());
  }

  ~CheckScriptEvaluationWithCallback()
  {
    MOZ_ASSERT(mDone);
  }

  bool
  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
  {
    aWorkerPrivate->AssertIsOnWorkerThread();

    bool fetchHandlerWasAdded = aWorkerPrivate->FetchHandlerWasAdded();
    nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<bool>(
      "dom::CheckScriptEvaluationWithCallback::ReportFetchFlag",
      this,
      &CheckScriptEvaluationWithCallback::ReportFetchFlag,
      fetchHandlerWasAdded);
    aWorkerPrivate->DispatchToMainThread(runnable.forget());

    ReportScriptEvaluationResult(aWorkerPrivate->WorkerScriptExecutedSuccessfully());

    return true;
  }

  void
  ReportFetchFlag(bool aFetchHandlerWasAdded)
  {
    MOZ_ASSERT(NS_IsMainThread());
    mServiceWorkerPrivate->SetHandlesFetch(aFetchHandlerWasAdded);
  }

  nsresult
  Cancel() override
  {
    ReportScriptEvaluationResult(false);
    return WorkerRunnable::Cancel();
  }

private:
  void
  ReportScriptEvaluationResult(bool aScriptEvaluationResult)
  {
#ifdef DEBUG
    mDone = true;
#endif
    mScriptEvaluationCallback->SetResult(aScriptEvaluationResult);
    MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(mScriptEvaluationCallback));
  }
};

} // anonymous namespace

nsresult
ServiceWorkerPrivate::CheckScriptEvaluation(LifeCycleEventCallback* aScriptEvaluationCallback)
{
  nsresult rv = SpawnWorkerIfNeeded(LifeCycleEvent, nullptr);
  NS_ENSURE_SUCCESS(rv, rv);

  RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
  RefPtr<WorkerRunnable> r = new CheckScriptEvaluationWithCallback(mWorkerPrivate,
                                                                   this, token,
                                                                   aScriptEvaluationCallback);
  if (NS_WARN_IF(!r->Dispatch())) {
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

namespace {

enum ExtendableEventResult {
    Rejected = 0,
    Resolved
};

class ExtendableEventCallback {
public:
  virtual void
  FinishedWithResult(ExtendableEventResult aResult) = 0;

  NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
};

class KeepAliveHandler final : public WorkerHolder
                             , public ExtendableEvent::ExtensionsHandler
                             , public PromiseNativeHandler
{
  // This class manages lifetime extensions added by calling WaitUntil()
  // or RespondWith(). We allow new extensions as long as we still hold
  // |mKeepAliveToken|. Once the last promise was settled, we queue a microtask
  // which releases the token and prevents further extensions. By doing this,
  // we give other pending microtasks a chance to continue adding extensions.

  nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;
  WorkerPrivate* MOZ_NON_OWNING_REF mWorkerPrivate;
  bool mWorkerHolderAdded;

  // We start holding a self reference when the first extension promise is
  // added. As far as I can tell, the only case where this is useful is when
  // we're waiting indefinitely on a promise that's no longer reachable
  // and will never be settled.
  // The cycle is broken when the last promise was settled or when the
  // worker is shutting down.
  RefPtr<KeepAliveHandler> mSelfRef;

  // Called when the last promise was settled.
  RefPtr<ExtendableEventCallback> mCallback;

  uint32_t mPendingPromisesCount;

  // We don't actually care what values the promises resolve to, only whether
  // any of them were rejected.
  bool mRejected;

public:
  NS_DECL_ISUPPORTS

  explicit KeepAliveHandler(const nsMainThreadPtrHandle<KeepAliveToken>& aKeepAliveToken,
                            ExtendableEventCallback* aCallback)
    : WorkerHolder("KeepAliveHolder")
    , mKeepAliveToken(aKeepAliveToken)
    , mWorkerPrivate(GetCurrentThreadWorkerPrivate())
    , mWorkerHolderAdded(false)
    , mCallback(aCallback)
    , mPendingPromisesCount(0)
    , mRejected(false)
  {
    MOZ_ASSERT(mKeepAliveToken);
    MOZ_ASSERT(mWorkerPrivate);
  }

  bool
  UseWorkerHolder()
  {
    MOZ_ASSERT(mWorkerPrivate);
    mWorkerPrivate->AssertIsOnWorkerThread();
    MOZ_ASSERT(!mWorkerHolderAdded);
    mWorkerHolderAdded = HoldWorker(mWorkerPrivate, Terminating);
    return mWorkerHolderAdded;
  }

  bool
  WaitOnPromise(Promise& aPromise) override
  {
    if (!mKeepAliveToken) {
      MOZ_ASSERT(!mSelfRef, "We shouldn't be holding a self reference!");
      return false;
    }
    if (!mSelfRef) {
      MOZ_ASSERT(!mPendingPromisesCount);
      mSelfRef = this;
    }

    ++mPendingPromisesCount;
    aPromise.AppendNativeHandler(this);

    return true;
  }

  void
  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
  {
    RemovePromise(Resolved);
  }

  void
  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
  {
    RemovePromise(Rejected);
  }

  bool
  Notify(WorkerStatus aStatus) override
  {
    MOZ_ASSERT(mWorkerPrivate);
    mWorkerPrivate->AssertIsOnWorkerThread();
    if (aStatus < Terminating) {
      return true;
    }

    MaybeCleanup();
    return true;
  }

  void
  MaybeDone()
  {
    MOZ_ASSERT(mWorkerPrivate);
    mWorkerPrivate->AssertIsOnWorkerThread();

    if (mPendingPromisesCount) {
      return;
    }
    if (mCallback) {
      mCallback->FinishedWithResult(mRejected ? Rejected : Resolved);
    }

    MaybeCleanup();
  }

private:
  ~KeepAliveHandler()
  {
    MaybeCleanup();
  }

  void
  MaybeCleanup()
  {
    MOZ_ASSERT(mWorkerPrivate);
    mWorkerPrivate->AssertIsOnWorkerThread();
    if (!mKeepAliveToken) {
      return;
    }
    if (mWorkerHolderAdded) {
      ReleaseWorker();
    }

    mKeepAliveToken = nullptr;
    mSelfRef = nullptr;
  }

  void
  RemovePromise(ExtendableEventResult aResult)
  {
    MOZ_ASSERT(mWorkerPrivate);
    mWorkerPrivate->AssertIsOnWorkerThread();
    MOZ_DIAGNOSTIC_ASSERT(mPendingPromisesCount > 0);

    // Note: mSelfRef and mKeepAliveToken can be nullptr here
    //       if MaybeCleanup() was called just before a promise
    //       settled.  This can happen, for example, if the
    //       worker thread is being terminated for running too
    //       long, browser shutdown, etc.

    mRejected |= (aResult == Rejected);

    --mPendingPromisesCount;
    if (mPendingPromisesCount) {
      return;
    }

    CycleCollectedJSContext* cx = CycleCollectedJSContext::Get();
    MOZ_ASSERT(cx);

    RefPtr<nsIRunnable> r =
      NewRunnableMethod("dom::KeepAliveHandler::MaybeDone",
                        this,
                        &KeepAliveHandler::MaybeDone);
    cx->DispatchToMicroTask(r.forget());
  }
};

NS_IMPL_ISUPPORTS0(KeepAliveHandler)

class RegistrationUpdateRunnable : public Runnable
{
  nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
  const bool mNeedTimeCheck;

public:
  RegistrationUpdateRunnable(
    nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
    bool aNeedTimeCheck)
    : Runnable("dom::RegistrationUpdateRunnable")
    , mRegistration(aRegistration)
    , mNeedTimeCheck(aNeedTimeCheck)
  {
    MOZ_DIAGNOSTIC_ASSERT(mRegistration);
  }

  NS_IMETHOD
  Run() override
  {
    if (mNeedTimeCheck) {
      mRegistration->MaybeScheduleTimeCheckAndUpdate();
    } else {
      mRegistration->MaybeScheduleUpdate();
    }
    return NS_OK;
  }
};

class ExtendableEventWorkerRunnable : public WorkerRunnable
{
protected:
  nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;

public:
  ExtendableEventWorkerRunnable(WorkerPrivate* aWorkerPrivate,
                                KeepAliveToken* aKeepAliveToken)
    : WorkerRunnable(aWorkerPrivate)
  {
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(aWorkerPrivate);
    MOZ_ASSERT(aKeepAliveToken);

    mKeepAliveToken =
      new nsMainThreadPtrHolder<KeepAliveToken>(
        "ExtendableEventWorkerRunnable::mKeepAliveToken", aKeepAliveToken);
  }

  nsresult
  DispatchExtendableEventOnWorkerScope(JSContext* aCx,
                                       WorkerGlobalScope* aWorkerScope,
                                       ExtendableEvent* aEvent,
                                       ExtendableEventCallback* aCallback)
  {
    MOZ_ASSERT(aWorkerScope);
    MOZ_ASSERT(aEvent);
    nsCOMPtr<nsIGlobalObject> sgo = aWorkerScope;
    WidgetEvent* internalEvent = aEvent->WidgetEventPtr();

    RefPtr<KeepAliveHandler> keepAliveHandler =
      new KeepAliveHandler(mKeepAliveToken, aCallback);
    if (NS_WARN_IF(!keepAliveHandler->UseWorkerHolder())) {
      return NS_ERROR_FAILURE;
    }

    // This must always be set *before* dispatching the event, otherwise
    // waitUntil calls will fail.
    aEvent->SetKeepAliveHandler(keepAliveHandler);

    ErrorResult result;
    bool dummy;
    result = aWorkerScope->DispatchEvent(aEvent, &dummy);
    if (NS_WARN_IF(result.Failed())) {
      result.SuppressException();
      return NS_ERROR_FAILURE;
    }

    // [[ If e’s extend lifetime promises is empty, unset e’s extensions allowed
    //    flag and abort these steps. ]]
    keepAliveHandler->MaybeDone();

    // We don't block the event when getting an exception but still report the
    // error message.
    // Report exception message. Note: This will not stop the event.
    if (internalEvent->mFlags.mExceptionWasRaised) {
      result.SuppressException();
      return NS_ERROR_XPC_JS_THREW_EXCEPTION;
    }

    return NS_OK;
  }
};

class SendMessageEventRunnable final : public ExtendableEventWorkerRunnable
                                     , public StructuredCloneHolder
{
  const ClientInfoAndState mClientInfoAndState;

public:
  SendMessageEventRunnable(WorkerPrivate*  aWorkerPrivate,
                           KeepAliveToken* aKeepAliveToken,
                           const ClientInfoAndState& aClientInfoAndState)
    : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
    , StructuredCloneHolder(CloningSupported, TransferringSupported,
                            StructuredCloneScope::SameProcessDifferentThread)
    , mClientInfoAndState(aClientInfoAndState)
  {
    MOZ_ASSERT(NS_IsMainThread());
  }

  bool
  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
  {
    JS::Rooted<JS::Value> messageData(aCx);
    nsCOMPtr<nsIGlobalObject> sgo = aWorkerPrivate->GlobalScope();
    ErrorResult rv;
    Read(sgo, aCx, &messageData, rv);
    if (NS_WARN_IF(rv.Failed())) {
      return true;
    }

    Sequence<OwningNonNull<MessagePort>> ports;
    if (!TakeTransferredPortsAsSequence(ports)) {
      return true;
    }

    RootedDictionary<ExtendableMessageEventInit> init(aCx);

    init.mBubbles = false;
    init.mCancelable = false;

    init.mData = messageData;
    init.mPorts = ports;
    init.mSource.SetValue().SetAsClient() =
      new Client(sgo, mClientInfoAndState);

    RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
    RefPtr<ExtendableMessageEvent> extendableEvent =
      ExtendableMessageEvent::Constructor(target, NS_LITERAL_STRING("message"),
                                          init, rv);
    if (NS_WARN_IF(rv.Failed())) {
      rv.SuppressException();
      return false;
    }

    extendableEvent->SetTrusted(true);

    return NS_SUCCEEDED(DispatchExtendableEventOnWorkerScope(aCx,
                                                             aWorkerPrivate->GlobalScope(),
                                                             extendableEvent,
                                                             nullptr));
  }
};

} // anonymous namespace

nsresult
ServiceWorkerPrivate::SendMessageEvent(JSContext* aCx,
                                       JS::Handle<JS::Value> aMessage,
                                       const Sequence<JSObject*>& aTransferable,
                                       const ClientInfoAndState& aClientInfoAndState)
{
  MOZ_ASSERT(NS_IsMainThread());

  ErrorResult rv(SpawnWorkerIfNeeded(MessageEvent, nullptr));
  if (NS_WARN_IF(rv.Failed())) {
    return rv.StealNSResult();
  }

  JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedHandleValue);

  rv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
                                                         &transferable);
  if (NS_WARN_IF(rv.Failed())) {
    return rv.StealNSResult();
  }

  RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
  RefPtr<SendMessageEventRunnable> runnable =
    new SendMessageEventRunnable(mWorkerPrivate, token, aClientInfoAndState);

  runnable->Write(aCx, aMessage, transferable, JS::CloneDataPolicy(), rv);
  if (NS_WARN_IF(rv.Failed())) {
    return rv.StealNSResult();
  }

  if (!runnable->Dispatch()) {
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

namespace {

// Handle functional event
// 9.9.7 If 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.
class ExtendableFunctionalEventWorkerRunnable : public ExtendableEventWorkerRunnable
{
protected:
  nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
public:
  ExtendableFunctionalEventWorkerRunnable(WorkerPrivate* aWorkerPrivate,
                                          KeepAliveToken* aKeepAliveToken,
                                          nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
    : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
    , mRegistration(aRegistration)
  {
    MOZ_DIAGNOSTIC_ASSERT(aRegistration);
  }

  void
  PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) override
  {
    // Sub-class PreRun() or WorkerRun() methods could clear our mRegistration.
    if (mRegistration) {
      nsCOMPtr<nsIRunnable> runnable =
        new RegistrationUpdateRunnable(mRegistration, true /* time check */);
      aWorkerPrivate->DispatchToMainThread(runnable.forget());
    }

    ExtendableEventWorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
  }
};

/*
 * Fires 'install' event on the ServiceWorkerGlobalScope. Modifies busy count
 * since it fires the event. This is ok since there can't be nested
 * ServiceWorkers, so the parent thread -> worker thread requirement for
 * runnables is satisfied.
 */
class LifecycleEventWorkerRunnable : public ExtendableEventWorkerRunnable
{
  nsString mEventName;
  RefPtr<LifeCycleEventCallback> mCallback;

public:
  LifecycleEventWorkerRunnable(WorkerPrivate* aWorkerPrivate,
                               KeepAliveToken* aToken,
                               const nsAString& aEventName,
                               LifeCycleEventCallback* aCallback)
      : ExtendableEventWorkerRunnable(aWorkerPrivate, aToken)
      , mEventName(aEventName)
      , mCallback(aCallback)
  {
    MOZ_ASSERT(NS_IsMainThread());
  }

  bool
  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
  {
    MOZ_ASSERT(aWorkerPrivate);
    return DispatchLifecycleEvent(aCx, aWorkerPrivate);
  }

  nsresult
  Cancel() override
  {
    mCallback->SetResult(false);
    MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(mCallback));

    return WorkerRunnable::Cancel();
  }

private:
  bool
  DispatchLifecycleEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate);

};

/*
 * Used to handle ExtendableEvent::waitUntil() and catch abnormal worker
 * termination during the execution of life cycle events. It is responsible
 * with advancing the job queue for install/activate tasks.
 */
class LifeCycleEventWatcher final : public ExtendableEventCallback,
                                    public WorkerHolder
{
  WorkerPrivate* mWorkerPrivate;
  RefPtr<LifeCycleEventCallback> mCallback;
  bool mDone;

  ~LifeCycleEventWatcher()
  {
    if (mDone) {
      return;
    }

    MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate);
    // XXXcatalinb: If all the promises passed to waitUntil go out of scope,
    // the resulting Promise.all will be cycle collected and it will drop its
    // native handlers (including this object). Instead of waiting for a timeout
    // we report the failure now.
    ReportResult(false);
  }

public:
  NS_INLINE_DECL_REFCOUNTING(LifeCycleEventWatcher, override)

  LifeCycleEventWatcher(WorkerPrivate* aWorkerPrivate,
                        LifeCycleEventCallback* aCallback)
    : WorkerHolder("LifeCycleEventWatcher")
    , mWorkerPrivate(aWorkerPrivate)
    , mCallback(aCallback)
    , mDone(false)
  {
    MOZ_ASSERT(aWorkerPrivate);
    aWorkerPrivate->AssertIsOnWorkerThread();
  }

  bool
  Init()
  {
    MOZ_ASSERT(mWorkerPrivate);
    mWorkerPrivate->AssertIsOnWorkerThread();

    // We need to listen for worker termination in case the event handler
    // never completes or never resolves the waitUntil promise. There are
    // two possible scenarios:
    // 1. The keepAlive token expires and the worker is terminated, in which
    //    case the registration/update promise will be rejected
    // 2. A new service worker is registered which will terminate the current
    //    installing worker.
    if (NS_WARN_IF(!HoldWorker(mWorkerPrivate, Terminating))) {
      NS_WARNING("LifeCycleEventWatcher failed to add feature.");
      ReportResult(false);
      return false;
    }

    return true;
  }

  bool
  Notify(WorkerStatus aStatus) override
  {
    if (aStatus < Terminating) {
      return true;
    }

    MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate);
    ReportResult(false);

    return true;
  }

  void
  ReportResult(bool aResult)
  {
    mWorkerPrivate->AssertIsOnWorkerThread();

    if (mDone) {
      return;
    }
    mDone = true;

    mCallback->SetResult(aResult);
    nsresult rv = mWorkerPrivate->DispatchToMainThread(mCallback);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      MOZ_CRASH("Failed to dispatch life cycle event handler.");
    }

    ReleaseWorker();
  }

  void
  FinishedWithResult(ExtendableEventResult aResult) override
  {
    MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate);
    mWorkerPrivate->AssertIsOnWorkerThread();
    ReportResult(aResult == Resolved);

    // Note, all WaitUntil() rejections are reported to client consoles
    // by the WaitUntilHandler in ServiceWorkerEvents.  This ensures that
    // errors in non-lifecycle events like FetchEvent and PushEvent are
    // reported properly.
  }
};

bool
LifecycleEventWorkerRunnable::DispatchLifecycleEvent(JSContext* aCx,
                                                     WorkerPrivate* aWorkerPrivate)
{
  aWorkerPrivate->AssertIsOnWorkerThread();
  MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());

  RefPtr<ExtendableEvent> event;
  RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();

  if (mEventName.EqualsASCII("install") || mEventName.EqualsASCII("activate")) {
    ExtendableEventInit init;
    init.mBubbles = false;
    init.mCancelable = false;
    event = ExtendableEvent::Constructor(target, mEventName, init);
  } else {
    MOZ_CRASH("Unexpected lifecycle event");
  }

  event->SetTrusted(true);

  // It is important to initialize the watcher before actually dispatching
  // the event in order to catch worker termination while the event handler
  // is still executing. This can happen with infinite loops, for example.
  RefPtr<LifeCycleEventWatcher> watcher =
    new LifeCycleEventWatcher(aWorkerPrivate, mCallback);

  if (!watcher->Init()) {
    return true;
  }

  nsresult rv = DispatchExtendableEventOnWorkerScope(aCx,
                                                     aWorkerPrivate->GlobalScope(),
                                                     event,
                                                     watcher);
  // Do not fail event processing when an exception is thrown.
  if (NS_FAILED(rv) && rv != NS_ERROR_XPC_JS_THREW_EXCEPTION) {
    watcher->ReportResult(false);
  }

  return true;
}

} // anonymous namespace

nsresult
ServiceWorkerPrivate::SendLifeCycleEvent(const nsAString& aEventType,
                                         LifeCycleEventCallback* aCallback,
                                         nsIRunnable* aLoadFailure)
{
  nsresult rv = SpawnWorkerIfNeeded(LifeCycleEvent, aLoadFailure);
  NS_ENSURE_SUCCESS(rv, rv);

  RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
  RefPtr<WorkerRunnable> r = new LifecycleEventWorkerRunnable(mWorkerPrivate,
                                                              token,
                                                              aEventType,
                                                              aCallback);
  if (NS_WARN_IF(!r->Dispatch())) {
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

namespace {

class PushErrorReporter final : public ExtendableEventCallback
{
  WorkerPrivate* mWorkerPrivate;
  nsString mMessageId;

  ~PushErrorReporter()
  {
  }

public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PushErrorReporter, override)

  PushErrorReporter(WorkerPrivate* aWorkerPrivate,
                    const nsAString& aMessageId)
    : mWorkerPrivate(aWorkerPrivate)
    , mMessageId(aMessageId)
  {
    mWorkerPrivate->AssertIsOnWorkerThread();
  }

  void
  FinishedWithResult(ExtendableEventResult aResult) override
  {
    if (aResult == Rejected) {
      Report(nsIPushErrorReporter::DELIVERY_UNHANDLED_REJECTION);
    }
  }

  void Report(uint16_t aReason = nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR)
  {
    WorkerPrivate* workerPrivate = mWorkerPrivate;
    mWorkerPrivate->AssertIsOnWorkerThread();

    if (NS_WARN_IF(aReason > nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR) ||
        mMessageId.IsEmpty()) {
      return;
    }
    nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<uint16_t>(
      "dom::PushErrorReporter::ReportOnMainThread",
      this,
      &PushErrorReporter::ReportOnMainThread,
      aReason);
    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
      workerPrivate->DispatchToMainThread(runnable.forget())));
  }

  void ReportOnMainThread(uint16_t aReason)
  {
    MOZ_ASSERT(NS_IsMainThread());
    nsCOMPtr<nsIPushErrorReporter> reporter =
      do_GetService("@mozilla.org/push/Service;1");
    if (reporter) {
      nsresult rv = reporter->ReportDeliveryError(mMessageId, aReason);
      Unused << NS_WARN_IF(NS_FAILED(rv));
    }
  }
};

class SendPushEventRunnable final : public ExtendableFunctionalEventWorkerRunnable
{
  nsString mMessageId;
  Maybe<nsTArray<uint8_t>> mData;

public:
  SendPushEventRunnable(WorkerPrivate* aWorkerPrivate,
                        KeepAliveToken* aKeepAliveToken,
                        const nsAString& aMessageId,
                        const Maybe<nsTArray<uint8_t>>& aData,
                        nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> aRegistration)
      : ExtendableFunctionalEventWorkerRunnable(
          aWorkerPrivate, aKeepAliveToken, aRegistration)
      , mMessageId(aMessageId)
      , mData(aData)
  {
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(aWorkerPrivate);
    MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
  }

  bool
  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
  {
    MOZ_ASSERT(aWorkerPrivate);
    GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());

    RefPtr<PushErrorReporter> errorReporter =
      new PushErrorReporter(aWorkerPrivate, mMessageId);

    PushEventInit pei;
    if (mData) {
      const nsTArray<uint8_t>& bytes = mData.ref();
      JSObject* data = Uint8Array::Create(aCx, bytes.Length(), bytes.Elements());
      if (!data) {
        errorReporter->Report();
        return false;
      }
      pei.mData.Construct().SetAsArrayBufferView().Init(data);
    }
    pei.mBubbles = false;
    pei.mCancelable = false;

    ErrorResult result;
    RefPtr<PushEvent> event =
      PushEvent::Constructor(globalObj, NS_LITERAL_STRING("push"), pei, result);
    if (NS_WARN_IF(result.Failed())) {
      result.SuppressException();
      errorReporter->Report();
      return false;
    }
    event->SetTrusted(true);

    nsresult rv = DispatchExtendableEventOnWorkerScope(aCx,
                                                       aWorkerPrivate->GlobalScope(),
                                                       event,
                                                       errorReporter);
    if (NS_FAILED(rv)) {
      // We don't cancel WorkerPrivate when catching an excetpion.
      errorReporter->Report(nsIPushErrorReporter::DELIVERY_UNCAUGHT_EXCEPTION);
    }

    return true;
  }
};

class SendPushSubscriptionChangeEventRunnable final : public ExtendableEventWorkerRunnable
{

public:
  explicit SendPushSubscriptionChangeEventRunnable(
    WorkerPrivate* aWorkerPrivate, KeepAliveToken* aKeepAliveToken)
      : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
  {
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(aWorkerPrivate);
    MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
  }

  bool
  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
  {
    MOZ_ASSERT(aWorkerPrivate);

    RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();

    ExtendableEventInit init;
    init.mBubbles = false;
    init.mCancelable = false;

    RefPtr<ExtendableEvent> event =
      ExtendableEvent::Constructor(target,
                                   NS_LITERAL_STRING("pushsubscriptionchange"),
                                   init);

    event->SetTrusted(true);

    DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(),
                                         event, nullptr);

    return true;
  }
};

} // anonymous namespace

nsresult
ServiceWorkerPrivate::SendPushEvent(const nsAString& aMessageId,
                                    const Maybe<nsTArray<uint8_t>>& aData,
                                    ServiceWorkerRegistrationInfo* aRegistration)
{
  nsresult rv = SpawnWorkerIfNeeded(PushEvent, nullptr);
  NS_ENSURE_SUCCESS(rv, rv);

  RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();

  nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
    new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(
      "ServiceWorkerRegistrationInfoProxy", aRegistration, false));

  RefPtr<WorkerRunnable> r = new SendPushEventRunnable(mWorkerPrivate,
                                                       token,
                                                       aMessageId,
                                                       aData,
                                                       regInfo);

  if (mInfo->State() == ServiceWorkerState::Activating) {
    mPendingFunctionalEvents.AppendElement(r.forget());
    return NS_OK;
  }

  MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated);

  if (NS_WARN_IF(!r->Dispatch())) {
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

nsresult
ServiceWorkerPrivate::SendPushSubscriptionChangeEvent()
{
  nsresult rv = SpawnWorkerIfNeeded(PushSubscriptionChangeEvent, nullptr);
  NS_ENSURE_SUCCESS(rv, rv);

  RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
  RefPtr<WorkerRunnable> r =
    new SendPushSubscriptionChangeEventRunnable(mWorkerPrivate, token);
  if (NS_WARN_IF(!r->Dispatch())) {
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

namespace {

class AllowWindowInteractionHandler final : public ExtendableEventCallback
                                          , public nsITimerCallback
                                          , public nsINamed
                                          , public WorkerHolder
{
  nsCOMPtr<nsITimer> mTimer;

  ~AllowWindowInteractionHandler()
  {
    // We must either fail to initialize or call ClearWindowAllowed.
    MOZ_DIAGNOSTIC_ASSERT(!mTimer);
    MOZ_DIAGNOSTIC_ASSERT(!mWorkerPrivate);
  }

  void
  ClearWindowAllowed(WorkerPrivate* aWorkerPrivate)
  {
    MOZ_ASSERT(aWorkerPrivate);
    aWorkerPrivate->AssertIsOnWorkerThread();

    if (!mTimer) {
      return;
    }

    // XXXcatalinb: This *might* be executed after the global was unrooted, in
    // which case GlobalScope() will return null. Making the check here just
    // to be safe.
    WorkerGlobalScope* globalScope = aWorkerPrivate->GlobalScope();
    if (!globalScope) {
      return;
    }

    globalScope->ConsumeWindowInteraction();
    mTimer->Cancel();
    mTimer = nullptr;

    ReleaseWorker();
  }

  void
  StartClearWindowTimer(WorkerPrivate* aWorkerPrivate)
  {
    MOZ_ASSERT(aWorkerPrivate);
    aWorkerPrivate->AssertIsOnWorkerThread();
    MOZ_ASSERT(!mTimer);

    nsresult rv;
    nsCOMPtr<nsITimer> timer = NS_NewTimer(aWorkerPrivate->ControlEventTarget());
    if (NS_WARN_IF(!timer)) {
      return;
    }

    if (!HoldWorker(aWorkerPrivate, Closing)) {
      return;
    }

    aWorkerPrivate->GlobalScope()->AllowWindowInteraction();
    timer.swap(mTimer);

    // We swap first and then initialize the timer so that even if initializing
    // fails, we still clean the busy count and interaction count correctly.
    // The timer can't be initialized before modifying the busy count since the
    // timer thread could run and call the timeout but the worker may
    // already be terminating and modifying the busy count could fail.
    rv = mTimer->InitWithCallback(this,
                                  gDOMDisableOpenClickDelay,
                                  nsITimer::TYPE_ONE_SHOT);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      ClearWindowAllowed(aWorkerPrivate);
      return;
    }
  }

  // nsITimerCallback virtual methods
  NS_IMETHOD
  Notify(nsITimer* aTimer) override
  {
    MOZ_DIAGNOSTIC_ASSERT(mTimer == aTimer);
    ClearWindowAllowed(mWorkerPrivate);
    return NS_OK;
  }

  // nsINamed virtual methods
  NS_IMETHOD
  GetName(nsACString& aName) override
  {
    aName.AssignLiteral("AllowWindowInteractionHandler");
    return NS_OK;
  }

  // WorkerHolder virtual methods
  bool
  Notify(WorkerStatus aStatus) override
  {
    // We could try to hold the worker alive until the timer fires, but other
    // APIs are not likely to work in this partially shutdown state.  We might
    // as well let the worker thread exit.
    ClearWindowAllowed(mWorkerPrivate);
    return true;
  }

public:
  NS_DECL_THREADSAFE_ISUPPORTS

  explicit AllowWindowInteractionHandler(WorkerPrivate* aWorkerPrivate)
    : WorkerHolder("AllowWindowInteractionHandler")
  {
    StartClearWindowTimer(aWorkerPrivate);
  }

  void
  FinishedWithResult(ExtendableEventResult /* aResult */) override
  {
    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
    ClearWindowAllowed(workerPrivate);
  }
};

NS_IMPL_ISUPPORTS(AllowWindowInteractionHandler, nsITimerCallback, nsINamed)

class SendNotificationEventRunnable final : public ExtendableEventWorkerRunnable
{
  const nsString mEventName;
  const nsString mID;
  const nsString mTitle;
  const nsString mDir;
  const nsString mLang;
  const nsString mBody;
  const nsString mTag;
  const nsString mIcon;
  const nsString mData;
  const nsString mBehavior;
  const nsString mScope;

public:
  SendNotificationEventRunnable(WorkerPrivate* aWorkerPrivate,
                                KeepAliveToken* aKeepAliveToken,
                                const nsAString& aEventName,
                                const nsAString& aID,
                                const nsAString& aTitle,
                                const nsAString& aDir,
                                const nsAString& aLang,
                                const nsAString& aBody,
                                const nsAString& aTag,
                                const nsAString& aIcon,
                                const nsAString& aData,
                                const nsAString& aBehavior,
                                const nsAString& aScope)
      : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
      , mEventName(aEventName)
      , mID(aID)
      , mTitle(aTitle)
      , mDir(aDir)
      , mLang(aLang)
      , mBody(aBody)
      , mTag(aTag)
      , mIcon(aIcon)
      , mData(aData)
      , mBehavior(aBehavior)
      , mScope(aScope)
  {
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(aWorkerPrivate);
    MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
  }

  bool
  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
  {
    MOZ_ASSERT(aWorkerPrivate);

    RefPtr<EventTarget> target = do_QueryObject(aWorkerPrivate->GlobalScope());

    ErrorResult result;
    RefPtr<Notification> notification =
      Notification::ConstructFromFields(aWorkerPrivate->GlobalScope(), mID,
                                        mTitle, mDir, mLang, mBody, mTag, mIcon,
                                        mData, mScope, result);
    if (NS_WARN_IF(result.Failed())) {
      return false;
    }

    NotificationEventInit nei;
    nei.mNotification = notification;
    nei.mBubbles = false;
    nei.mCancelable = false;

    RefPtr<NotificationEvent> event =
      NotificationEvent::Constructor(target, mEventName,
                                     nei, result);
    if (NS_WARN_IF(result.Failed())) {
      return false;
    }

    event->SetTrusted(true);

    RefPtr<AllowWindowInteractionHandler> allowWindowInteraction;
    if (mEventName.EqualsLiteral(NOTIFICATION_CLICK_EVENT_NAME)) {
      allowWindowInteraction =
        new AllowWindowInteractionHandler(aWorkerPrivate);
    }

    nsresult rv = DispatchExtendableEventOnWorkerScope(aCx,
                                                       aWorkerPrivate->GlobalScope(),
                                                       event,
                                                       allowWindowInteraction);
    // Don't reject when catching an exception
    if (NS_FAILED(rv) && rv != NS_ERROR_XPC_JS_THREW_EXCEPTION &&
        allowWindowInteraction) {
      allowWindowInteraction->FinishedWithResult(Rejected);
    }

    return true;
  }
};

} // namespace anonymous

nsresult
ServiceWorkerPrivate::SendNotificationEvent(const nsAString& aEventName,
                                            const nsAString& aID,
                                            const nsAString& aTitle,
                                            const nsAString& aDir,
                                            const nsAString& aLang,
                                            const nsAString& aBody,
                                            const nsAString& aTag,
                                            const nsAString& aIcon,
                                            const nsAString& aData,
                                            const nsAString& aBehavior,
                                            const nsAString& aScope)
{
  WakeUpReason why;
  if (aEventName.EqualsLiteral(NOTIFICATION_CLICK_EVENT_NAME)) {
    why = NotificationClickEvent;
    gDOMDisableOpenClickDelay =
      Preferences::GetInt("dom.serviceWorkers.disable_open_click_delay");
  } else if (aEventName.EqualsLiteral(NOTIFICATION_CLOSE_EVENT_NAME)) {
    why = NotificationCloseEvent;
  } else {
    MOZ_ASSERT_UNREACHABLE("Invalid notification event name");
    return NS_ERROR_FAILURE;
  }

  nsresult rv = SpawnWorkerIfNeeded(why, nullptr);
  NS_ENSURE_SUCCESS(rv, rv);

  RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();

  RefPtr<WorkerRunnable> r =
    new SendNotificationEventRunnable(mWorkerPrivate, token,
                                      aEventName, aID, aTitle, aDir, aLang,
                                      aBody, aTag, aIcon, aData, aBehavior,
                                      aScope);
  if (NS_WARN_IF(!r->Dispatch())) {
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

namespace {

// Inheriting ExtendableEventWorkerRunnable so that the worker is not terminated
// while handling the fetch event, though that's very unlikely.
class FetchEventRunnable : public ExtendableFunctionalEventWorkerRunnable
                         , public nsIHttpHeaderVisitor {
  nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
  const nsCString mScriptSpec;
  nsTArray<nsCString> mHeaderNames;
  nsTArray<nsCString> mHeaderValues;
  nsCString mSpec;
  nsCString mFragment;
  nsCString mMethod;
  nsString mClientId;
  bool mIsReload;
  bool mMarkLaunchServiceWorkerEnd;
  RequestCache mCacheMode;
  RequestMode mRequestMode;
  RequestRedirect mRequestRedirect;
  RequestCredentials mRequestCredentials;
  nsContentPolicyType mContentPolicyType;
  nsCOMPtr<nsIInputStream> mUploadStream;
  int64_t mUploadStreamContentLength;
  nsCString mReferrer;
  ReferrerPolicy mReferrerPolicy;
  nsString mIntegrity;
public:
  FetchEventRunnable(WorkerPrivate* aWorkerPrivate,
                     KeepAliveToken* aKeepAliveToken,
                     nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
                     // CSP checks might require the worker script spec
                     // later on.
                     const nsACString& aScriptSpec,
                     nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
                     const nsAString& aClientId,
                     bool aIsReload,
                     bool aMarkLaunchServiceWorkerEnd)
    : ExtendableFunctionalEventWorkerRunnable(
        aWorkerPrivate, aKeepAliveToken, aRegistration)
    , mInterceptedChannel(aChannel)
    , mScriptSpec(aScriptSpec)
    , mClientId(aClientId)
    , mIsReload(aIsReload)
    , mMarkLaunchServiceWorkerEnd(aMarkLaunchServiceWorkerEnd)
    , mCacheMode(RequestCache::Default)
    , mRequestMode(RequestMode::No_cors)
    , mRequestRedirect(RequestRedirect::Follow)
    // By default we set it to same-origin since normal HTTP fetches always
    // send credentials to same-origin websites unless explicitly forbidden.
    , mRequestCredentials(RequestCredentials::Same_origin)
    , mContentPolicyType(nsIContentPolicy::TYPE_INVALID)
    , mUploadStreamContentLength(-1)
    , mReferrer(kFETCH_CLIENT_REFERRER_STR)
    , mReferrerPolicy(ReferrerPolicy::_empty)
  {
    MOZ_ASSERT(aWorkerPrivate);
  }

  NS_DECL_ISUPPORTS_INHERITED

  NS_IMETHOD
  VisitHeader(const nsACString& aHeader, const nsACString& aValue) override
  {
    mHeaderNames.AppendElement(aHeader);
    mHeaderValues.AppendElement(aValue);
    return NS_OK;
  }

  nsresult
  Init()
  {
    MOZ_ASSERT(NS_IsMainThread());
    nsCOMPtr<nsIChannel> channel;
    nsresult rv = mInterceptedChannel->GetChannel(getter_AddRefs(channel));
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsIURI> uri;
    rv = mInterceptedChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri));
    NS_ENSURE_SUCCESS(rv, rv);

    // Normally we rely on the Request constructor to strip the fragment, but
    // when creating the FetchEvent we bypass the constructor.  So strip the
    // fragment manually here instead.  We can't do it later when we create
    // the Request because that code executes off the main thread.
    nsCOMPtr<nsIURI> uriNoFragment;
    rv = uri->CloneIgnoringRef(getter_AddRefs(uriNoFragment));
    NS_ENSURE_SUCCESS(rv, rv);
    rv = uriNoFragment->GetSpec(mSpec);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = uri->GetRef(mFragment);
    NS_ENSURE_SUCCESS(rv, rv);

    uint32_t loadFlags;
    rv = channel->GetLoadFlags(&loadFlags);
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr<nsILoadInfo> loadInfo;
    rv = channel->GetLoadInfo(getter_AddRefs(loadInfo));
    NS_ENSURE_SUCCESS(rv, rv);
    NS_ENSURE_STATE(loadInfo);
    mContentPolicyType = loadInfo->InternalContentPolicyType();


    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
    MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?");

    nsAutoCString referrer;
    // Ignore the return value since the Referer header may not exist.
    Unused << httpChannel->GetRequestHeader(NS_LITERAL_CSTRING("Referer"),
                                            referrer);
    if (!referrer.IsEmpty()) {
      mReferrer = referrer;
    } else {
      // If there's no referrer Header, means the header was omitted for
      // security/privacy reason.
      mReferrer = EmptyCString();
    }

    uint32_t referrerPolicy = 0;
    rv = httpChannel->GetReferrerPolicy(&referrerPolicy);
    NS_ENSURE_SUCCESS(rv, rv);
    switch (referrerPolicy) {
      case nsIHttpChannel::REFERRER_POLICY_UNSET:
      mReferrerPolicy = ReferrerPolicy::_empty;
      break;
    case nsIHttpChannel::REFERRER_POLICY_NO_REFERRER:
      mReferrerPolicy = ReferrerPolicy::No_referrer;
      break;
    case nsIHttpChannel::REFERRER_POLICY_ORIGIN:
      mReferrerPolicy = ReferrerPolicy::Origin;
      break;
    case nsIHttpChannel::REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE:
      mReferrerPolicy = ReferrerPolicy::No_referrer_when_downgrade;
      break;
    case nsIHttpChannel::REFERRER_POLICY_ORIGIN_WHEN_XORIGIN:
      mReferrerPolicy = ReferrerPolicy::Origin_when_cross_origin;
      break;
    case nsIHttpChannel::REFERRER_POLICY_UNSAFE_URL:
      mReferrerPolicy = ReferrerPolicy::Unsafe_url;
      break;
    case nsIHttpChannel::REFERRER_POLICY_SAME_ORIGIN:
      mReferrerPolicy = ReferrerPolicy::Same_origin;
      break;
    case nsIHttpChannel::REFERRER_POLICY_STRICT_ORIGIN_WHEN_XORIGIN:
      mReferrerPolicy = ReferrerPolicy::Strict_origin_when_cross_origin;
      break;
    case nsIHttpChannel::REFERRER_POLICY_STRICT_ORIGIN:
      mReferrerPolicy = ReferrerPolicy::Strict_origin;
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("Invalid Referrer Policy enum value?");
      break;
    }

    rv = httpChannel->GetRequestMethod(mMethod);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(httpChannel);
    NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE);

    mRequestMode = InternalRequest::MapChannelToRequestMode(channel);

    // This is safe due to static_asserts in ServiceWorkerManager.cpp.
    uint32_t redirectMode;
    rv = internalChannel->GetRedirectMode(&redirectMode);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    mRequestRedirect = static_cast<RequestRedirect>(redirectMode);

    // This is safe due to static_asserts in ServiceWorkerManager.cpp.
    uint32_t cacheMode;
    rv = internalChannel->GetFetchCacheMode(&cacheMode);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    mCacheMode = static_cast<RequestCache>(cacheMode);

    rv = internalChannel->GetIntegrityMetadata(mIntegrity);
    MOZ_ASSERT(NS_SUCCEEDED(rv));

    mRequestCredentials = InternalRequest::MapChannelToRequestCredentials(channel);

    rv = httpChannel->VisitNonDefaultRequestHeaders(this);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
    if (uploadChannel) {
      MOZ_ASSERT(!mUploadStream);
      nsCOMPtr<nsIInputStream> uploadStream;
      rv = uploadChannel->CloneUploadStream(&mUploadStreamContentLength,
                                            getter_AddRefs(uploadStream));
      NS_ENSURE_SUCCESS(rv, rv);
      mUploadStream = uploadStream;
    }

    return NS_OK;
  }

  bool
  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
  {
    MOZ_ASSERT(aWorkerPrivate);

    if (mMarkLaunchServiceWorkerEnd) {
      mInterceptedChannel->SetLaunchServiceWorkerEnd(TimeStamp::Now());

      // A probe to measure sw launch time for telemetry.
      TimeStamp launchStartTime = TimeStamp();
      mInterceptedChannel->GetLaunchServiceWorkerStart(&launchStartTime);

      TimeStamp launchEndTime = TimeStamp();
      mInterceptedChannel->GetLaunchServiceWorkerEnd(&launchEndTime);
      Telemetry::AccumulateTimeDelta(Telemetry::SERVICE_WORKER_LAUNCH_TIME,
                                     launchStartTime, launchEndTime);
    }

    mInterceptedChannel->SetDispatchFetchEventEnd(TimeStamp::Now());
    return DispatchFetchEvent(aCx, aWorkerPrivate);
  }

  nsresult
  Cancel() override
  {
    nsCOMPtr<nsIRunnable> runnable = new ResumeRequest(mInterceptedChannel);
    if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable))) {
      NS_WARNING("Failed to resume channel on FetchEventRunnable::Cancel()!\n");
    }
    WorkerRunnable::Cancel();
    return NS_OK;
  }

private:
  ~FetchEventRunnable() {}

  class ResumeRequest final : public Runnable {
    nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
  public:
    explicit ResumeRequest(
      nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel)
      : Runnable("dom::FetchEventRunnable::ResumeRequest")
      , mChannel(aChannel)
    {
      mChannel->SetFinishResponseStart(TimeStamp::Now());
    }

    NS_IMETHOD Run() override
    {
      MOZ_ASSERT(NS_IsMainThread());

      TimeStamp timeStamp = TimeStamp::Now();
      mChannel->SetHandleFetchEventEnd(timeStamp);
      mChannel->SetChannelResetEnd(timeStamp);
      mChannel->SaveTimeStamps();

      nsresult rv = mChannel->ResetInterception();
      if (NS_FAILED(rv)) {
        NS_WARNING("Failed to resume intercepted network request");
        mChannel->CancelInterception(rv);
      }
      return rv;
    }
  };

  bool
  DispatchFetchEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
  {
    MOZ_ASSERT(aCx);
    MOZ_ASSERT(aWorkerPrivate);
    MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
    GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());

    RefPtr<InternalHeaders> internalHeaders = new InternalHeaders(HeadersGuardEnum::Request);
    MOZ_ASSERT(mHeaderNames.Length() == mHeaderValues.Length());
    for (uint32_t i = 0; i < mHeaderNames.Length(); i++) {
      ErrorResult result;
      internalHeaders->Set(mHeaderNames[i], mHeaderValues[i], result);
      if (NS_WARN_IF(result.Failed())) {
        result.SuppressException();
        return false;
      }
    }

    ErrorResult result;
    internalHeaders->SetGuard(HeadersGuardEnum::Immutable, result);
    if (NS_WARN_IF(result.Failed())) {
      result.SuppressException();
      return false;
    }
    RefPtr<InternalRequest> internalReq = new InternalRequest(mSpec,
                                                              mFragment,
                                                              mMethod,
                                                              internalHeaders.forget(),
                                                              mCacheMode,
                                                              mRequestMode,
                                                              mRequestRedirect,
                                                              mRequestCredentials,
                                                              NS_ConvertUTF8toUTF16(mReferrer),
                                                              mReferrerPolicy,
                                                              mContentPolicyType,
                                                              mIntegrity);
    internalReq->SetBody(mUploadStream, mUploadStreamContentLength);
    // For Telemetry, note that this Request object was created by a Fetch event.
    internalReq->SetCreatedByFetchEvent();

    nsCOMPtr<nsIChannel> channel;
    nsresult rv = mInterceptedChannel->GetChannel(getter_AddRefs(channel));
    NS_ENSURE_SUCCESS(rv, false);

    nsAutoCString alternativeDataType;
    nsCOMPtr<nsICacheInfoChannel> cic = do_QueryInterface(channel);
    if (cic &&
        NS_SUCCEEDED(cic->GetPreferredAlternativeDataType(alternativeDataType)) &&
        !alternativeDataType.IsEmpty()) {
      internalReq->SetPreferredAlternativeDataType(alternativeDataType);
    }

    nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(globalObj.GetAsSupports());
    if (NS_WARN_IF(!global)) {
      return false;
    }

    // TODO This request object should be created with a AbortSignal object
    // which should be aborted if the loading is aborted. See bug 1394102.
    RefPtr<Request> request = new Request(global, internalReq, nullptr);

    MOZ_ASSERT_IF(internalReq->IsNavigationRequest(),
                  request->Redirect() == RequestRedirect::Manual);

    RootedDictionary<FetchEventInit> init(aCx);
    init.mRequest = request;
    init.mBubbles = false;
    init.mCancelable = true;
    // Only expose the FetchEvent.clientId on subresource requests for now.
    // Once we implement .resultingClientId and .targetClientId we can then
    // start exposing .clientId on non-subresource requests as well.  See
    // bug 1264177.
    if (!mClientId.IsEmpty() && !internalReq->IsNavigationRequest()) {
      init.mClientId = mClientId;
    }
    init.mIsReload = mIsReload;
    RefPtr<FetchEvent> event =
      FetchEvent::Constructor(globalObj, NS_LITERAL_STRING("fetch"), init, result);
    if (NS_WARN_IF(result.Failed())) {
      result.SuppressException();
      return false;
    }

    event->PostInit(mInterceptedChannel, mRegistration, mScriptSpec);
    event->SetTrusted(true);

    mInterceptedChannel->SetHandleFetchEventStart(TimeStamp::Now());

    nsresult rv2 =
      DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(),
                                           event, nullptr);
    if ((NS_WARN_IF(NS_FAILED(rv2)) && rv2 != NS_ERROR_XPC_JS_THREW_EXCEPTION) ||
        !event->WaitToRespond()) {
      nsCOMPtr<nsIRunnable> runnable;
      MOZ_ASSERT(!aWorkerPrivate->UsesSystemPrincipal(),
                 "We don't support system-principal serviceworkers");
      if (event->DefaultPrevented(CallerType::NonSystem)) {
        runnable = new CancelChannelRunnable(mInterceptedChannel,
                                             mRegistration,
                                             NS_ERROR_INTERCEPTION_FAILED);
      } else {
        runnable = new ResumeRequest(mInterceptedChannel);
      }

      MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(runnable.forget()));
    }

    return true;
  }
};

NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable, nsIHttpHeaderVisitor)

} // anonymous namespace

nsresult
ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel,
                                     nsILoadGroup* aLoadGroup,
                                     const nsAString& aClientId, bool aIsReload)
{
  MOZ_ASSERT(NS_IsMainThread());

  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
  if (NS_WARN_IF(!mInfo || !swm)) {
    return NS_ERROR_FAILURE;
  }

  RefPtr<ServiceWorkerRegistrationInfo> registration =
    swm->GetRegistration(mInfo->Principal(), mInfo->Scope());

  // Its possible the registration is removed between starting the interception
  // and actually dispatching the fetch event.  In these cases we simply
  // want to restart the original network request.  Since this is a normal
  // condition we handle the reset here instead of returning an error which
  // would in turn trigger a console report.
  if (!registration) {
    nsresult rv = aChannel->ResetInterception();
    if (NS_FAILED(rv)) {
      NS_WARNING("Failed to resume intercepted network request");
      aChannel->CancelInterception(rv);
    }
    return NS_OK;
  }

  // Handle Fetch algorithm - step 16. If the service worker didn't register
  // any fetch event handlers, then abort the interception and maybe trigger
  // the soft update algorithm.
  if (!mInfo->HandlesFetch()) {
    nsresult rv = aChannel->ResetInterception();
    if (NS_FAILED(rv)) {
      NS_WARNING("Failed to resume intercepted network request");
      aChannel->CancelInterception(rv);
    }

    // Trigger soft updates if necessary.
    registration->MaybeScheduleTimeCheckAndUpdate();

    return NS_OK;
  }

  // if the ServiceWorker script fails to load for some reason, just resume
  // the original channel.
  nsCOMPtr<nsIRunnable> failRunnable =
    NewRunnableMethod("nsIInterceptedChannel::ResetInterception",
                      aChannel,
                      &nsIInterceptedChannel::ResetInterception);

  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<nsIInterceptedChannel> handle(
    new nsMainThreadPtrHolder<nsIInterceptedChannel>(
      "nsIInterceptedChannel", aChannel, false));

  nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
    new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(
      "ServiceWorkerRegistrationInfoProxy", registration, false));

  RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();


  RefPtr<FetchEventRunnable> r =
    new FetchEventRunnable(mWorkerPrivate, token, handle,
                           mInfo->ScriptSpec(), regInfo,
                           aClientId, aIsReload, newWorkerCreated);
  rv = r->Init();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  if (mInfo->State() == ServiceWorkerState::Activating) {
    mPendingFunctionalEvents.AppendElement(r.forget());
    return NS_OK;
  }

  MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated);

  if (NS_WARN_IF(!r->Dispatch())) {
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

nsresult
ServiceWorkerPrivate::SpawnWorkerIfNeeded(WakeUpReason aWhy,
                                          nsIRunnable* aLoadFailedRunnable,
                                          bool* aNewWorkerCreated,
                                          nsILoadGroup* aLoadGroup)
{
  MOZ_ASSERT(NS_IsMainThread());

  // 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) {
    // If we have a load group here then use it to update the service worker
    // load group.  This was added when we needed the load group's tab child
    // to pass some security checks.  Those security checks are gone, though,
    // and we could possibly remove this now.  For now we just do it
    // opportunistically.  When the service worker is running in a separate
    // process from the client that initiated the intercepted channel, then
    // the load group will be nullptr.  UpdateOverrideLoadGroup ignores nullptr
    // load groups.
    mWorkerPrivate->UpdateOverridenLoadGroup(aLoadGroup);
    RenewKeepAliveToken(aWhy);

    return NS_OK;
  }

  // Sanity check: mSupportsArray should be empty if we're about to
  // spin up a new worker.
  MOZ_ASSERT(mSupportsArray.IsEmpty());

  if (NS_WARN_IF(!mInfo)) {
    NS_WARNING("Trying to wake up a dead service worker.");
    return NS_ERROR_FAILURE;
  }

  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
  NS_ENSURE_TRUE(swm, NS_ERROR_FAILURE);

  RefPtr<ServiceWorkerRegistrationInfo> reg =
    swm->GetRegistration(mInfo->Principal(), mInfo->Scope());
  NS_ENSURE_TRUE(reg, NS_ERROR_FAILURE);

  // TODO(catalinb): Bug 1192138 - Add telemetry for service worker wake-ups.

  // Ensure that the IndexedDatabaseManager is initialized
  Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate());

  WorkerLoadInfo info;
  nsresult rv = NS_NewURI(getter_AddRefs(info.mBaseURI), mInfo->ScriptSpec(),
                          nullptr, nullptr);

  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  info.mResolvedScriptURI = info.mBaseURI;
  MOZ_ASSERT(!mInfo->CacheName().IsEmpty());
  info.mServiceWorkerCacheName = mInfo->CacheName();

  info.mServiceWorkerDescriptor.emplace(mInfo->Descriptor());
  info.mServiceWorkerRegistrationDescriptor.emplace(reg->Descriptor());

  info.mLoadGroup = aLoadGroup;
  info.mLoadFailedAsyncRunnable = aLoadFailedRunnable;

  // If we are loading a script for a ServiceWorker then we must not
  // try to intercept it.  If the interception matches the current
  // ServiceWorker's scope then we could deadlock the load.
  info.mLoadFlags = mInfo->GetImportsLoadFlags() |
                    nsIChannel::LOAD_BYPASS_SERVICE_WORKER;

  rv = info.mBaseURI->GetHost(info.mDomain);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsCOMPtr<nsIURI> uri;
  rv = mInfo->Principal()->GetURI(getter_AddRefs(uri));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  if (NS_WARN_IF(!uri)) {
    return NS_ERROR_FAILURE;
  }

  // Create a pristine codebase principal to avoid any possibility of inheriting
  // CSP values.  The principal on the registration may be polluted with CSP
  // from the registering page or other places the principal is passed.  If
  // bug 965637 is ever fixed this can be removed.
  info.mPrincipal =
    BasePrincipal::CreateCodebasePrincipal(uri, mInfo->GetOriginAttributes());
  if (NS_WARN_IF(!info.mPrincipal)) {
    return NS_ERROR_FAILURE;
  }
  info.mLoadingPrincipal = info.mPrincipal;

  nsContentUtils::StorageAccess access =
    nsContentUtils::StorageAllowedForPrincipal(info.mPrincipal);
  info.mStorageAllowed = access > nsContentUtils::StorageAccess::ePrivateBrowsing;
  info.mOriginAttributes = mInfo->GetOriginAttributes();

  // Verify that we don't have any CSP on pristine principal.
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
  nsCOMPtr<nsIContentSecurityPolicy> csp;
  Unused << info.mPrincipal->GetCsp(getter_AddRefs(csp));
  MOZ_DIAGNOSTIC_ASSERT(!csp);
#endif

  // Default CSP permissions for now.  These will be overrided if necessary
  // based on the script CSP headers during load in ScriptLoader.
  info.mEvalAllowed = true;
  info.mReportCSPViolations = false;

  WorkerPrivate::OverrideLoadInfoLoadGroup(info, info.mPrincipal);

  rv = info.SetPrincipalOnMainThread(info.mPrincipal, info.mLoadGroup);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  AutoJSAPI jsapi;
  jsapi.Init();
  ErrorResult error;
  NS_ConvertUTF8toUTF16 scriptSpec(mInfo->ScriptSpec());

  mWorkerPrivate = WorkerPrivate::Constructor(jsapi.cx(),
                                              scriptSpec,
                                              false, WorkerTypeService,
                                              VoidString(),
                                              EmptyCString(),
                                              &info, error);
  if (NS_WARN_IF(error.Failed())) {
    return error.StealNSResult();
  }

  RenewKeepAliveToken(aWhy);

  if (aNewWorkerCreated) {
    *aNewWorkerCreated = true;
  }

  return NS_OK;
}

void
ServiceWorkerPrivate::StoreISupports(nsISupports* aSupports)
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(mWorkerPrivate);
  MOZ_ASSERT(!mSupportsArray.Contains(aSupports));

  mSupportsArray.AppendElement(aSupports);
}

void
ServiceWorkerPrivate::RemoveISupports(nsISupports* aSupports)
{
  MOZ_ASSERT(NS_IsMainThread());
  mSupportsArray.RemoveElement(aSupports);
}

void
ServiceWorkerPrivate::TerminateWorker()
{
  MOZ_ASSERT(NS_IsMainThread());

  mIdleWorkerTimer->Cancel();
  mIdleKeepAliveToken = nullptr;
  if (mWorkerPrivate) {
    if (DOMPrefs::ServiceWorkersTestingEnabled()) {
      nsCOMPtr<nsIObserverService> os = services::GetObserverService();
      if (os) {
        os->NotifyObservers(nullptr, "service-worker-shutdown", nullptr);
      }
    }

    Unused << NS_WARN_IF(!mWorkerPrivate->Terminate());
    mWorkerPrivate = nullptr;
    mSupportsArray.Clear();

    // Any pending events are never going to fire on this worker.  Cancel
    // them so that intercepted channels can be reset and other resources
    // cleaned up.
    nsTArray<RefPtr<WorkerRunnable>> pendingEvents;
    mPendingFunctionalEvents.SwapElements(pendingEvents);
    for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
      pendingEvents[i]->Cancel();
    }
  }
}

void
ServiceWorkerPrivate::NoteDeadServiceWorkerInfo()
{
  MOZ_ASSERT(NS_IsMainThread());
  mInfo = nullptr;
  TerminateWorker();
}

namespace {

class UpdateStateControlRunnable final : public MainThreadWorkerControlRunnable
{
  const ServiceWorkerState mState;

  bool
  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
  {
    MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
    aWorkerPrivate->UpdateServiceWorkerState(mState);
    return true;
  }

public:
  UpdateStateControlRunnable(WorkerPrivate* aWorkerPrivate,
                             ServiceWorkerState aState)
    : MainThreadWorkerControlRunnable(aWorkerPrivate)
    , mState(aState)
  {
  }
};

} // anonymous namespace

void
ServiceWorkerPrivate::UpdateState(ServiceWorkerState aState)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (!mWorkerPrivate) {
    MOZ_DIAGNOSTIC_ASSERT(mPendingFunctionalEvents.IsEmpty());
    return;
  }

  RefPtr<WorkerRunnable> r =
    new UpdateStateControlRunnable(mWorkerPrivate, aState);
  Unused << r->Dispatch();

  if (aState != ServiceWorkerState::Activated) {
    return;
  }

  nsTArray<RefPtr<WorkerRunnable>> pendingEvents;
  mPendingFunctionalEvents.SwapElements(pendingEvents);

  for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
    RefPtr<WorkerRunnable> r = pendingEvents[i].forget();
    if (NS_WARN_IF(!r->Dispatch())) {
      NS_WARNING("Failed to dispatch pending functional event!");
    }
  }
}

nsresult
ServiceWorkerPrivate::GetDebugger(nsIWorkerDebugger** aResult)
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aResult);

  if (!mDebuggerCount) {
    return NS_OK;
  }

  MOZ_ASSERT(mWorkerPrivate);

  nsCOMPtr<nsIWorkerDebugger> debugger = do_QueryInterface(mWorkerPrivate->Debugger());
  debugger.forget(aResult);

  return NS_OK;
}

nsresult
ServiceWorkerPrivate::AttachDebugger()
{
  MOZ_ASSERT(NS_IsMainThread());

  // When the first debugger attaches to a worker, we spawn a worker if needed,
  // and cancel the idle timeout. The idle timeout should not be reset until
  // the last debugger detached from the worker.
  if (!mDebuggerCount) {
    nsresult rv = SpawnWorkerIfNeeded(AttachEvent, nullptr);
    NS_ENSURE_SUCCESS(rv, rv);

    mIdleWorkerTimer->Cancel();
  }

  ++mDebuggerCount;

  return NS_OK;
}

nsresult
ServiceWorkerPrivate::DetachDebugger()
{
  MOZ_ASSERT(NS_IsMainThread());

  if (!mDebuggerCount) {
    return NS_ERROR_UNEXPECTED;
  }

  --mDebuggerCount;

  // When the last debugger detaches from a worker, we either reset the idle
  // timeout, or terminate the worker if there are no more active tokens.
  if (!mDebuggerCount) {
    if (mTokenCount) {
      ResetIdleTimeout();
    } else {
      TerminateWorker();
    }
  }

  return NS_OK;
}

bool
ServiceWorkerPrivate::IsIdle() const
{
  MOZ_ASSERT(NS_IsMainThread());
  return mTokenCount == 0 || (mTokenCount == 1 && mIdleKeepAliveToken);
}

namespace {

class ServiceWorkerPrivateTimerCallback final : public nsITimerCallback
                                              , public nsINamed
{
public:
  typedef void (ServiceWorkerPrivate::*Method)(nsITimer*);

  ServiceWorkerPrivateTimerCallback(ServiceWorkerPrivate* aServiceWorkerPrivate,
                                    Method aMethod)
    : mServiceWorkerPrivate(aServiceWorkerPrivate)
    , mMethod(aMethod)
  {
  }

  NS_IMETHOD
  Notify(nsITimer* aTimer) override
  {
    (mServiceWorkerPrivate->*mMethod)(aTimer);
    mServiceWorkerPrivate = nullptr;
    return NS_OK;
  }

  NS_IMETHOD
  GetName(nsACString& aName) override
  {
    aName.AssignLiteral("ServiceWorkerPrivateTimerCallback");
    return NS_OK;
  }

private:
  ~ServiceWorkerPrivateTimerCallback() = default;

  RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
  Method mMethod;

  NS_DECL_THREADSAFE_ISUPPORTS
};

NS_IMPL_ISUPPORTS(ServiceWorkerPrivateTimerCallback, nsITimerCallback, nsINamed);

} // anonymous namespace

void
ServiceWorkerPrivate::NoteIdleWorkerCallback(nsITimer* aTimer)
{
  MOZ_ASSERT(NS_IsMainThread());

  MOZ_ASSERT(aTimer == mIdleWorkerTimer, "Invalid timer!");

  // Release ServiceWorkerPrivate's token, since the grace period has ended.
  mIdleKeepAliveToken = nullptr;

  if (mWorkerPrivate) {
    // If we still have a workerPrivate at this point it means there are pending
    // waitUntil promises. Wait a bit more until we forcibly terminate the
    // worker.
    uint32_t timeout = Preferences::GetInt("dom.serviceWorkers.idle_extended_timeout");
    nsCOMPtr<nsITimerCallback> cb = new ServiceWorkerPrivateTimerCallback(
      this, &ServiceWorkerPrivate::TerminateWorkerCallback);
    DebugOnly<nsresult> rv =
      mIdleWorkerTimer->InitWithCallback(cb, timeout, nsITimer::TYPE_ONE_SHOT);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
  }
}

void
ServiceWorkerPrivate::TerminateWorkerCallback(nsITimer* aTimer)
{
  MOZ_ASSERT(NS_IsMainThread());

  MOZ_ASSERT(aTimer == this->mIdleWorkerTimer, "Invalid timer!");

  // mInfo must be non-null at this point because NoteDeadServiceWorkerInfo
  // which zeroes it calls TerminateWorker which cancels our timer which will
  // ensure we don't get invoked even if the nsTimerEvent is in the event queue.
  ServiceWorkerManager::LocalizeAndReportToAllClients(
    mInfo->Scope(),
    "ServiceWorkerGraceTimeoutTermination",
    nsTArray<nsString> { NS_ConvertUTF8toUTF16(mInfo->Scope()) });

  TerminateWorker();
}

void
ServiceWorkerPrivate::RenewKeepAliveToken(WakeUpReason aWhy)
{
  // We should have an active worker if we're renewing the keep alive token.
  MOZ_ASSERT(mWorkerPrivate);

  // If there is at least one debugger attached to the worker, the idle worker
  // timeout was canceled when the first debugger attached to the worker. It
  // should not be reset until the last debugger detaches from the worker.
  if (!mDebuggerCount) {
    ResetIdleTimeout();
  }

  if (!mIdleKeepAliveToken) {
    mIdleKeepAliveToken = new KeepAliveToken(this);
  }
}

void
ServiceWorkerPrivate::ResetIdleTimeout()
{
  uint32_t timeout = Preferences::GetInt("dom.serviceWorkers.idle_timeout");
  nsCOMPtr<nsITimerCallback> cb = new ServiceWorkerPrivateTimerCallback(
    this, &ServiceWorkerPrivate::NoteIdleWorkerCallback);
  DebugOnly<nsresult> rv =
    mIdleWorkerTimer->InitWithCallback(cb, timeout, nsITimer::TYPE_ONE_SHOT);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
}

void
ServiceWorkerPrivate::AddToken()
{
  MOZ_ASSERT(NS_IsMainThread());
  ++mTokenCount;
}

void
ServiceWorkerPrivate::ReleaseToken()
{
  MOZ_ASSERT(NS_IsMainThread());

  MOZ_ASSERT(mTokenCount > 0);
  --mTokenCount;
  if (!mTokenCount) {
    TerminateWorker();
  }

  // mInfo can be nullptr here if NoteDeadServiceWorkerInfo() is called while
  // the KeepAliveToken is being proxy released as a runnable.
  else if (mInfo && IsIdle()) {
    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
    if (swm) {
      swm->WorkerIsIdle(mInfo);
    }
  }
}

already_AddRefed<KeepAliveToken>
ServiceWorkerPrivate::CreateEventKeepAliveToken()
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(mWorkerPrivate);
  MOZ_ASSERT(mIdleKeepAliveToken);
  RefPtr<KeepAliveToken> ref = new KeepAliveToken(this);
  return ref.forget();
}

void
ServiceWorkerPrivate::SetHandlesFetch(bool aValue)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (NS_WARN_IF(!mInfo)) {
    return;
  }

  mInfo->SetHandlesFetch(aValue);
}

} // namespace dom
} // namespace mozilla