Bug 1226441 - Part 2: Delay functional event dispatch until service worker is activated; r=catalinb a=ritu
authorBen Kelly <ben@wanderview.com>
Thu, 26 Nov 2015 19:56:19 +0200
changeset 305668 7ea5152b84175b42bfe7519e450d24e546e014db
parent 305667 bab785163ce40e7514be377da2686a1b7bdcf158
child 305669 c8917edc60508ce3854e2aaa585289dd06b43d07
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscatalinb, ritu
bugs1226441
milestone44.0a2
Bug 1226441 - Part 2: Delay functional event dispatch until service worker is activated; r=catalinb a=ritu Conflicts: dom/workers/ServiceWorkerPrivate.h
dom/workers/ServiceWorkerManager.cpp
dom/workers/ServiceWorkerManager.h
dom/workers/ServiceWorkerPrivate.cpp
dom/workers/ServiceWorkerPrivate.h
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -4679,28 +4679,36 @@ private:
   ServiceWorkerState mState;
 };
 
 }
 
 void
 ServiceWorkerInfo::UpdateState(ServiceWorkerState aState)
 {
+  AssertIsOnMainThread();
 #ifdef DEBUG
   // Any state can directly transition to redundant, but everything else is
   // ordered.
   if (aState != ServiceWorkerState::Redundant) {
     MOZ_ASSERT_IF(mState == ServiceWorkerState::EndGuard_, aState == ServiceWorkerState::Installing);
     MOZ_ASSERT_IF(mState == ServiceWorkerState::Installing, aState == ServiceWorkerState::Installed);
     MOZ_ASSERT_IF(mState == ServiceWorkerState::Installed, aState == ServiceWorkerState::Activating);
     MOZ_ASSERT_IF(mState == ServiceWorkerState::Activating, aState == ServiceWorkerState::Activated);
   }
   // Activated can only go to redundant.
   MOZ_ASSERT_IF(mState == ServiceWorkerState::Activated, aState == ServiceWorkerState::Redundant);
 #endif
+  // Flush any pending functional events to the worker when it transitions to the
+  // activated state.
+  // TODO: Do we care that these events will race with the propagation of the
+  //       state change?
+  if (aState == ServiceWorkerState::Activated && mState != aState) {
+    mServiceWorkerPrivate->Activated();
+  }
   mState = aState;
   nsCOMPtr<nsIRunnable> r = new ChangeStateUpdater(mInstances, mState);
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r.forget())));
 }
 
 ServiceWorkerInfo::ServiceWorkerInfo(ServiceWorkerRegistrationInfo* aReg,
                                      const nsACString& aScriptSpec,
                                      const nsAString& aCacheName)
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -271,16 +271,17 @@ public:
 
   void
   UpdateState(ServiceWorkerState aState);
 
   // Only used to set initial state when loading from disk!
   void
   SetActivateStateUncheckedWithoutEvent(ServiceWorkerState aState)
   {
+    AssertIsOnMainThread();
     mState = aState;
   }
 
   void
   AppendWorker(ServiceWorker* aWorker);
 
   void
   RemoveWorker(ServiceWorker* aWorker);
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -582,16 +582,24 @@ ServiceWorkerPrivate::SendPushEvent(cons
 
   nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
     new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(aRegistration, false));
 
   RefPtr<WorkerRunnable> r = new SendPushEventRunnable(mWorkerPrivate,
                                                          mKeepAliveToken,
                                                          aData,
                                                          regInfo);
+
+  if (mInfo->State() == ServiceWorkerState::Activating) {
+    mPendingFunctionalEvents.AppendElement(r.forget());
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated);
+
   AutoJSAPI jsapi;
   jsapi.Init();
   if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 #endif // MOZ_SIMPLEPUSH
@@ -1049,16 +1057,27 @@ public:
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate);
     return DispatchFetchEvent(aCx, aWorkerPrivate);
   }
 
+  NS_IMETHOD
+  Cancel() override
+  {
+    nsCOMPtr<nsIRunnable> runnable = new ResumeRequest(mInterceptedChannel);
+    if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
+      NS_WARNING("Failed to resume channel on FetchEventRunnable::Cancel()!\n");
+    }
+    WorkerRunnable::Cancel();
+    return NS_OK;
+  }
+
 private:
   ~FetchEventRunnable() {}
 
   class ResumeRequest final : public nsRunnable {
     nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
   public:
     explicit ResumeRequest(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel)
       : mChannel(aChannel)
@@ -1238,16 +1257,18 @@ NS_IMPL_ISUPPORTS_INHERITED(FetchEventRu
 } // anonymous namespace
 
 nsresult
 ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel,
                                      nsILoadGroup* aLoadGroup,
                                      UniquePtr<ServiceWorkerClientInfo>&& aClientInfo,
                                      bool aIsReload)
 {
+  AssertIsOnMainThread();
+
   // if the ServiceWorker script fails to load for some reason, just resume
   // the original channel.
   nsCOMPtr<nsIRunnable> failRunnable =
     NS_NewRunnableMethod(aChannel, &nsIInterceptedChannel::ResetInterception);
 
   nsresult rv = SpawnWorkerIfNeeded(FetchEvent, failRunnable, aLoadGroup);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1271,16 +1292,23 @@ ServiceWorkerPrivate::SendFetchEvent(nsI
     new FetchEventRunnable(mWorkerPrivate, mKeepAliveToken, handle,
                            mInfo->ScriptSpec(), regInfo,
                            Move(aClientInfo), aIsReload);
   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);
+
   AutoJSAPI jsapi;
   jsapi.Init();
   if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
@@ -1417,16 +1445,25 @@ ServiceWorkerPrivate::TerminateWorker()
       }
     }
 
     AutoJSAPI jsapi;
     jsapi.Init();
     NS_WARN_IF(!mWorkerPrivate->Terminate(jsapi.cx()));
     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()
 {
   AssertIsOnMainThread();
   mInfo = nullptr;
@@ -1439,16 +1476,39 @@ ServiceWorkerPrivate::NoteStoppedControl
   AssertIsOnMainThread();
   if (mIsPushWorker) {
     return;
   }
 
   TerminateWorker();
 }
 
+void
+ServiceWorkerPrivate::Activated()
+{
+  AssertIsOnMainThread();
+
+  // If we had to queue up events due to the worker activating, that means
+  // the worker must be currently running.  We should be called synchronously
+  // when the worker becomes activated.
+  MOZ_ASSERT_IF(!mPendingFunctionalEvents.IsEmpty(), mWorkerPrivate);
+
+  nsTArray<RefPtr<WorkerRunnable>> pendingEvents;
+  mPendingFunctionalEvents.SwapElements(pendingEvents);
+
+  for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
+    RefPtr<WorkerRunnable> r = pendingEvents[i].forget();
+    AutoJSAPI jsapi;
+    jsapi.Init();
+    if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
+      NS_WARNING("Failed to dispatch pending functional event!");
+    }
+  }
+}
+
 /* static */ void
 ServiceWorkerPrivate::NoteIdleWorkerCallback(nsITimer* aTimer, void* aPrivate)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aPrivate);
 
   RefPtr<ServiceWorkerPrivate> swp = static_cast<ServiceWorkerPrivate*>(aPrivate);
 
--- a/dom/workers/ServiceWorkerPrivate.h
+++ b/dom/workers/ServiceWorkerPrivate.h
@@ -123,16 +123,19 @@ public:
   TerminateWorker();
 
   void
   NoteDeadServiceWorkerInfo();
 
   void
   NoteStoppedControllingDocuments();
 
+  void
+  Activated();
+
 private:
   enum WakeUpReason {
     FetchEvent = 0,
     PushEvent,
     PushSubscriptionChangeEvent,
     MessageEvent,
     NotificationClickEvent,
     LifeCycleEvent
@@ -186,15 +189,19 @@ private:
 
   uint64_t mTokenCount;
 
   // Meant for keeping objects alive while handling requests from the worker
   // on the main thread. Access to this array is provided through
   // |StoreISupports| and |RemoveISupports|. Note that the array is also
   // cleared whenever the worker is terminated.
   nsTArray<nsCOMPtr<nsISupports>> mSupportsArray;
+
+  // Array of function event worker runnables that are pending due to
+  // the worker activating.  Main thread only.
+  nsTArray<RefPtr<WorkerRunnable>> mPendingFunctionalEvents;
 };
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_workers_serviceworkerprivate_h