Bug 1043701 - Fire statechange event on ServiceWorker instances. r=baku
authorNikhil Marathe <nsm.nikhil@gmail.com>
Fri, 19 Dec 2014 03:25:56 -0800
changeset 254258 0b908f3e1a1df6f04ca8653ee3b26387bb10e16c
parent 254257 99edce3a82d9462556292ee31636d159e4fead3f
child 254259 3bf6b6a31b6a304f0479b2db215e166f5235ec83
push id721
push userjlund@mozilla.com
push dateTue, 21 Apr 2015 23:03:33 +0000
treeherdermozilla-release@d27c9211ebb3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1043701
milestone38.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 1043701 - Fire statechange event on ServiceWorker instances. r=baku Folded: Various registration related UpdateState() calls and abort on failure to create a service worker. Set ServiceWorker instances state based on corresponding ServiceWorkerInfo state.
dom/workers/ServiceWorker.h
dom/workers/ServiceWorkerManager.cpp
dom/workers/ServiceWorkerManager.h
dom/workers/ServiceWorkerRegistration.cpp
dom/workers/ServiceWorkerRegistration.h
--- a/dom/workers/ServiceWorker.h
+++ b/dom/workers/ServiceWorker.h
@@ -36,21 +36,33 @@ public:
 
   ServiceWorkerState
   State() const
   {
     return mState;
   }
 
   void
+  SetState(ServiceWorkerState aState)
+  {
+    mState = aState;
+  }
+
+  void
   GetScriptURL(nsString& aURL) const
   {
     aURL = mURL;
   }
 
+  void
+  DispatchStateChange()
+  {
+    DOMEventTargetHelper::DispatchTrustedEvent(NS_LITERAL_STRING("statechange"));
+  }
+
   WorkerPrivate*
   GetWorkerPrivate() const;
 
 private:
   // This class can only be created from the RuntimeService.
   ServiceWorker(nsPIDOMWindow* aWindow, SharedWorker* aSharedWorker);
 
   // This class is reference-counted and will be destroyed from Release().
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -63,31 +63,30 @@ ServiceWorkerJob::Done(nsresult aStatus)
   }
 }
 
 void
 ServiceWorkerRegistrationInfo::Clear()
 {
   if (mInstallingWorker) {
     // FIXME(nsm): Terminate installing worker.
-    // Bug 1043701 Set state to redundant.
-    // Fire statechange.
+    mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
     mInstallingWorker = nullptr;
     // FIXME(nsm): Abort any inflight requests from installing worker.
   }
 
   if (mWaitingWorker) {
-    // FIXME(nsm): Bug 1043701 Set state to redundant.
+    mWaitingWorker->UpdateState(ServiceWorkerState::Redundant);
     // Fire statechange.
     mWaitingWorker = nullptr;
     mWaitingToActivate = false;
   }
 
   if (mActiveWorker) {
-    // FIXME(nsm): Bug 1043701 Set state to redundant.
+    mActiveWorker->UpdateState(ServiceWorkerState::Redundant);
     mActiveWorker = nullptr;
   }
 
   nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   MOZ_ASSERT(swm);
   swm->InvalidateServiceWorkerRegistrationWorker(this,
                                                  WhichServiceWorker::INSTALLING_WORKER |
                                                  WhichServiceWorker::WAITING_WORKER |
@@ -323,17 +322,17 @@ public:
       nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
       nsRefPtr<ServiceWorkerManager::ServiceWorkerDomainInfo> domainInfo =
         swm->GetDomainInfo(mScope);
       MOZ_ASSERT(domainInfo);
       mRegistration = domainInfo->GetRegistration(mScope);
 
       if (mRegistration) {
         nsRefPtr<ServiceWorkerInfo> newest = mRegistration->Newest();
-        if (newest && mScriptSpec.Equals(newest->GetScriptSpec()) &&
+        if (newest && mScriptSpec.Equals(newest->ScriptSpec()) &&
             mScriptSpec.Equals(mRegistration->mScriptSpec)) {
           mRegistration->mPendingUninstall = false;
           Succeed();
           Done(NS_OK);
           return;
         }
       } else {
         mRegistration = domainInfo->CreateNewRegistration(mScope);
@@ -398,17 +397,17 @@ private:
   // Aspects of (actually the whole algorithm) of [[Update]] after
   // "Run the following steps in parallel."
   void
   ContinueUpdate()
   {
     AssertIsOnMainThread();
     if (mRegistration->mInstallingWorker) {
       // FIXME(nsm): "Terminate installing worker".
-      // "Run the update state"
+      mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
       mRegistration->mInstallingWorker = nullptr;
     }
 
     // FIXME(nsm): Plug in FetchDriver when it is ready.
     nsCOMPtr<nsIURI> uri;
     nsresult rv = NS_NewURI(getter_AddRefs(uri), mRegistration->mScriptSpec, nullptr, nullptr);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return Fail(rv);
@@ -471,45 +470,46 @@ private:
     Done(rv);
   }
 
   void
   ContinueInstall()
   {
     if (mRegistration->mInstallingWorker) {
       // FIXME(nsm): Terminate and stuff
+      mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
     }
 
     nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
     swm->InvalidateServiceWorkerRegistrationWorker(mRegistration,
                                                    WhichServiceWorker::INSTALLING_WORKER);
-    mRegistration->mInstallingWorker = new ServiceWorkerInfo(mRegistration->mScriptSpec);
+    mRegistration->mInstallingWorker = new ServiceWorkerInfo(mRegistration, mRegistration->mScriptSpec);
     mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Installing);
 
     Succeed();
 
     nsRefPtr<QueueFireUpdateFoundRunnable> upr =
       new QueueFireUpdateFoundRunnable(mRegistration);
     NS_DispatchToMainThread(upr);
 
     // XXXnsm this leads to double fetches right now, ideally we'll be able to
     // use the persistent cache later.
     nsRefPtr<ServiceWorkerJob> upcasted = this;
     nsMainThreadPtrHandle<nsISupports> handle(
         new nsMainThreadPtrHolder<nsISupports>(upcasted));
 
     nsRefPtr<ServiceWorker> serviceWorker;
     nsresult rv =
-      swm->CreateServiceWorker(mRegistration->mInstallingWorker->GetScriptSpec(),
+      swm->CreateServiceWorker(mRegistration->mInstallingWorker->ScriptSpec(),
                                mRegistration->mScope,
                                getter_AddRefs(serviceWorker));
 
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      // FIXME(nsm):
-      MOZ_CRASH("Shouldn't happen yet");
+      ContinueAfterInstallEvent(false /* success */, false /* activate immediately */);
+      return;
     }
 
     nsRefPtr<InstallEventRunnable> r =
       new InstallEventRunnable(serviceWorker->GetWorkerPrivate(), handle, mRegistration->mScope);
 
     AutoJSAPI jsapi;
     jsapi.Init();
     r->Dispatch(jsapi.cx());
@@ -971,22 +971,22 @@ ServiceWorkerRegistrationInfo::Activate(
 
   // XXXnsm I have my doubts about this. Leaving the main thread means that
   // subsequent calls to Activate() not from a Register() call, i.e. due to all
   // controlled documents going away, may lead to two or more calls being
   // interleaved.
   MOZ_ASSERT(mActiveWorker);
   nsRefPtr<ServiceWorker> serviceWorker;
   nsresult rv =
-    swm->CreateServiceWorker(mActiveWorker->GetScriptSpec(),
+    swm->CreateServiceWorker(mActiveWorker->ScriptSpec(),
                              mScope,
                              getter_AddRefs(serviceWorker));
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    // FIXME(nsm): Do the same thing as when "activate" event failed to
-    // dispatch.
+    FinishActivate(false /* success */);
+    return;
   }
 
   nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> handle(
     new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(this));
 
   nsRefPtr<ActivateEventRunnable> r =
     new ActivateEventRunnable(serviceWorker->GetWorkerPrivate(), handle);
 
@@ -1461,16 +1461,58 @@ ServiceWorkerRegistrationInfo::FinishAct
   if (aSuccess) {
     mActiveWorker->UpdateState(ServiceWorkerState::Activated);
   } else {
     mActiveWorker->UpdateState(ServiceWorkerState::Redundant);
     mActiveWorker = nullptr;
   }
 }
 
+void
+ServiceWorkerRegistrationInfo::QueueStateChangeEvent(ServiceWorkerInfo* aInfo,
+                                                     ServiceWorkerState aState) const
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aInfo);
+  MOZ_ASSERT(aInfo == mInstallingWorker ||
+             aInfo == mWaitingWorker ||
+             aInfo == mActiveWorker);
+
+  nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+  nsRefPtr<ServiceWorkerManager::ServiceWorkerDomainInfo> domainInfo =
+    swm->GetDomainInfo(mScope);
+
+  if (domainInfo) {
+    WhichServiceWorker whichOne;
+    if (aInfo == mInstallingWorker) {
+      whichOne = WhichServiceWorker::INSTALLING_WORKER;
+    } else if (aInfo == mWaitingWorker) {
+      whichOne = WhichServiceWorker::WAITING_WORKER;
+    } else if (aInfo == mActiveWorker) {
+      whichOne = WhichServiceWorker::ACTIVE_WORKER;
+    } else {
+      MOZ_CRASH("Hit unexpected case");
+    }
+
+    // Refactor this iteration pattern across this and 2 other call-sites.
+    nsTObserverArray<ServiceWorkerRegistration*>::ForwardIterator it(domainInfo->mServiceWorkerRegistrations);
+    while (it.HasMore()) {
+      nsRefPtr<ServiceWorkerRegistration> target = it.GetNext();
+      nsAutoString regScope;
+      target->GetScope(regScope);
+      MOZ_ASSERT(!regScope.IsEmpty());
+
+      NS_ConvertUTF16toUTF8 utf8Scope(regScope);
+      if (utf8Scope.Equals(mScope)) {
+        target->QueueStateChangeEvent(whichOne, aState);
+      }
+    }
+  }
+}
+
 NS_IMETHODIMP
 ServiceWorkerManager::CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow,
                                                    const nsACString& aScriptSpec,
                                                    const nsACString& aScope,
                                                    ServiceWorker** aServiceWorker)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aWindow);
@@ -1841,23 +1883,24 @@ ServiceWorkerManager::GetServiceWorkerFo
   }
 
   if (!info) {
     return NS_ERROR_DOM_NOT_FOUND_ERR;
   }
 
   nsRefPtr<ServiceWorker> serviceWorker;
   rv = CreateServiceWorkerForWindow(window,
-                                    info->GetScriptSpec(),
+                                    info->ScriptSpec(),
                                     registration->mScope,
                                     getter_AddRefs(serviceWorker));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  serviceWorker->SetState(info->State());
   serviceWorker.forget(aServiceWorker);
   return NS_OK;
 }
 
 /*
  * The .controller is for the registration associated with the document when
  * the document was loaded.
  */
@@ -1885,17 +1928,17 @@ ServiceWorkerManager::GetDocumentControl
   // If the document is controlled, the current worker MUST be non-null.
   if (!registration->mActiveWorker) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
 
   nsRefPtr<ServiceWorker> serviceWorker;
   nsresult rv = CreateServiceWorkerForWindow(window,
-                                             registration->mActiveWorker->GetScriptSpec(),
+                                             registration->mActiveWorker->ScriptSpec(),
                                              registration->mScope,
                                              getter_AddRefs(serviceWorker));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   serviceWorker.forget(aServiceWorker);
   return NS_OK;
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -28,64 +28,17 @@ class nsIScriptError;
 namespace mozilla {
 namespace dom {
 
 class ServiceWorkerRegistration;
 
 namespace workers {
 
 class ServiceWorker;
-
-/*
- * Wherever the spec treats a worker instance and a description of said worker
- * as the same thing; i.e. "Resolve foo with
- * _GetNewestWorker(serviceWorkerRegistration)", we represent the description
- * by this class and spawn a ServiceWorker in the right global when required.
- */
-class ServiceWorkerInfo MOZ_FINAL
-{
-  nsCString mScriptSpec;
-  ServiceWorkerState mState;
-
-  ~ServiceWorkerInfo()
-  { }
-
-public:
-  NS_INLINE_DECL_REFCOUNTING(ServiceWorkerInfo)
-
-  const nsCString&
-  GetScriptSpec() const
-  {
-    return mScriptSpec;
-  }
-
-  explicit ServiceWorkerInfo(const nsACString& aScriptSpec)
-    : mScriptSpec(aScriptSpec)
-    , mState(ServiceWorkerState::EndGuard_)
-  { }
-
-  void
-  UpdateState(ServiceWorkerState aState)
-  {
-#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
-    mState = aState;
-    // FIXME(nsm): Inform all relevant ServiceWorker instances.
-  }
-};
+class ServiceWorkerInfo;
 
 class ServiceWorkerJobQueue;
 
 class ServiceWorkerJob : public nsISupports
 {
   // The queue keeps the jobs alive, so they can hold a rawptr back to the
   // queue.
   ServiceWorkerJobQueue* mQueue;
@@ -220,16 +173,80 @@ public:
   void
   TryToActivate();
 
   void
   Activate();
 
   void
   FinishActivate(bool aSuccess);
+
+  void
+  QueueStateChangeEvent(ServiceWorkerInfo* aInfo,
+                        ServiceWorkerState aState) const;
+};
+
+/*
+ * Wherever the spec treats a worker instance and a description of said worker
+ * as the same thing; i.e. "Resolve foo with
+ * _GetNewestWorker(serviceWorkerRegistration)", we represent the description
+ * by this class and spawn a ServiceWorker in the right global when required.
+ */
+class ServiceWorkerInfo MOZ_FINAL
+{
+private:
+  const ServiceWorkerRegistrationInfo* mRegistration;
+  nsCString mScriptSpec;
+  ServiceWorkerState mState;
+
+  ~ServiceWorkerInfo()
+  { }
+
+public:
+  NS_INLINE_DECL_REFCOUNTING(ServiceWorkerInfo)
+
+  const nsCString&
+  ScriptSpec() const
+  {
+    return mScriptSpec;
+  }
+
+  explicit ServiceWorkerInfo(ServiceWorkerRegistrationInfo* aReg,
+                             const nsACString& aScriptSpec)
+    : mRegistration(aReg)
+    , mScriptSpec(aScriptSpec)
+    , mState(ServiceWorkerState::EndGuard_)
+  {
+    MOZ_ASSERT(mRegistration);
+  }
+
+  ServiceWorkerState
+  State() const
+  {
+    return mState;
+  }
+
+  void
+  UpdateState(ServiceWorkerState aState)
+  {
+#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
+    mState = aState;
+    mRegistration->QueueStateChangeEvent(this, mState);
+  }
 };
 
 #define NS_SERVICEWORKERMANAGER_IMPL_IID                 \
 { /* f4f8755a-69ca-46e8-a65d-775745535990 */             \
   0xf4f8755a,                                            \
   0x69ca,                                                \
   0x46e8,                                                \
   { 0xa6, 0x5d, 0x77, 0x57, 0x45, 0x53, 0x59, 0x90 }     \
--- a/dom/workers/ServiceWorkerRegistration.cpp
+++ b/dom/workers/ServiceWorkerRegistration.cpp
@@ -259,16 +259,38 @@ ServiceWorkerRegistration::InvalidateWor
     mWaitingWorker = nullptr;
   }
 
   if (aWhichOnes & WhichServiceWorker::ACTIVE_WORKER) {
     mActiveWorker = nullptr;
   }
 }
 
+void
+ServiceWorkerRegistration::QueueStateChangeEvent(WhichServiceWorker aWhichOne,
+                                                 ServiceWorkerState aState) const
+{
+  nsRefPtr<ServiceWorker> worker;
+  if (aWhichOne == WhichServiceWorker::INSTALLING_WORKER) {
+    worker = mInstallingWorker;
+  } else if (aWhichOne == WhichServiceWorker::WAITING_WORKER) {
+    worker = mWaitingWorker;
+  } else if (aWhichOne == WhichServiceWorker::ACTIVE_WORKER) {
+    worker = mActiveWorker;
+  } else {
+    MOZ_CRASH("Invalid case");
+  }
+
+  if (worker) {
+    worker->SetState(aState);
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(worker, &ServiceWorker::DispatchStateChange);
+    NS_DispatchToMainThread(r);
+  }
+}
+
 // XXXnsm, maybe this can be optimized to only add when a event handler is
 // registered.
 void
 ServiceWorkerRegistration::StartListeningForEvents()
 {
   nsCOMPtr<nsIServiceWorkerManager> swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID);
   if (swm) {
     swm->AddRegistrationEventListener(mScope, this);
--- a/dom/workers/ServiceWorkerRegistration.h
+++ b/dom/workers/ServiceWorkerRegistration.h
@@ -53,16 +53,19 @@ public:
 
   already_AddRefed<Promise>
   Unregister(ErrorResult& aRv);
 
   // Useful methods for ServiceWorkerManager:
   void
   InvalidateWorkerReference(WhichServiceWorker aWhichOnes);
 
+  void
+  QueueStateChangeEvent(WhichServiceWorker aWhichOne, ServiceWorkerState aState) const;
+
   // DOMEventTargethelper
   virtual void DisconnectFromOwner() MOZ_OVERRIDE;
 
 private:
   ~ServiceWorkerRegistration();
 
   already_AddRefed<workers::ServiceWorker>
   GetWorkerReference(WhichServiceWorker aWhichOne);