1011268-swm-unregister
author Nikhil Marathe <nsm.nikhil@gmail.com>
Fri, 11 Jul 2014 17:58:38 -0700
changeset 1103 3e505e3ac2b279fcc039bb9080b42115e9e43c32
parent 1068 935eb36f165fd56fd39dcb8050857837cdd6db2c
permissions -rw-r--r--
UPDATE: 1011268-swm-unregister dom/workers/ServiceWorkerManager.cpp | 4 ++++ dom/workers/test/serviceworkers/mochitest.ini | 2 ++ 2 files changed, 6 insertions(+), 0 deletions(-) qparent: 6b8638719063 qtip: 810db2a5835d top: 1011268-swm-unregister

# HG changeset patch
# Parent 6b863871906376f504b6d6fa2829172d7891e8d9
# User Nikhil Marathe <nsm.nikhil@gmail.com>
Bug 1011268: Unregister algorithm. r=ehsan

diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -431,16 +431,94 @@ public:
     // for the same registration) are aborted. Update() sets a new
     // UpdatePromise on the registration.
     registration->mUpdatePromise->AddPromise(mPromise);
 
     return rv;
   }
 };
 
+/*
+ * Implements the async aspects of the unregister algorithm.
+ */
+class UnregisterRunnable : public nsRunnable
+{
+  nsCOMPtr<nsPIDOMWindow> mWindow;
+  nsCOMPtr<nsIURI> mScopeURI;
+  nsRefPtr<Promise> mPromise;
+public:
+  UnregisterRunnable(nsPIDOMWindow* aWindow, nsIURI* aScopeURI,
+                     Promise* aPromise)
+    : mWindow(aWindow), mScopeURI(aScopeURI), mPromise(aPromise)
+  {
+    AssertIsOnMainThread();
+  }
+
+  NS_IMETHODIMP
+  Run()
+  {
+    AssertIsOnMainThread();
+
+    nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+
+    ServiceWorkerManager::ServiceWorkerDomainInfo* domainInfo =
+      swm->GetDomainInfo(mScopeURI);
+    MOZ_ASSERT(domainInfo);
+
+    if (!domainInfo) {
+      mPromise->MaybeResolve(JS::UndefinedHandleValue);
+      return NS_OK;
+    }
+
+    nsCString spec;
+    nsresult rv = mScopeURI->GetSpecIgnoringRef(spec);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mPromise->MaybeReject(rv);
+      return NS_OK;
+    }
+
+    nsRefPtr<ServiceWorkerRegistration> registration;
+    if (!domainInfo->mServiceWorkerRegistrations.Get(spec,
+                                                     getter_AddRefs(registration))) {
+      mPromise->MaybeResolve(JS::UndefinedHandleValue);
+      return NS_OK;
+    }
+
+    MOZ_ASSERT(registration);
+
+    registration->mPendingUninstall = true;
+
+    if (registration->HasUpdatePromise()) {
+      swm->AbortCurrentUpdate(registration);
+    }
+
+    if (registration->mInstallingWorker) {
+      // FIXME(nsm): Terminate installing worker.
+      // Set state to redundant.
+      // Fire statechange.
+      registration->mInstallingWorker = nullptr;
+      // FIXME(nsm): Abort any inflight requests from installing worker.
+    }
+
+    if (registration->mWaitingWorker) {
+      // FIXME(nsm): Set state to redundant.
+      // Fire statechange.
+      registration->mWaitingWorker = nullptr;
+    }
+
+    mPromise->MaybeResolve(JS::UndefinedHandleValue);
+
+    if (!registration->IsControllingDocuments()) {
+      domainInfo->RemoveRegistration(registration);
+    }
+
+    return NS_OK;
+  }
+};
+
 // If we return an error code here, the ServiceWorkerContainer will
 // automatically reject the Promise.
 NS_IMETHODIMP
 ServiceWorkerManager::Register(nsIDOMWindow* aWindow, const nsAString& aScope,
                                const nsAString& aScriptURL,
                                nsISupports** aPromise)
 {
   AssertIsOnMainThread();
@@ -549,20 +627,17 @@ ServiceWorkerManager::RejectUpdatePromis
  * may access the registration's (new) Promise after calling this method.
  */
 NS_IMETHODIMP
 ServiceWorkerManager::Update(ServiceWorkerRegistration* aRegistration,
                              nsPIDOMWindow* aWindow)
 {
   if (aRegistration->HasUpdatePromise()) {
     NS_WARNING("Already had a UpdatePromise. Aborting that one!");
-    RejectUpdatePromiseObservers(aRegistration, NS_ERROR_DOM_ABORT_ERR);
-    MOZ_ASSERT(aRegistration->mUpdateInstance);
-    aRegistration->mUpdateInstance->Abort();
-    aRegistration->mUpdateInstance = nullptr;
+    AbortCurrentUpdate(aRegistration);
   }
 
   if (aRegistration->mInstallingWorker) {
     // FIXME(nsm): Terminate the worker. We still haven't figured out worker
     // instance ownership when not associated with a window, so let's wait on
     // this.
     // FIXME(nsm): We should be setting the state on the actual worker
     // instance.
@@ -578,30 +653,79 @@ ServiceWorkerManager::Update(ServiceWork
 
   aRegistration->mUpdateInstance =
     new ServiceWorkerUpdateInstance(aRegistration, aWindow);
   aRegistration->mUpdateInstance->Update();
 
   return NS_OK;
 }
 
+void
+ServiceWorkerManager::AbortCurrentUpdate(ServiceWorkerRegistration* aRegistration)
+{
+  MOZ_ASSERT(aRegistration->HasUpdatePromise());
+  RejectUpdatePromiseObservers(aRegistration, NS_ERROR_DOM_ABORT_ERR);
+  MOZ_ASSERT(aRegistration->mUpdateInstance);
+  aRegistration->mUpdateInstance->Abort();
+  aRegistration->mUpdateInstance = nullptr;
+}
+
 // If we return an error, ServiceWorkerContainer will reject the Promise.
 NS_IMETHODIMP
 ServiceWorkerManager::Unregister(nsIDOMWindow* aWindow, const nsAString& aScope,
                                  nsISupports** aPromise)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aWindow);
 
   // XXXnsm Don't allow chrome callers for now.
   MOZ_ASSERT(!nsContentUtils::IsCallerChrome());
 
-  // FIXME(nsm): Same bug, different patch.
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
+  if (!window) {
+    return NS_ERROR_FAILURE;
+  }
 
-  return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+  nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
+  nsRefPtr<Promise> promise = new Promise(sgo);
+
+  nsCOMPtr<nsIURI> documentURI = window->GetDocumentURI();
+  if (!documentURI) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Although the spec says that the same-origin checks should also be done
+  // asynchronously, we do them in sync because the Promise created by the
+  // WebIDL infrastructure due to a returned error will be resolved
+  // asynchronously. We aren't making any internal state changes in these
+  // checks, so ordering of multiple calls is not affected.
+
+  nsCOMPtr<nsIPrincipal> documentPrincipal;
+  if (window->GetExtantDoc()) {
+    documentPrincipal = window->GetExtantDoc()->NodePrincipal();
+  } else {
+    documentPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1");
+  }
+
+  nsCOMPtr<nsIURI> scopeURI;
+  nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, documentURI);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  rv = documentPrincipal->CheckMayLoad(scopeURI, true /* report */,
+                                       false /* allowIfInheritsPrinciple */);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsRefPtr<nsIRunnable> unregisterRunnable =
+    new UnregisterRunnable(window, scopeURI, promise);
+  promise.forget(aPromise);
+  return NS_DispatchToCurrentThread(unregisterRunnable);
 }
 
 /* static */
 already_AddRefed<ServiceWorkerManager>
 ServiceWorkerManager::GetInstance()
 {
   nsCOMPtr<nsIServiceWorkerManager> swm =
     do_GetService(SERVICEWORKERMANAGER_CONTRACTID);
@@ -1055,16 +1179,20 @@ public:
     if (mRegistration->mCurrentWorker) {
       // FIXME(nsm). Steps 3.1-3.4 of the algorithm.
     }
 
     nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
 
     mRegistration->mCurrentWorker = mRegistration->mWaitingWorker.forget();
     swm->InvalidateServiceWorkerContainerWorker(mRegistration, ACTIVE_WORKER | WAITING_WORKER);
+    if (!mRegistration->mCurrentWorker) {
+      // Just got unregistered!
+      MOZ_ASSERT(false);
+    }
 
     // FIXME(nsm): Steps 7 of the algorithm.
 
     swm->FireEventOnServiceWorkerContainers(mRegistration,
                                             NS_LITERAL_STRING("controllerchange"));
 
     nsRefPtr<ServiceWorker> serviceWorker;
     nsresult rv =
diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -192,16 +192,17 @@ public:
  */
 class ServiceWorkerManager MOZ_FINAL : public nsIServiceWorkerManager
 {
   friend class ActivationRunnable;
   friend class RegisterRunnable;
   friend class CallInstallRunnable;
   friend class CancelServiceWorkerInstallationRunnable;
   friend class ServiceWorkerUpdateInstance;
+  friend class UnregisterRunnable;
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSISERVICEWORKERMANAGER
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_SERVICEWORKERMANAGER_IMPL_IID)
 
   static ServiceWorkerManager* FactoryCreate()
   {
@@ -254,16 +255,24 @@ 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;
     }
+
+    void
+    RemoveRegistration(ServiceWorkerRegistration* aRegistration)
+    {
+      MOZ_ASSERT(mServiceWorkerRegistrations.Contains(aRegistration->mScope));
+      mServiceWorkerRegistrations.Remove(aRegistration->mScope);
+      ServiceWorkerManager::RemoveScope(mOrderedScopes, aRegistration->mScope);
+    }
   };
 
   nsClassHashtable<nsCStringHashKey, ServiceWorkerDomainInfo> mDomainMap;
 
   void
   ResolveRegisterPromises(ServiceWorkerRegistration* aRegistration,
                           const nsACString& aWorkerScriptSpec);
 
@@ -298,16 +307,19 @@ public:
 
   static already_AddRefed<ServiceWorkerManager>
   GetInstance();
 
 private:
   ServiceWorkerManager();
   ~ServiceWorkerManager();
 
+  void
+  AbortCurrentUpdate(ServiceWorkerRegistration* aRegistration);
+
   NS_IMETHOD
   Update(ServiceWorkerRegistration* aRegistration, nsPIDOMWindow* aWindow);
 
   void
   Install(ServiceWorkerRegistration* aRegistration,
           ServiceWorkerInfo* aServiceWorkerInfo);
 
   NS_IMETHOD
diff --git a/dom/workers/test/serviceworkers/mochitest.ini b/dom/workers/test/serviceworkers/mochitest.ini
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -1,13 +1,15 @@
 [DEFAULT]
 support-files =
   worker.js
   worker2.js
   worker3.js
   parse_error_worker.js
   install_event_worker.js
   simpleregister/index.html
+  unregister/index.html
 
 [test_installation_simple.html]
 [test_install_event.html]
 [test_navigator.html]
 [test_scopes.html]
+[test_unregister.html]
diff --git a/dom/workers/test/serviceworkers/test_unregister.html b/dom/workers/test/serviceworkers/test_unregister.html
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_unregister.html
@@ -0,0 +1,92 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 984048 - Test unregister</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+  function simpleRegister() {
+    return navigator.serviceWorker.register("worker.js", { scope: "unregister/*" });
+  }
+
+  function testControlled() {
+    var testPromise = new Promise(function(res, rej) {
+      window.onmessage = function(e) {
+        if (e.data === "READY") {
+          w.postMessage({ "controlled": true }, "*");
+        } else if (e.data === "SUCCESS") {
+          ok(true, "New window should be controlled.");
+          res();
+        } else if (e.data === "FAIL") {
+          ok(false, "New window should be controlled");
+          res();
+        }
+      }
+    });
+
+    var w;
+    setTimeout(function() {
+      w = window.open("unregister/index.html", "_blank", "width=700, height=400");
+    }, 150);
+    return testPromise.then(() => w.close());
+  }
+
+  function unregister() {
+    return navigator.serviceWorker.unregister("unregister/*");
+  }
+
+  function testUncontrolled() {
+    var testPromise = new Promise(function(res, rej) {
+      window.onmessage = function(e) {
+        if (e.data === "READY") {
+          w.postMessage({ "controlled": false }, "*");
+        } else if (e.data === "SUCCESS") {
+          ok(true, "New window should not be controlled.");
+          res();
+        } else if (e.data === "FAIL") {
+          ok(false, "New window should not be controlled");
+          res();
+        }
+      }
+    });
+
+    var w;
+    setTimeout(function() {
+      w = window.open("unregister/index.html", "_blank", "width=700, height=400");
+    }, 150);
+    return testPromise.then(() => w.close());
+  }
+
+  function runTest() {
+    simpleRegister()
+      .then(testControlled)
+      .then(unregister)
+      .then(testUncontrolled)
+      .then(function() {
+        SimpleTest.finish();
+      }).catch(function(e) {
+        ok(false, "Some test failed with error " + e);
+        SimpleTest.finish();
+      });
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+  ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/unregister/index.html b/dom/workers/test/serviceworkers/unregister/index.html
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/unregister/index.html
@@ -0,0 +1,47 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 984048 - Test unregister</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+  function fail(msg) {
+      info("unregister/index.html: " + msg);
+      opener.postMessage("FAIL", "*");
+  }
+
+  if (!opener) {
+      info("unregister/index.html should not to be launched directly!");
+  }
+
+  window.addEventListener("message", function(e) {
+      if (!e.data) {
+          return fail("Message had no data!");
+      }
+
+      // FIXME(nsm): Use controller and not current!
+      if (e.data.controlled === true && !navigator.serviceWorker.controller) {
+          return fail("Not controlled!");
+      } else if (e.data.controlled === false && navigator.serviceWorker.controller) {
+          return fail("Controlled when it shouldn't be!");
+      }
+
+      opener.postMessage("SUCCESS", "*");
+  }, false);
+
+  window.onload = function() {
+      opener.postMessage("READY", "*");
+  }
+</script>
+</pre>
+</body>
+</html>