Bug 1025077 - Implement ServiceWorkerContainer.ready, r=nsm
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 26 Aug 2014 09:16:03 +0100
changeset 201628 e48a1782de89eca7b4de7f96b2bbe00cc29b3230
parent 201627 68bb8434fbec690eb9e41fd0daf281cbefff2c5d
child 201629 06168844e276351a29996224b3cbc28bec3c1a53
push id27375
push userryanvm@gmail.com
push dateTue, 26 Aug 2014 19:56:59 +0000
treeherdermozilla-central@f9bfe115fee5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnsm
bugs1025077
milestone34.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 1025077 - Implement ServiceWorkerContainer.ready, r=nsm
dom/interfaces/base/nsIServiceWorkerManager.idl
dom/workers/ServiceWorkerContainer.cpp
dom/workers/ServiceWorkerContainer.h
dom/workers/ServiceWorkerManager.cpp
dom/workers/ServiceWorkerManager.h
dom/workers/test/serviceworkers/mochitest.ini
dom/workers/test/serviceworkers/simpleregister/ready.html
dom/workers/test/serviceworkers/test_installation_simple.html
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -3,17 +3,17 @@
  * 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"
 
 interface nsIDocument;
 interface nsIURI;
 
-[uuid(c7132f91-c46c-4e01-b75a-43babb254d93)]
+[uuid(91b9d3fc-1654-44da-b438-15123cdbe7aa)]
 interface nsIServiceWorkerManager : nsISupports
 {
   /**
    * Registers a ServiceWorker with script loaded from `aScriptURI` to act as
    * the ServiceWorker for aScope.  Requires a valid entry settings object on
    * the stack. This means you must call this from content code 'within'
    * a window.
    *
@@ -31,16 +31,22 @@ interface nsIServiceWorkerManager : nsIS
   nsISupports unregister(in DOMString aScope);
 
   // Returns a Promise
   nsISupports getRegistrations(in nsIDOMWindow aWindow);
 
   // Returns a Promise
   nsISupports getRegistration(in nsIDOMWindow aWindow, in DOMString aScope);
 
+  // Returns a Promise
+  nsISupports getReadyPromise(in nsIDOMWindow aWindow);
+
+  // Remove ready pending Promise
+  void removeReadyPromise(in nsIDOMWindow aWindow);
+
   // aTarget MUST be a ServiceWorkerRegistration.
   [noscript] void AddRegistrationEventListener(in nsIURI aPageURI, in nsIDOMEventTarget aTarget);
   [noscript] void RemoveRegistrationEventListener(in nsIURI aPageURI, in nsIDOMEventTarget aTarget);
 
   /**
    * Call this to request that document `aDoc` be controlled by a ServiceWorker
    * if a registration exists for it's scope.
    *
--- a/dom/workers/ServiceWorkerContainer.cpp
+++ b/dom/workers/ServiceWorkerContainer.cpp
@@ -25,28 +25,38 @@ namespace dom {
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServiceWorkerContainer)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper)
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper,
-                                   mControllerWorker)
+                                   mControllerWorker, mReadyPromise)
 
 ServiceWorkerContainer::ServiceWorkerContainer(nsPIDOMWindow* aWindow)
-  : mWindow(aWindow)
+  : DOMEventTargetHelper(aWindow)
 {
-  SetIsDOMBinding();
 }
 
 ServiceWorkerContainer::~ServiceWorkerContainer()
 {
 }
 
+void
+ServiceWorkerContainer::DisconnectFromOwner()
+{
+  nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
+  MOZ_ASSERT(swm);
+
+  swm->RemoveReadyPromise(GetOwner());
+
+  DOMEventTargetHelper::DisconnectFromOwner();
+}
+
 JSObject*
 ServiceWorkerContainer::WrapObject(JSContext* aCx)
 {
   return ServiceWorkerContainerBinding::Wrap(aCx, this);
 }
 
 already_AddRefed<Promise>
 ServiceWorkerContainer::Register(const nsAString& aScriptURL,
@@ -77,17 +87,17 @@ ServiceWorkerContainer::GetController()
   if (!mControllerWorker) {
     nsresult rv;
     nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
     if (!swm) {
       return nullptr;
     }
 
     nsCOMPtr<nsISupports> serviceWorker;
-    rv = swm->GetDocumentController(mWindow, getter_AddRefs(serviceWorker));
+    rv = swm->GetDocumentController(GetOwner(), getter_AddRefs(serviceWorker));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return nullptr;
     }
 
     mControllerWorker =
       static_cast<workers::ServiceWorker*>(serviceWorker.get());
   }
 
@@ -101,17 +111,17 @@ ServiceWorkerContainer::GetRegistrations
   nsresult rv;
   nsCOMPtr<nsIServiceWorkerManager> swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(rv);
     return nullptr;
   }
 
   nsCOMPtr<nsISupports> promise;
-  aRv = swm->GetRegistrations(mWindow, getter_AddRefs(promise));
+  aRv = swm->GetRegistrations(GetOwner(), getter_AddRefs(promise));
   if (aRv.Failed()) {
     return nullptr;
   }
 
   nsRefPtr<Promise> ret = static_cast<Promise*>(promise.get());
   MOZ_ASSERT(ret);
   return ret.forget();
 }
@@ -123,32 +133,44 @@ ServiceWorkerContainer::GetRegistration(
   nsresult rv;
   nsCOMPtr<nsIServiceWorkerManager> swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(rv);
     return nullptr;
   }
 
   nsCOMPtr<nsISupports> promise;
-  aRv = swm->GetRegistration(mWindow, aDocumentURL, getter_AddRefs(promise));
+  aRv = swm->GetRegistration(GetOwner(), aDocumentURL, getter_AddRefs(promise));
   if (aRv.Failed()) {
     return nullptr;
   }
 
   nsRefPtr<Promise> ret = static_cast<Promise*>(promise.get());
   MOZ_ASSERT(ret);
   return ret.forget();
 }
 
-already_AddRefed<Promise>
+Promise*
 ServiceWorkerContainer::GetReady(ErrorResult& aRv)
 {
-  // FIXME(nsm): Bug 1025077
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
-  return Promise::Create(global, aRv);
+  if (mReadyPromise) {
+    return mReadyPromise;
+  }
+
+  nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
+  if (!swm) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsCOMPtr<nsISupports> promise;
+  aRv = swm->GetReadyPromise(GetOwner(), getter_AddRefs(promise));
+
+  mReadyPromise = static_cast<Promise*>(promise.get());
+  return mReadyPromise;
 }
 
 // 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
@@ -29,22 +29,16 @@ public:
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper)
 
   IMPL_EVENT_HANDLER(controllerchange)
   IMPL_EVENT_HANDLER(reloadpage)
   IMPL_EVENT_HANDLER(error)
 
   explicit ServiceWorkerContainer(nsPIDOMWindow* aWindow);
 
-  nsPIDOMWindow*
-  GetParentObject() const
-  {
-    return mWindow;
-  }
-
   JSObject*
   WrapObject(JSContext* aCx);
 
   already_AddRefed<Promise>
   Register(const nsAString& aScriptURL,
            const RegistrationOptionList& aOptions,
            ErrorResult& aRv);
 
@@ -53,39 +47,43 @@ public:
 
   already_AddRefed<Promise>
   GetRegistration(const nsAString& aDocumentURL,
                   ErrorResult& aRv);
 
   already_AddRefed<Promise>
   GetRegistrations(ErrorResult& aRv);
 
-  already_AddRefed<Promise>
+  Promise*
   GetReady(ErrorResult& aRv);
 
   // 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);
+
+  // DOMEventTargetHelper
+  void DisconnectFromOwner() MOZ_OVERRIDE;
+
 private:
   ~ServiceWorkerContainer();
 
-  nsCOMPtr<nsPIDOMWindow> mWindow;
-
   // This only changes when a worker hijacks everything in its scope by calling
   // replace().
   // FIXME(nsm): Bug 982711. Provide API to let SWM invalidate this.
   nsRefPtr<workers::ServiceWorker> mControllerWorker;
+
+  nsRefPtr<Promise> mReadyPromise;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_workers_serviceworkercontainer_h__ */
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -815,16 +815,152 @@ ServiceWorkerManager::GetRegistration(ns
   }
 
   nsRefPtr<nsIRunnable> runnable =
     new GetRegistrationRunnable(window, promise, aDocumentURL);
   promise.forget(aPromise);
   return NS_DispatchToCurrentThread(runnable);
 }
 
+class GetReadyPromiseRunnable : public nsRunnable
+{
+  nsCOMPtr<nsPIDOMWindow> mWindow;
+  nsRefPtr<Promise> mPromise;
+
+public:
+  GetReadyPromiseRunnable(nsPIDOMWindow* aWindow, Promise* aPromise)
+    : mWindow(aWindow), mPromise(aPromise)
+  { }
+
+  NS_IMETHODIMP
+  Run()
+  {
+    nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+
+    nsIDocument* doc = mWindow->GetExtantDoc();
+    if (!doc) {
+      mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+      return NS_OK;
+    }
+
+    nsCOMPtr<nsIURI> docURI = doc->GetDocumentURI();
+    if (!docURI) {
+      mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+      return NS_OK;
+    }
+
+    if (!swm->CheckReadyPromise(mWindow, docURI, mPromise)) {
+      swm->StorePendingReadyPromise(mWindow, docURI, mPromise);
+    }
+
+    return NS_OK;
+  }
+};
+
+NS_IMETHODIMP
+ServiceWorkerManager::GetReadyPromise(nsIDOMWindow* aWindow,
+                                      nsISupports** aPromise)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aWindow);
+
+  // XXXnsm Don't allow chrome callers for now, we don't support chrome
+  // ServiceWorkers.
+  MOZ_ASSERT(!nsContentUtils::IsCallerChrome());
+
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
+  if (!window) {
+    return NS_ERROR_FAILURE;
+  }
+
+  MOZ_ASSERT(!mPendingReadyPromises.Contains(window));
+
+  nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
+  ErrorResult result;
+  nsRefPtr<Promise> promise = Promise::Create(sgo, result);
+  if (result.Failed()) {
+    return result.ErrorCode();
+  }
+
+  nsRefPtr<nsIRunnable> runnable =
+    new GetReadyPromiseRunnable(window, promise);
+  promise.forget(aPromise);
+  return NS_DispatchToCurrentThread(runnable);
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::RemoveReadyPromise(nsIDOMWindow* aWindow)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aWindow);
+
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
+  if (!window) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mPendingReadyPromises.Remove(aWindow);
+  return NS_OK;
+}
+
+void
+ServiceWorkerManager::StorePendingReadyPromise(nsPIDOMWindow* aWindow,
+                                               nsIURI* aURI,
+                                               Promise* aPromise)
+{
+  PendingReadyPromise* data;
+
+  // We should not have 2 pending promises for the same window.
+  MOZ_ASSERT(!mPendingReadyPromises.Get(aWindow, &data));
+
+  data = new PendingReadyPromise(aURI, aPromise);
+  mPendingReadyPromises.Put(aWindow, data);
+}
+
+void
+ServiceWorkerManager::CheckPendingReadyPromises()
+{
+  mPendingReadyPromises.Enumerate(CheckPendingReadyPromisesEnumerator, this);
+}
+
+PLDHashOperator
+ServiceWorkerManager::CheckPendingReadyPromisesEnumerator(
+                                          nsISupports* aSupports,
+                                          nsAutoPtr<PendingReadyPromise>& aData,
+                                          void* aPtr)
+{
+  ServiceWorkerManager* aSwm = static_cast<ServiceWorkerManager*>(aPtr);
+
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aSupports);
+
+  if (aSwm->CheckReadyPromise(window, aData->mURI, aData->mPromise)) {
+    return PL_DHASH_REMOVE;
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+bool
+ServiceWorkerManager::CheckReadyPromise(nsPIDOMWindow* aWindow,
+                                        nsIURI* aURI, Promise* aPromise)
+{
+  nsRefPtr<ServiceWorkerRegistrationInfo> registration =
+    GetServiceWorkerRegistrationInfo(aURI);
+
+  if (registration && registration->mCurrentWorker) {
+    NS_ConvertUTF8toUTF16 scope(registration->mScope);
+    nsRefPtr<ServiceWorkerRegistration> swr =
+      new ServiceWorkerRegistration(aWindow, scope);
+    aPromise->MaybeResolve(swr);
+    return true;
+  }
+
+  return false;
+}
+
 void
 ServiceWorkerManager::RejectUpdatePromiseObservers(ServiceWorkerRegistrationInfo* aRegistration,
                                                    nsresult aRv)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aRegistration->HasUpdatePromise());
   aRegistration->mUpdatePromise->RejectAllPromises(aRv);
   aRegistration->mUpdatePromise = nullptr;
@@ -1401,16 +1537,18 @@ public:
     nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
     swm->InvalidateServiceWorkerRegistrationWorker(mRegistration,
                                                    WhichServiceWorker::ACTIVE_WORKER | WhichServiceWorker::WAITING_WORKER);
     if (!mRegistration->mCurrentWorker) {
       // FIXME(nsm): Just got unregistered!
       return NS_OK;
     }
 
+    swm->CheckPendingReadyPromises();
+
     // FIXME(nsm): Steps 7 of the algorithm.
 
     swm->FireEventOnServiceWorkerRegistrations(mRegistration,
                                                NS_LITERAL_STRING("controllerchange"));
 
     MOZ_ASSERT(mRegistration->mCurrentWorker);
     nsRefPtr<ServiceWorker> serviceWorker;
     nsresult rv =
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -15,16 +15,17 @@
 #include "mozilla/TypedEnumBits.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ServiceWorkerCommon.h"
 #include "nsRefPtrHashtable.h"
 #include "nsTArrayForwardDeclare.h"
 #include "nsTObserverArray.h"
+#include "nsClassHashtable.h"
 
 class nsIScriptError;
 
 namespace mozilla {
 namespace dom {
 
 class ServiceWorkerRegistration;
 
@@ -194,16 +195,17 @@ public:
 class ServiceWorkerManager MOZ_FINAL : public nsIServiceWorkerManager
 {
   friend class ActivationRunnable;
   friend class RegisterRunnable;
   friend class CallInstallRunnable;
   friend class CancelServiceWorkerInstallationRunnable;
   friend class ServiceWorkerRegistrationInfo;
   friend class ServiceWorkerUpdateInstance;
+  friend class GetReadyPromiseRunnable;
   friend class GetRegistrationsRunnable;
   friend class GetRegistrationRunnable;
   friend class UnregisterRunnable;
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSISERVICEWORKERMANAGER
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_SERVICEWORKERMANAGER_IMPL_IID)
@@ -384,16 +386,41 @@ private:
 
   static void
   RemoveScope(nsTArray<nsCString>& aList, const nsACString& aScope);
 
   void
   FireEventOnServiceWorkerRegistrations(ServiceWorkerRegistrationInfo* aRegistration,
                                         const nsAString& aName);
 
+  void
+  StorePendingReadyPromise(nsPIDOMWindow* aWindow, nsIURI* aURI, Promise* aPromise);
+
+  void
+  CheckPendingReadyPromises();
+
+  bool
+  CheckReadyPromise(nsPIDOMWindow* aWindow, nsIURI* aURI, Promise* aPromise);
+
+  struct PendingReadyPromise
+  {
+    PendingReadyPromise(nsIURI* aURI, Promise* aPromise)
+      : mURI(aURI), mPromise(aPromise)
+    { }
+
+    nsCOMPtr<nsIURI> mURI;
+    nsRefPtr<Promise> mPromise;
+  };
+
+  static PLDHashOperator
+  CheckPendingReadyPromisesEnumerator(nsISupports* aSupports,
+                                      nsAutoPtr<PendingReadyPromise>& aData,
+                                      void* aUnused);
+
+  nsClassHashtable<nsISupportsHashKey, PendingReadyPromise> mPendingReadyPromises;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(ServiceWorkerManager,
                               NS_SERVICEWORKERMANAGER_IMPL_IID);
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -2,16 +2,17 @@
 run-if = os == "win" # Bug 1040924 - Failure prone on !Windows
 support-files =
   worker.js
   worker2.js
   worker3.js
   parse_error_worker.js
   install_event_worker.js
   simpleregister/index.html
+  simpleregister/ready.html
   controller/index.html
   unregister/index.html
 
 [test_installation_simple.html]
 [test_install_event.html]
 [test_navigator.html]
 [test_scopes.html]
 [test_controller.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/simpleregister/ready.html
@@ -0,0 +1,15 @@
+<html>
+  <head></head>
+  <body>
+    <script type="text/javascript">
+
+       window.addEventListener('message', function(evt) {
+         navigator.serviceWorker.ready.then(function() {
+           evt.ports[0].postMessage("WOW!");
+         });
+       }, false);
+
+    </script>
+  </body>
+</html>
+
--- a/dom/workers/test/serviceworkers/test_installation_simple.html
+++ b/dom/workers/test/serviceworkers/test_installation_simple.html
@@ -136,37 +136,65 @@
         resolve();
       } else if (e.data.type == "check") {
         ok(e.data.status, e.data.msg);
       }
     }
     return p;
   }
 
+  var readyPromiseResolved = false;
+
+  function readyPromise() {
+    var frame = document.createElement("iframe");
+    frame.setAttribute("id", "simpleregister-frame-ready");
+    frame.setAttribute("src", new URL("simpleregister/ready.html", document.baseURI).href);
+    document.body.appendChild(frame);
+
+    var channel = new MessageChannel();
+    frame.addEventListener('load', function() {
+      frame.contentWindow.postMessage('your port!', '*', [channel.port2]);
+    }, false);
+
+    channel.port1.onmessage = function() {
+      readyPromiseResolved = true;
+    }
+
+    return Promise.resolve();
+  }
+
+  function checkReadyPromise() {
+    ok(readyPromiseResolved, "The ready promise has been resolved!");
+    return Promise.resolve();
+  }
+
   function runTest() {
     simpleRegister()
+      .then(readyPromise)
       .then(sameOriginWorker)
       .then(sameOriginScope)
       .then(httpsOnly)
       .then(realWorker)
       .then(abortPrevious)
       .then(networkError404)
       .then(parseError)
       .then(updatefound)
+      .then(checkReadyPromise)
       // put more tests here.
       .then(function() {
         SimpleTest.finish();
       }).catch(function(e) {
         ok(false, "Some test failed with error " + e);
         SimpleTest.finish();
       });
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
+    ["dom.messageChannel.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>