Bug 1263304 - Allow waitUntil() to be called asynchronously. r=bkelly
authorCatalin Badea <catalin.badea392@gmail.com>
Mon, 21 Nov 2016 02:14:53 +0000
changeset 368542 509e98eec0cebef7fad5bbdd736ea281cef47d49
parent 368541 85a6e5e89b42137920fba26206fe03ae5dc0f2d9
child 368543 3ecf72a6a2da8b0e9bd48b9eb4d78b5085f8ca90
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbkelly
bugs1263304
milestone53.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 1263304 - Allow waitUntil() to be called asynchronously. r=bkelly The behaviour is changed in two ways: 1. waitUntil() will now wait for each promise separately, even if one of them is rejected. 2. Extensions can be added asynchronously as long there is a pending waitUntil promise.
dom/workers/ServiceWorkerEvents.cpp
dom/workers/ServiceWorkerEvents.h
dom/workers/ServiceWorkerPrivate.cpp
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -762,19 +762,19 @@ FetchEvent::RespondWith(JSContext* aCx, 
   mWaitToRespond = true;
   RefPtr<RespondWithHandler> handler =
     new RespondWithHandler(mChannel, mRegistration, mRequest->Mode(),
                            ir->IsClientRequest(), mRequest->Redirect(),
                            mScriptSpec, NS_ConvertUTF8toUTF16(requestURL),
                            spec, line, column);
   aArg.AppendNativeHandler(handler);
 
-  // Append directly to the lifecycle promises array.  Don't call WaitUntil()
-  // because that will lead to double-reporting any errors.
-  mPromises.AppendElement(&aArg);
+  if (!WaitOnPromise(aArg)) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
 }
 
 void
 FetchEvent::PreventDefault(JSContext* aCx, CallerType aCallerType)
 {
   MOZ_ASSERT(aCx);
   MOZ_ASSERT(aCallerType != CallerType::System,
              "Since when do we support system-principal service workers?");
@@ -907,69 +907,56 @@ NS_INTERFACE_MAP_END_INHERITING(Extendab
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(FetchEvent, ExtendableEvent, mRequest)
 
 ExtendableEvent::ExtendableEvent(EventTarget* aOwner)
   : Event(aOwner, nullptr, nullptr)
 {
 }
 
+bool
+ExtendableEvent::WaitOnPromise(Promise& aPromise)
+{
+  MOZ_ASSERT(mExtensionsHandler);
+  return mExtensionsHandler->WaitOnPromise(aPromise);
+}
+
+void
+ExtendableEvent::SetKeepAliveHandler(ExtensionsHandler* aExtensionsHandler)
+{
+  MOZ_ASSERT(!mExtensionsHandler);
+  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(worker);
+  worker->AssertIsOnWorkerThread();
+  mExtensionsHandler = aExtensionsHandler;
+}
+
 void
 ExtendableEvent::WaitUntil(JSContext* aCx, Promise& aPromise, ErrorResult& aRv)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
-  if (EventPhase() == nsIDOMEvent::NONE) {
+  if (!WaitOnPromise(aPromise)) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   // Append our handler to each waitUntil promise separately so we
   // can record the location in script where waitUntil was called.
   RefPtr<WaitUntilHandler> handler =
     new WaitUntilHandler(GetCurrentThreadWorkerPrivate(), aCx);
   aPromise.AppendNativeHandler(handler);
-
-  mPromises.AppendElement(&aPromise);
-}
-
-already_AddRefed<Promise>
-ExtendableEvent::GetPromise()
-{
-  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
-  MOZ_ASSERT(worker);
-  worker->AssertIsOnWorkerThread();
-
-  nsIGlobalObject* globalObj = worker->GlobalScope();
-
-  AutoJSAPI jsapi;
-  if (!jsapi.Init(globalObj)) {
-    return nullptr;
-  }
-  JSContext* cx = jsapi.cx();
-
-  GlobalObject global(cx, globalObj->GetGlobalJSObject());
-
-  ErrorResult result;
-  RefPtr<Promise> p = Promise::All(global, Move(mPromises), result);
-  if (NS_WARN_IF(result.MaybeSetPendingException(cx))) {
-    return nullptr;
-  }
-
-  return p.forget();
 }
 
 NS_IMPL_ADDREF_INHERITED(ExtendableEvent, Event)
 NS_IMPL_RELEASE_INHERITED(ExtendableEvent, Event)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ExtendableEvent)
 NS_INTERFACE_MAP_END_INHERITING(Event)
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED(ExtendableEvent, Event, mPromises)
-
 namespace {
 nsresult
 ExtractBytesFromUSVString(const nsAString& aStr, nsTArray<uint8_t>& aBytes)
 {
   MOZ_ASSERT(aBytes.IsEmpty());
   nsCOMPtr<nsIUnicodeEncoder> encoder = EncodingUtils::EncoderForEncoding("UTF-8");
   if (NS_WARN_IF(!encoder)) {
     return NS_ERROR_OUT_OF_MEMORY;
--- a/dom/workers/ServiceWorkerEvents.h
+++ b/dom/workers/ServiceWorkerEvents.h
@@ -46,27 +46,46 @@ public:
                         nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
                         nsresult aStatus);
 
   NS_IMETHOD Run() override;
 };
 
 class ExtendableEvent : public Event
 {
+public:
+  class ExtensionsHandler {
+  public:
+    virtual bool
+    WaitOnPromise(Promise& aPromise) = 0;
+
+    NS_IMETHOD_(MozExternalRefCountType)
+    AddRef() = 0;
+
+    NS_IMETHOD_(MozExternalRefCountType)
+    Release() = 0;
+  };
+
+private:
+  RefPtr<ExtensionsHandler> mExtensionsHandler;
+
 protected:
-  nsTArray<RefPtr<Promise>> mPromises;
+  bool
+  WaitOnPromise(Promise& aPromise);
 
   explicit ExtendableEvent(mozilla::dom::EventTarget* aOwner);
   ~ExtendableEvent() {}
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ExtendableEvent, Event)
   NS_FORWARD_TO_EVENT
 
+  void
+  SetKeepAliveHandler(ExtensionsHandler* aExtensionsHandler);
+
   virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
   {
     return mozilla::dom::ExtendableEventBinding::Wrap(aCx, this, aGivenProto);
   }
 
   static already_AddRefed<ExtendableEvent>
   Constructor(mozilla::dom::EventTarget* aOwner,
               const nsAString& aType,
@@ -88,19 +107,16 @@ public:
   {
     nsCOMPtr<EventTarget> target = do_QueryInterface(aGlobal.GetAsSupports());
     return Constructor(target, aType, aOptions);
   }
 
   void
   WaitUntil(JSContext* aCx, Promise& aPromise, ErrorResult& aRv);
 
-  already_AddRefed<Promise>
-  GetPromise();
-
   virtual ExtendableEvent* AsExtendableEvent() override
   {
     return this;
   }
 };
 
 class FetchEvent final : public ExtendableEvent
 {
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -176,164 +176,196 @@ ServiceWorkerPrivate::CheckScriptEvaluat
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 namespace {
 
-// Holds the worker alive until the waitUntil promise is resolved or
-// rejected.
-class KeepAliveHandler final
+enum ExtendableEventResult {
+    Rejected = 0,
+    Resolved
+};
+
+class ExtendableEventCallback {
+public:
+  virtual void
+  FinishedWithResult(ExtendableEventResult aResult) = 0;
+
+  NS_IMETHOD_(MozExternalRefCountType)
+  AddRef() = 0;
+
+  NS_IMETHOD_(MozExternalRefCountType)
+  Release() = 0;
+};
+
+class KeepAliveHandler final : public WorkerHolder
+                             , public ExtendableEvent::ExtensionsHandler
+                             , public PromiseNativeHandler
 {
-  // Use an internal class to listen for the promise resolve/reject
-  // callbacks.  This class also registers a feature so that it can
-  // preemptively cleanup if the service worker is timed out and
-  // terminated.
-  class InternalHandler final : public PromiseNativeHandler
-                              , public WorkerHolder
-  {
-    nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;
+  // 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;
 
-    // Worker thread only
-    WorkerPrivate* mWorkerPrivate;
-    RefPtr<Promise> mPromise;
-    bool mWorkerHolderAdded;
+  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
 
-    ~InternalHandler()
-    {
-      MaybeCleanup();
+  explicit KeepAliveHandler(const nsMainThreadPtrHandle<KeepAliveToken>& aKeepAliveToken,
+                            ExtendableEventCallback* aCallback)
+    : 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;
     }
-
-    bool
-    UseWorkerHolder()
-    {
-      MOZ_ASSERT(mWorkerPrivate);
-      mWorkerPrivate->AssertIsOnWorkerThread();
-      MOZ_ASSERT(!mWorkerHolderAdded);
-      mWorkerHolderAdded = HoldWorker(mWorkerPrivate, Terminating);
-      return mWorkerHolderAdded;
+    if (!mSelfRef) {
+      MOZ_ASSERT(!mPendingPromisesCount);
+      mSelfRef = this;
     }
 
-    void
-    MaybeCleanup()
-    {
-      MOZ_ASSERT(mWorkerPrivate);
-      mWorkerPrivate->AssertIsOnWorkerThread();
-      if (!mPromise) {
-        return;
-      }
-      if (mWorkerHolderAdded) {
-        ReleaseWorker();
-      }
-      mPromise = nullptr;
-      mKeepAliveToken = nullptr;
-    }
+    ++mPendingPromisesCount;
+    aPromise.AppendNativeHandler(this);
+
+    return true;
+  }
+
+  void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    RemovePromise(Resolved);
+  }
 
-    void
-    ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
-    {
-      MOZ_ASSERT(mWorkerPrivate);
-      mWorkerPrivate->AssertIsOnWorkerThread();
-      MaybeCleanup();
-    }
+  void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    RemovePromise(Rejected);
+  }
 
-    void
-    RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
-    {
-      MOZ_ASSERT(mWorkerPrivate);
-      mWorkerPrivate->AssertIsOnWorkerThread();
-      MaybeCleanup();
-    }
-
-    bool
-    Notify(Status aStatus) override
-    {
-      MOZ_ASSERT(mWorkerPrivate);
-      mWorkerPrivate->AssertIsOnWorkerThread();
-      if (aStatus < Terminating) {
-        return true;
-      }
-      MaybeCleanup();
+  bool
+  Notify(Status aStatus) override
+  {
+    MOZ_ASSERT(mWorkerPrivate);
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    if (aStatus < Terminating) {
       return true;
     }
 
-    InternalHandler(const nsMainThreadPtrHandle<KeepAliveToken>& aKeepAliveToken,
-                    WorkerPrivate* aWorkerPrivate,
-                    Promise* aPromise)
-      : mKeepAliveToken(aKeepAliveToken)
-      , mWorkerPrivate(aWorkerPrivate)
-      , mPromise(aPromise)
-      , mWorkerHolderAdded(false)
-    {
-      MOZ_ASSERT(mKeepAliveToken);
-      MOZ_ASSERT(mWorkerPrivate);
-      MOZ_ASSERT(mPromise);
-    }
+    MaybeCleanup();
+    return true;
+  }
 
-  public:
-    static already_AddRefed<InternalHandler>
-    Create(const nsMainThreadPtrHandle<KeepAliveToken>& aKeepAliveToken,
-           WorkerPrivate* aWorkerPrivate,
-           Promise* aPromise)
-    {
-      RefPtr<InternalHandler> ref = new InternalHandler(aKeepAliveToken,
-                                                        aWorkerPrivate,
-                                                        aPromise);
+  void
+  MaybeDone()
+  {
+    MOZ_ASSERT(mWorkerPrivate);
+    mWorkerPrivate->AssertIsOnWorkerThread();
 
-      if (NS_WARN_IF(!ref->UseWorkerHolder())) {
-        return nullptr;
-      }
-
-      return ref.forget();
+    if (mPendingPromisesCount) {
+      return;
+    }
+    if (mCallback) {
+      mCallback->FinishedWithResult(mRejected ? Rejected : Resolved);
     }
 
-    NS_DECL_ISUPPORTS
-  };
+    MaybeCleanup();
+  }
 
-  // This is really just a wrapper class to keep the InternalHandler
-  // private.  We don't want any code to accidentally call
-  // Promise::AppendNativeHandler() without also referencing the promise.
-  // Therefore we force all code through the static CreateAndAttachToPromise()
-  // and use the private InternalHandler object.
-  KeepAliveHandler() = delete;
-  ~KeepAliveHandler() = delete;
+private:
+  ~KeepAliveHandler()
+  {
+    MaybeCleanup();
+  }
 
-public:
-  // Create a private handler object and attach it to the given Promise.
-  // This will also create a strong ref to the Promise in a ref cycle.  The
-  // ref cycle is broken when the Promise is fulfilled or the worker thread
-  // is Terminated.
-  static void
-  CreateAndAttachToPromise(const nsMainThreadPtrHandle<KeepAliveToken>& aKeepAliveToken,
-                           Promise* aPromise)
+  void
+  MaybeCleanup()
   {
-    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
-    MOZ_ASSERT(workerPrivate);
-    workerPrivate->AssertIsOnWorkerThread();
-    MOZ_ASSERT(aKeepAliveToken);
-    MOZ_ASSERT(aPromise);
+    MOZ_ASSERT(mWorkerPrivate);
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    if (!mKeepAliveToken) {
+      return;
+    }
+    if (mWorkerHolderAdded) {
+      ReleaseWorker();
+    }
+
+    mKeepAliveToken = nullptr;
+    mSelfRef = nullptr;
+  }
 
-    // This creates a strong ref to the promise.
-    RefPtr<InternalHandler> handler = InternalHandler::Create(aKeepAliveToken,
-                                                              workerPrivate,
-                                                              aPromise);
-    if (NS_WARN_IF(!handler)) {
+  void
+  RemovePromise(ExtendableEventResult aResult)
+  {
+    MOZ_ASSERT(mWorkerPrivate);
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    MOZ_DIAGNOSTIC_ASSERT(mPendingPromisesCount > 0);
+    MOZ_ASSERT(mSelfRef);
+    MOZ_ASSERT(mKeepAliveToken);
+
+    mRejected |= (aResult == Rejected);
+
+    --mPendingPromisesCount;
+    if (mPendingPromisesCount) {
       return;
     }
 
-    // This then creates a strong ref cycle between the promise and the
-    // handler.  The cycle is broken when the Promise is fulfilled or
-    // the worker thread is Terminated.
-    aPromise->AppendNativeHandler(handler);
+    CycleCollectedJSContext* cx = CycleCollectedJSContext::Get();
+    MOZ_ASSERT(cx);
+
+    RefPtr<nsIRunnable> r = NewRunnableMethod(this, &KeepAliveHandler::MaybeDone);
+    cx->DispatchToMicroTask(r.forget());
   }
 };
 
-NS_IMPL_ISUPPORTS0(KeepAliveHandler::InternalHandler)
+NS_IMPL_ISUPPORTS0(KeepAliveHandler)
 
 class RegistrationUpdateRunnable : public Runnable
 {
   nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
   const bool mNeedTimeCheck;
 
 public:
   RegistrationUpdateRunnable(nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
@@ -372,50 +404,43 @@ public:
     mKeepAliveToken =
       new nsMainThreadPtrHolder<KeepAliveToken>(aKeepAliveToken);
   }
 
   bool
   DispatchExtendableEventOnWorkerScope(JSContext* aCx,
                                        WorkerGlobalScope* aWorkerScope,
                                        ExtendableEvent* aEvent,
-                                       PromiseNativeHandler* aPromiseHandler)
+                                       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 false;
+    }
+
+    // This must always be set *before* dispatching the event, otherwise
+    // waitUntil calls will fail.
+    aEvent->SetKeepAliveHandler(keepAliveHandler);
+
     ErrorResult result;
     result = aWorkerScope->DispatchDOMEvent(nullptr, aEvent, nullptr, nullptr);
     if (NS_WARN_IF(result.Failed()) || internalEvent->mFlags.mExceptionWasRaised) {
       result.SuppressException();
       return false;
     }
 
-    RefPtr<Promise> waitUntilPromise = aEvent->GetPromise();
-    if (!waitUntilPromise) {
-      waitUntilPromise =
-        Promise::Resolve(sgo, aCx, JS::UndefinedHandleValue, result);
-      MOZ_RELEASE_ASSERT(!result.Failed());
-    }
-
-    MOZ_ASSERT(waitUntilPromise);
-
-    // Make sure to append the caller's promise handler before attaching
-    // our keep alive handler.  This can avoid terminating the worker
-    // before a success result is delivered to the caller in cases where
-    // the idle timeout has been set to zero.  This low timeout value is
-    // sometimes set in tests.
-    if (aPromiseHandler) {
-      waitUntilPromise->AppendNativeHandler(aPromiseHandler);
-    }
-
-    KeepAliveHandler::CreateAndAttachToPromise(mKeepAliveToken,
-                                               waitUntilPromise);
+    // [[ If e’s extend lifetime promises is empty, unset e’s extensions allowed
+    //    flag and abort these steps. ]]
+    keepAliveHandler->MaybeDone();
 
     return true;
   }
 };
 
 class SendMesssageEventRunnable final : public ExtendableEventWorkerRunnable
                                       , public StructuredCloneHolder
 {
@@ -596,17 +621,17 @@ private:
 
 };
 
 /*
  * 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 PromiseNativeHandler,
+class LifeCycleEventWatcher final : public ExtendableEventCallback,
                                     public WorkerHolder
 {
   WorkerPrivate* mWorkerPrivate;
   RefPtr<LifeCycleEventCallback> mCallback;
   bool mDone;
 
   ~LifeCycleEventWatcher()
   {
@@ -618,17 +643,17 @@ class LifeCycleEventWatcher final : publ
     // 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_DECL_ISUPPORTS
+  NS_INLINE_DECL_REFCOUNTING(LifeCycleEventWatcher, override)
 
   LifeCycleEventWatcher(WorkerPrivate* aWorkerPrivate,
                         LifeCycleEventCallback* aCallback)
     : mWorkerPrivate(aWorkerPrivate)
     , mCallback(aCallback)
     , mDone(false)
   {
     MOZ_ASSERT(aWorkerPrivate);
@@ -685,41 +710,29 @@ public:
     if (NS_WARN_IF(NS_FAILED(rv))) {
       NS_RUNTIMEABORT("Failed to dispatch life cycle event handler.");
     }
 
     ReleaseWorker();
   }
 
   void
-  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  FinishedWithResult(ExtendableEventResult aResult) override
   {
     MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate);
     mWorkerPrivate->AssertIsOnWorkerThread();
-
-    ReportResult(true);
-  }
-
-  void
-  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
-  {
-    MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate);
-    mWorkerPrivate->AssertIsOnWorkerThread();
-
-    ReportResult(false);
+    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.
   }
 };
 
-NS_IMPL_ISUPPORTS0(LifeCycleEventWatcher)
-
 bool
 LifecycleEventWorkerRunnable::DispatchLifecycleEvent(JSContext* aCx,
                                                      WorkerPrivate* aWorkerPrivate)
 {
   aWorkerPrivate->AssertIsOnWorkerThread();
   MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
 
   RefPtr<ExtendableEvent> event;
@@ -773,46 +786,42 @@ ServiceWorkerPrivate::SendLifeCycleEvent
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 namespace {
 
-class PushErrorReporter final : public PromiseNativeHandler
+class PushErrorReporter final : public ExtendableEventCallback
 {
   WorkerPrivate* mWorkerPrivate;
   nsString mMessageId;
 
   ~PushErrorReporter()
   {
   }
 
 public:
-  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PushErrorReporter, override)
 
   PushErrorReporter(WorkerPrivate* aWorkerPrivate,
                     const nsAString& aMessageId)
     : mWorkerPrivate(aWorkerPrivate)
     , mMessageId(aMessageId)
   {
     mWorkerPrivate->AssertIsOnWorkerThread();
   }
 
-  void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  void
+  FinishedWithResult(ExtendableEventResult aResult) override
   {
-    mWorkerPrivate->AssertIsOnWorkerThread();
-    mWorkerPrivate = nullptr;
-    // Do nothing; we only use this to report errors to the Push service.
-  }
-
-  void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
-  {
-    Report(nsIPushErrorReporter::DELIVERY_UNHANDLED_REJECTION);
+    if (aResult == Rejected) {
+      Report(nsIPushErrorReporter::DELIVERY_UNHANDLED_REJECTION);
+    }
   }
 
   void Report(uint16_t aReason = nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR)
   {
     WorkerPrivate* workerPrivate = mWorkerPrivate;
     mWorkerPrivate->AssertIsOnWorkerThread();
     mWorkerPrivate = nullptr;
 
@@ -834,18 +843,16 @@ public:
       do_GetService("@mozilla.org/push/Service;1");
     if (reporter) {
       nsresult rv = reporter->ReportDeliveryError(mMessageId, aReason);
       Unused << NS_WARN_IF(NS_FAILED(rv));
     }
   }
 };
 
-NS_IMPL_ISUPPORTS0(PushErrorReporter)
-
 class SendPushEventRunnable final : public ExtendableFunctionalEventWorkerRunnable
 {
   nsString mMessageId;
   Maybe<nsTArray<uint8_t>> mData;
 
 public:
   SendPushEventRunnable(WorkerPrivate* aWorkerPrivate,
                         KeepAliveToken* aKeepAliveToken,
@@ -1038,17 +1045,17 @@ private:
     // are cancelled.
     mHandler = nullptr;
     return WorkerRunnable::Cancel();
   }
 
   RefPtr<AllowWindowInteractionHandler> mHandler;
 };
 
-class AllowWindowInteractionHandler final : public PromiseNativeHandler
+class AllowWindowInteractionHandler final : public ExtendableEventCallback
 {
   friend class ClearWindowAllowedRunnable;
   nsCOMPtr<nsITimer> mTimer;
 
   ~AllowWindowInteractionHandler()
   {
   }
 
@@ -1117,40 +1124,31 @@ class AllowWindowInteractionHandler fina
                                       nsITimer::TYPE_ONE_SHOT);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       ClearWindowAllowed(aWorkerPrivate);
       return;
     }
   }
 
 public:
-  NS_DECL_ISUPPORTS
+  NS_INLINE_DECL_REFCOUNTING(AllowWindowInteractionHandler, override)
 
   explicit AllowWindowInteractionHandler(WorkerPrivate* aWorkerPrivate)
   {
     StartClearWindowTimer(aWorkerPrivate);
   }
 
   void
-  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  FinishedWithResult(ExtendableEventResult /* aResult */) override
   {
-    WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
-    ClearWindowAllowed(workerPrivate);
-  }
-
-  void
-  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
-  {
-    WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
     ClearWindowAllowed(workerPrivate);
   }
 };
 
-NS_IMPL_ISUPPORTS0(AllowWindowInteractionHandler)
-
 bool
 ClearWindowAllowedRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
 {
   mHandler->ClearWindowAllowed(aWorkerPrivate);
   mHandler = nullptr;
   return true;
 }
 
@@ -1229,17 +1227,17 @@ public:
     }
 
     event->SetTrusted(true);
     aWorkerPrivate->GlobalScope()->AllowWindowInteraction();
     RefPtr<AllowWindowInteractionHandler> allowWindowInteraction =
       new AllowWindowInteractionHandler(aWorkerPrivate);
     if (!DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(),
                                               event, allowWindowInteraction)) {
-      allowWindowInteraction->RejectedCallback(aCx, JS::UndefinedHandleValue);
+      allowWindowInteraction->FinishedWithResult(Rejected);
     }
     aWorkerPrivate->GlobalScope()->ConsumeWindowInteraction();
 
     return true;
   }
 };
 
 } // namespace anonymous
@@ -1578,19 +1576,20 @@ private:
     if (NS_WARN_IF(result.Failed())) {
       result.SuppressException();
       return false;
     }
 
     event->PostInit(mInterceptedChannel, mRegistration, mScriptSpec);
     event->SetTrusted(true);
 
-    RefPtr<EventTarget> target = do_QueryObject(aWorkerPrivate->GlobalScope());
-    nsresult rv2 = target->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
-    if (NS_WARN_IF(NS_FAILED(rv2)) || !event->WaitToRespond()) {
+    bool rv2 =
+      DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(),
+                                           event, nullptr);
+    if (NS_WARN_IF(!rv2) || !event->WaitToRespond()) {
       nsCOMPtr<nsIRunnable> runnable;
       MOZ_ASSERT(!aWorkerPrivate->UsesSystemPrincipal(),
                  "We don't support system-principal serviceworkers");
       if (event->DefaultPrevented(CallerType::NonSystem)) {
         event->ReportCanceled();
       } else if (event->WidgetEventPtr()->mFlags.mExceptionWasRaised) {
         // Exception logged via the WorkerPrivate ErrorReporter
       } else {
@@ -1601,22 +1600,16 @@ private:
         runnable = new CancelChannelRunnable(mInterceptedChannel,
                                              mRegistration,
                                              NS_ERROR_INTERCEPTION_FAILED);
       }
 
       MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(runnable.forget()));
     }
 
-    RefPtr<Promise> waitUntilPromise = event->GetPromise();
-    if (waitUntilPromise) {
-      KeepAliveHandler::CreateAndAttachToPromise(mKeepAliveToken,
-                                                 waitUntilPromise);
-    }
-
     return true;
   }
 
   nsresult
   HandleBodyWithHeaders(nsIInputStream* aUploadStream)
   {
     // We are dealing with an nsMIMEInputStream which uses string input streams
     // under the hood, so all of the data is available synchronously.