Bug 1263304 - Allow waitUntil() to be called asynchronously. r=bkelly
☠☠ backed out by a10fce0b6fc8 ☠ ☠
authorCatalin Badea <catalin.badea392@gmail.com>
Fri, 11 Nov 2016 00:07:15 +0200
changeset 352154 b2d3c6629a37d02c1f57e6bb3465ba3e35ff81de
parent 352153 f03cd54afbc1297a7b7dbf0b25a9d76d8b7de806
child 352155 2c423ac723b6c7cfec1261f0b828bc05942a8677
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbkelly
bugs1263304
milestone52.0a1
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)
 {
   MOZ_ASSERT(aCx);
 
   if (mPreventDefaultScriptSpec.IsEmpty()) {
@@ -905,69 +905,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
@@ -1562,19 +1560,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;
       if (event->DefaultPrevented(aCx)) {
         event->ReportCanceled();
       } else if (event->WidgetEventPtr()->mFlags.mExceptionWasRaised) {
         // Exception logged via the WorkerPrivate ErrorReporter
       } else {
         runnable = new ResumeRequest(mInterceptedChannel);
       }
@@ -1583,22 +1582,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.