Bug 983497 - Patch 1: Infrastructure to fire events on ServiceWorkerContainers. r=ehsan,smaug
authorNikhil Marathe <nsm.nikhil@gmail.com>
Mon, 14 Jul 2014 14:15:23 -0700
changeset 216178 cd3ee2c040e19bde5ff9fdc4105918f648c3fb75
parent 216177 724724630cc956b57694906275ab7e13b44d5008
child 216179 8111e8d8875b94d7be94f097fc233319fe8cf42f
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan, smaug
bugs983497
milestone33.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 983497 - Patch 1: Infrastructure to fire events on ServiceWorkerContainers. r=ehsan,smaug
dom/events/DOMEventTargetHelper.h
dom/interfaces/base/nsIServiceWorkerManager.idl
dom/workers/ServiceWorkerContainer.cpp
dom/workers/ServiceWorkerContainer.h
dom/workers/ServiceWorkerManager.cpp
dom/workers/ServiceWorkerManager.h
--- a/dom/events/DOMEventTargetHelper.h
+++ b/dom/events/DOMEventTargetHelper.h
@@ -136,24 +136,25 @@ public:
   virtual void EventListenerAdded(nsIAtom* aType) MOZ_OVERRIDE;
   virtual void EventListenerRemoved(nsIAtom* aType) MOZ_OVERRIDE;
   virtual void EventListenerWasAdded(const nsAString& aType,
                                      ErrorResult& aRv,
                                      JSCompartment* aCompartment = nullptr) {}
   virtual void EventListenerWasRemoved(const nsAString& aType,
                                        ErrorResult& aRv,
                                        JSCompartment* aCompartment = nullptr) {}
+
+  // Dispatch a trusted, non-cancellable and non-bubbling event to |this|.
+  nsresult DispatchTrustedEvent(const nsAString& aEventName);
 protected:
   virtual ~DOMEventTargetHelper();
 
   nsresult WantsUntrusted(bool* aRetVal);
 
   nsRefPtr<EventListenerManager> mListenerManager;
-  // Dispatch a trusted, non-cancellable and non-bubbling event to |this|.
-  nsresult DispatchTrustedEvent(const nsAString& aEventName);
   // Make |event| trusted and dispatch |aEvent| to |this|.
   nsresult DispatchTrustedEvent(nsIDOMEvent* aEvent);
 
   virtual void LastRelease() {}
 private:
   // Inner window or sandbox.
   nsIGlobalObject*           mParentObject;
   // mParentObject pre QI-ed and cached (inner window)
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -1,23 +1,29 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "domstubs.idl"
 
-[uuid(44ef0b7e-92c0-48a7-a092-5a49f2533792)]
+interface nsIURI;
+
+[uuid(6117cdf1-cb10-42a3-9901-4f1bab7ffa4d)]
 interface nsIServiceWorkerManager : nsISupports
 {
   // Returns a Promise
   nsISupports register(in nsIDOMWindow aWindow, in DOMString aScope, in DOMString aScriptURI);
 
   // Returns a Promise
   nsISupports unregister(in nsIDOMWindow aWindow, in DOMString aScope);
 
+  // aTarget MUST be a ServiceWorkerContainer.
+  [noscript] void AddContainerEventListener(in nsIURI aPageURI, in nsIDOMEventTarget aTarget);
+  [noscript] void RemoveContainerEventListener(in nsIURI aPageURI, in nsIDOMEventTarget aTarget);
+
   // Testing
   DOMString getScopeForUrl(in DOMString path);
 };
 
 %{ C++
 #define SERVICEWORKERMANAGER_CONTRACTID "@mozilla.org/serviceworkers/manager;1"
 %}
--- a/dom/workers/ServiceWorkerContainer.cpp
+++ b/dom/workers/ServiceWorkerContainer.cpp
@@ -122,16 +122,36 @@ already_AddRefed<Promise>
 ServiceWorkerContainer::Ready()
 {
   // FIXME(nsm): Bug 1025077
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
   nsRefPtr<Promise> promise = new Promise(global);
   return promise.forget();
 }
 
+// XXXnsm, maybe this can be optimized to only add when a event handler is
+// registered.
+void
+ServiceWorkerContainer::StartListeningForEvents()
+{
+  nsCOMPtr<nsIServiceWorkerManager> swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID);
+  if (swm) {
+    swm->AddContainerEventListener(mWindow->GetDocumentURI(), this);
+  }
+}
+
+void
+ServiceWorkerContainer::StopListeningForEvents()
+{
+  nsCOMPtr<nsIServiceWorkerManager> swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID);
+  if (swm) {
+    swm->RemoveContainerEventListener(mWindow->GetDocumentURI(), this);
+  }
+}
+
 // Testing only.
 already_AddRefed<Promise>
 ServiceWorkerContainer::ClearAllServiceWorkerData(ErrorResult& aRv)
 {
   aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   return nullptr;
 }
 
--- a/dom/workers/ServiceWorkerContainer.h
+++ b/dom/workers/ServiceWorkerContainer.h
@@ -31,18 +31,18 @@ public:
   IMPL_EVENT_HANDLER(updatefound)
   IMPL_EVENT_HANDLER(controllerchange)
   IMPL_EVENT_HANDLER(reloadpage)
   IMPL_EVENT_HANDLER(error)
 
   explicit ServiceWorkerContainer(nsPIDOMWindow* aWindow)
     : mWindow(aWindow)
   {
-    // FIXME(nsm): Bug 983497. Here the NSW should hook into SWM to be notified of events.
     SetIsDOMBinding();
+    StartListeningForEvents();
   }
 
   nsPIDOMWindow*
   GetParentObject() const
   {
     return mWindow;
   }
 
@@ -70,35 +70,47 @@ public:
   GetController();
 
   already_AddRefed<Promise>
   GetAll(ErrorResult& aRv);
 
   already_AddRefed<Promise>
   Ready();
 
+  nsIURI*
+  GetDocumentURI() const
+  {
+    return mWindow->GetDocumentURI();
+  }
+
   // Testing only.
   already_AddRefed<Promise>
   ClearAllServiceWorkerData(ErrorResult& aRv);
 
   // Testing only.
   void
   GetScopeForUrl(const nsAString& aUrl, nsString& aScope, ErrorResult& aRv);
 
   // Testing only.
   void
   GetControllingWorkerScriptURLForPath(const nsAString& aPath,
                                        nsString& aScriptURL,
                                        ErrorResult& aRv);
 private:
   ~ServiceWorkerContainer()
   {
-    // FIXME(nsm): Bug 983497. Unhook from events.
+    StopListeningForEvents();
   }
 
+  void
+  StartListeningForEvents();
+
+  void
+  StopListeningForEvents();
+
   nsCOMPtr<nsPIDOMWindow> mWindow;
 };
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_workers_serviceworkercontainer_h__ */
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -1,14 +1,15 @@
 /* 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 "ServiceWorkerManager.h"
 
+#include "nsIDOMEventTarget.h"
 #include "nsIDocument.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsPIDOMWindow.h"
 
 #include "jsapi.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/BindingUtils.h"
@@ -362,20 +363,20 @@ public:
     nsCString domain;
     nsresult rv = mScriptURI->GetHost(domain);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mPromise->MaybeReject(rv);
       return NS_OK;
     }
 
     nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
-    ServiceWorkerManager::ServiceWorkerDomainInfo* domainInfo;
+    nsRefPtr<ServiceWorkerManager::ServiceWorkerDomainInfo> domainInfo;
     // XXXnsm: This pattern can be refactored if we end up using it
     // often enough.
-    if (!swm->mDomainMap.Get(domain, &domainInfo)) {
+    if (!swm->mDomainMap.Get(domain, getter_AddRefs(domainInfo))) {
       domainInfo = new ServiceWorkerManager::ServiceWorkerDomainInfo;
       swm->mDomainMap.Put(domain, domainInfo);
     }
 
     nsRefPtr<ServiceWorkerRegistration> registration =
       domainInfo->GetRegistration(mScope);
 
     nsCString spec;
@@ -676,18 +677,18 @@ ServiceWorkerManager::HandleError(JSCont
   }
 
   nsCString domain;
   rv = uri->GetHost(domain);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
-  ServiceWorkerDomainInfo* domainInfo;
-  if (!mDomainMap.Get(domain, &domainInfo)) {
+  nsRefPtr<ServiceWorkerDomainInfo> domainInfo;
+  if (!mDomainMap.Get(domain, getter_AddRefs(domainInfo))) {
     return;
   }
 
   nsCString scope;
   scope.Assign(aScope);
   nsRefPtr<ServiceWorkerRegistration> registration = domainInfo->GetRegistration(scope);
   MOZ_ASSERT(registration);
 
@@ -998,36 +999,23 @@ ServiceWorkerManager::GetServiceWorkerRe
 {
   nsCOMPtr<nsIURI> documentURI = aDoc->GetDocumentURI();
   return GetServiceWorkerRegistration(documentURI);
 }
 
 already_AddRefed<ServiceWorkerRegistration>
 ServiceWorkerManager::GetServiceWorkerRegistration(nsIURI* aURI)
 {
-  if (!aURI) {
-    return nullptr;
-  }
-
-  nsCString domain;
-  nsresult rv = aURI->GetHost(domain);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return nullptr;
-  }
-
-  ServiceWorkerDomainInfo* domainInfo = mDomainMap.Get(domain);
-
-  // XXXnsm: This pattern can be refactored if we end up using it
-  // often enough.
+  nsRefPtr<ServiceWorkerDomainInfo> domainInfo = GetDomainInfo(aURI);
   if (!domainInfo) {
     return nullptr;
   }
 
   nsCString spec;
-  rv = aURI->GetSpec(spec);
+  nsresult rv = aURI->GetSpec(spec);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return nullptr;
   }
 
   nsCString scope = FindScopeForPath(domainInfo->mOrderedScopes, spec);
   if (scope.IsEmpty()) {
     return nullptr;
   }
@@ -1121,16 +1109,55 @@ ServiceWorkerManager::FindScopeForPath(n
 }
 
 /* static */ void
 ServiceWorkerManager::RemoveScope(nsTArray<nsCString>& aList, const nsACString& aScope)
 {
   aList.RemoveElement(aScope);
 }
 
+already_AddRefed<ServiceWorkerManager::ServiceWorkerDomainInfo>
+ServiceWorkerManager::GetDomainInfo(nsIDocument* aDoc)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aDoc);
+  nsCOMPtr<nsIURI> documentURI = aDoc->GetDocumentURI();
+  return GetDomainInfo(documentURI);
+}
+
+already_AddRefed<ServiceWorkerManager::ServiceWorkerDomainInfo>
+ServiceWorkerManager::GetDomainInfo(nsIURI* aURI)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aURI);
+
+  nsCString domain;
+  nsresult rv = aURI->GetHost(domain);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  nsRefPtr<ServiceWorkerDomainInfo> domainInfo;
+  mDomainMap.Get(domain, getter_AddRefs(domainInfo));
+  return domainInfo.forget();
+}
+
+already_AddRefed<ServiceWorkerManager::ServiceWorkerDomainInfo>
+ServiceWorkerManager::GetDomainInfo(const nsCString& aURL)
+{
+  AssertIsOnMainThread();
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  return GetDomainInfo(uri);
+}
+
 NS_IMETHODIMP
 ServiceWorkerManager::GetScopeForUrl(const nsAString& aUrl, nsAString& aScope)
 {
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NS_NewURI(getter_AddRefs(uri), aUrl, nullptr, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NS_ERROR_FAILURE;
   }
@@ -1139,16 +1166,89 @@ ServiceWorkerManager::GetScopeForUrl(con
   if (!r) {
       return NS_ERROR_FAILURE;
   }
 
   aScope = NS_ConvertUTF8toUTF16(r->mScope);
   return NS_OK;
 }
 NS_IMETHODIMP
+ServiceWorkerManager::AddContainerEventListener(nsIURI* aDocumentURI, nsIDOMEventTarget* aListener)
+{
+  MOZ_ASSERT(aDocumentURI);
+  nsRefPtr<ServiceWorkerDomainInfo> domainInfo = GetDomainInfo(aDocumentURI);
+  if (!domainInfo) {
+    nsCString domain;
+    nsresult rv = aDocumentURI->GetHost(domain);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    domainInfo = new ServiceWorkerDomainInfo;
+    mDomainMap.Put(domain, domainInfo);
+  }
+
+  MOZ_ASSERT(domainInfo);
+
+  ServiceWorkerContainer* container = static_cast<ServiceWorkerContainer*>(aListener);
+  domainInfo->mServiceWorkerContainers.AppendElement(container);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::RemoveContainerEventListener(nsIURI* aDocumentURI, nsIDOMEventTarget* aListener)
+{
+  MOZ_ASSERT(aDocumentURI);
+  nsRefPtr<ServiceWorkerDomainInfo> domainInfo = GetDomainInfo(aDocumentURI);
+  if (!domainInfo) {
+    return NS_OK;
+  }
+
+  ServiceWorkerContainer* container = static_cast<ServiceWorkerContainer*>(aListener);
+  domainInfo->mServiceWorkerContainers.RemoveElement(container);
+  return NS_OK;
+}
+
+void
+ServiceWorkerManager::FireEventOnServiceWorkerContainers(
+  ServiceWorkerRegistration* aRegistration,
+  const nsAString& aName)
+{
+  AssertIsOnMainThread();
+  nsRefPtr<ServiceWorkerDomainInfo> domainInfo =
+    GetDomainInfo(aRegistration->mScriptSpec);
+
+  if (domainInfo) {
+    nsTObserverArray<ServiceWorkerContainer*>::ForwardIterator it(domainInfo->mServiceWorkerContainers);
+    while (it.HasMore()) {
+      nsRefPtr<ServiceWorkerContainer> target = it.GetNext();
+      nsIURI* targetURI = target->GetDocumentURI();
+      if (!targetURI) {
+        NS_WARNING("Controlled domain cannot have page with null URI!");
+        continue;
+      }
+
+      nsCString path;
+      nsresult rv = targetURI->GetSpec(path);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        continue;
+      }
+
+      nsCString scope = FindScopeForPath(domainInfo->mOrderedScopes, path);
+      if (scope.IsEmpty() ||
+          !scope.Equals(aRegistration->mScope)) {
+        continue;
+      }
+
+      target->DispatchTrustedEvent(aName);
+    }
+  }
+}
+
+NS_IMETHODIMP
 ServiceWorkerManager::CreateServiceWorker(const nsACString& aScriptSpec,
                                           const nsACString& aScope,
                                           ServiceWorker** aServiceWorker)
 {
   AssertIsOnMainThread();
 
   WorkerPrivate::LoadInfo info;
   nsresult rv = NS_NewURI(getter_AddRefs(info.mBaseURI), aScriptSpec, nullptr, nullptr);
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -7,20 +7,20 @@
 
 #include "nsIServiceWorkerManager.h"
 #include "nsCOMPtr.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/Promise.h"
-#include "nsClassHashtable.h"
-#include "nsDataHashtable.h"
+#include "mozilla/dom/ServiceWorkerContainer.h"
 #include "nsRefPtrHashtable.h"
 #include "nsTArrayForwardDeclare.h"
+#include "nsTObserverArray.h"
 #include "nsTWeakRef.h"
 
 class nsIScriptError;
 
 namespace mozilla {
 namespace dom {
 namespace workers {
 
@@ -205,16 +205,22 @@ public:
     // domain is expected to be relatively low. If that assumption was proved
     // wrong this should be replaced with a better structure to avoid the
     // memmoves associated with inserting stuff in the middle of the array.
     nsTArray<nsCString> mOrderedScopes;
 
     // Scope to registration.
     nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerRegistration> mServiceWorkerRegistrations;
 
+    // This array can't be stored in ServiceWorkerRegistration because one may
+    // not exist when a certain window is opened, but we still want that
+    // window's container to be notified if it's in scope.
+    // The containers inform the SWM on creation and destruction.
+    nsTObserverArray<ServiceWorkerContainer*> mServiceWorkerContainers;
+
     ServiceWorkerDomainInfo()
     { }
 
     already_AddRefed<ServiceWorkerRegistration>
     GetRegistration(const nsCString& aScope) const
     {
       nsRefPtr<ServiceWorkerRegistration> reg;
       mServiceWorkerRegistrations.Get(aScope, getter_AddRefs(reg));
@@ -227,19 +233,25 @@ public:
       ServiceWorkerRegistration* registration =
         new ServiceWorkerRegistration(aScope);
       // From now on ownership of registration is with
       // mServiceWorkerRegistrations.
       mServiceWorkerRegistrations.Put(aScope, registration);
       ServiceWorkerManager::AddScope(mOrderedScopes, aScope);
       return registration;
     }
+
+    NS_INLINE_DECL_REFCOUNTING(ServiceWorkerDomainInfo)
+
+  private:
+    ~ServiceWorkerDomainInfo()
+    { }
   };
 
-  nsClassHashtable<nsCStringHashKey, ServiceWorkerDomainInfo> mDomainMap;
+  nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerDomainInfo> mDomainMap;
 
   void
   ResolveRegisterPromises(ServiceWorkerRegistration* aRegistration,
                           const nsACString& aWorkerScriptSpec);
 
   void
   RejectUpdatePromiseObservers(ServiceWorkerRegistration* aRegistration,
                                nsresult aResult);
@@ -291,16 +303,25 @@ private:
                       const nsACString& aScope,
                       ServiceWorker** aServiceWorker);
 
   static PLDHashOperator
   CleanupServiceWorkerInformation(const nsACString& aDomain,
                                   ServiceWorkerDomainInfo* aDomainInfo,
                                   void *aUnused);
 
+  already_AddRefed<ServiceWorkerDomainInfo>
+  GetDomainInfo(nsIDocument* aDoc);
+
+  already_AddRefed<ServiceWorkerDomainInfo>
+  GetDomainInfo(nsIURI* aURI);
+
+  already_AddRefed<ServiceWorkerDomainInfo>
+  GetDomainInfo(const nsCString& aURL);
+
   already_AddRefed<ServiceWorkerRegistration>
   GetServiceWorkerRegistration(nsPIDOMWindow* aWindow);
 
   already_AddRefed<ServiceWorkerRegistration>
   GetServiceWorkerRegistration(nsIDocument* aDoc);
 
   already_AddRefed<ServiceWorkerRegistration>
   GetServiceWorkerRegistration(nsIURI* aURI);
@@ -309,16 +330,20 @@ private:
   AddScope(nsTArray<nsCString>& aList, const nsACString& aScope);
 
   static nsCString
   FindScopeForPath(nsTArray<nsCString>& aList, const nsACString& aPath);
 
   static void
   RemoveScope(nsTArray<nsCString>& aList, const nsACString& aScope);
 
+  void
+  FireEventOnServiceWorkerContainers(ServiceWorkerRegistration* aRegistration,
+                                     const nsAString& aName);
+
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(ServiceWorkerManager,
                               NS_SERVICEWORKERMANAGER_IMPL_IID);
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla