Bug 1131327 - Patch 5 - Implement ServiceWorkerRegistration.unregister() on worker. r=baku
authorNikhil Marathe <nsm.nikhil@gmail.com>
Tue, 07 Apr 2015 14:17:02 -0700
changeset 272130 3c2774add91330cc0fe2fe5857e8f693754a293f
parent 272129 3fe905b6624954082ce26596488f478f229cd288
child 272131 cc0295e4472ab6fd71cff40066d378b09ce15619
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1131327
milestone40.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 1131327 - Patch 5 - Implement ServiceWorkerRegistration.unregister() on worker. r=baku
dom/workers/ServiceWorkerRegistration.cpp
dom/workers/ServiceWorkerRegistration.h
dom/workers/test/serviceworkers/mochitest.ini
dom/workers/test/serviceworkers/test_workerUnregister.html
dom/workers/test/serviceworkers/unregister/unregister.html
dom/workers/test/serviceworkers/worker_unregister.js
--- a/dom/workers/ServiceWorkerRegistration.cpp
+++ b/dom/workers/ServiceWorkerRegistration.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "ServiceWorkerRegistration.h"
 
 #include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseWorkerProxy.h"
 #include "mozilla/dom/ServiceWorkerRegistrationBinding.h"
 #include "mozilla/Services.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
 #include "ServiceWorker.h"
 
 #include "nsIDocument.h"
@@ -279,29 +280,174 @@ public:
     return NS_OK;
   }
 
   NS_IMETHODIMP
   UnregisterFailed() override
   {
     AssertIsOnMainThread();
 
-    AutoJSAPI api;
-    api.Init(mPromise->GetParentObject());
-    mPromise->MaybeReject(api.cx(), JS::UndefinedHandleValue);
+    mPromise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return NS_OK;
   }
 
 private:
   ~UnregisterCallback()
   { }
 };
 
 NS_IMPL_ISUPPORTS(UnregisterCallback, nsIServiceWorkerUnregisterCallback)
 
+class FulfillUnregisterPromiseRunnable final : public WorkerRunnable
+{
+  nsRefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
+  Maybe<bool> mState;
+public:
+  FulfillUnregisterPromiseRunnable(WorkerPrivate* aWorkerPrivate,
+                                   PromiseWorkerProxy* aProxy,
+                                   Maybe<bool> aState)
+    : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
+    , mPromiseWorkerProxy(aProxy)
+    , mState(aState)
+  {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(mPromiseWorkerProxy);
+  }
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    Promise* promise = mPromiseWorkerProxy->GetWorkerPromise();
+    MOZ_ASSERT(promise);
+    if (mState.isSome()) {
+      promise->MaybeResolve(mState.value());
+    } else {
+      promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    }
+
+    mPromiseWorkerProxy->CleanUp(aCx);
+    return true;
+  }
+};
+
+class WorkerUnregisterCallback final : public nsIServiceWorkerUnregisterCallback
+{
+  nsRefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit WorkerUnregisterCallback(PromiseWorkerProxy* aProxy)
+    : mPromiseWorkerProxy(aProxy)
+  {
+  }
+
+  NS_IMETHODIMP
+  UnregisterSucceeded(bool aState) override
+  {
+    AssertIsOnMainThread();
+    Finish(Some(aState));
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP
+  UnregisterFailed() override
+  {
+    AssertIsOnMainThread();
+    Finish(Nothing());
+    return NS_OK;
+  }
+
+private:
+  ~WorkerUnregisterCallback()
+  { }
+
+  void
+  Finish(Maybe<bool> aState)
+  {
+    AssertIsOnMainThread();
+    if (!mPromiseWorkerProxy) {
+      return;
+    }
+
+    MutexAutoLock lock(mPromiseWorkerProxy->GetCleanUpLock());
+    if (mPromiseWorkerProxy->IsClean()) {
+      return;
+    }
+
+    nsRefPtr<WorkerRunnable> r =
+      new FulfillUnregisterPromiseRunnable(mPromiseWorkerProxy->GetWorkerPrivate(),
+                                           mPromiseWorkerProxy, aState);
+
+    AutoJSAPI jsapi;
+    jsapi.Init();
+    if (!r->Dispatch(jsapi.cx())) {
+      nsRefPtr<WorkerControlRunnable> cr =
+        new PromiseWorkerProxyControlRunnable(
+          mPromiseWorkerProxy->GetWorkerPrivate(),
+          mPromiseWorkerProxy);
+      cr->Dispatch(jsapi.cx());
+    }
+  }
+};
+
+NS_IMPL_ISUPPORTS(WorkerUnregisterCallback, nsIServiceWorkerUnregisterCallback);
+
+/*
+ * If the worker goes away, we still continue to unregister, but we don't try to
+ * resolve the worker Promise (which doesn't exist by that point).
+ */
+class StartUnregisterRunnable final : public nsRunnable
+{
+  nsRefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
+  const nsString mScope;
+
+public:
+  StartUnregisterRunnable(WorkerPrivate* aWorker, Promise* aPromise,
+                          const nsAString& aScope)
+    : mPromiseWorkerProxy(PromiseWorkerProxy::Create(aWorker, aPromise))
+    , mScope(aScope)
+  {
+    // mPromiseWorkerProxy may be null if AddFeature failed.
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    AssertIsOnMainThread();
+
+    nsRefPtr<WorkerUnregisterCallback> cb = new WorkerUnregisterCallback(mPromiseWorkerProxy);
+
+    // XXXnsm: There is a rare chance of this failing if the worker gets
+    // destroyed. In that case, unregister() called from a SW is no longer
+    // guaranteed to run. We should fix this by having a main thread proxy
+    // maintain a strongref to ServiceWorkerRegistrationInfo and use its
+    // principal. Can that be trusted?
+    nsCOMPtr<nsIPrincipal> principal;
+    {
+      MutexAutoLock lock(mPromiseWorkerProxy->GetCleanUpLock());
+      if (mPromiseWorkerProxy->IsClean()) {
+        return NS_OK;
+      }
+
+      WorkerPrivate* worker = mPromiseWorkerProxy->GetWorkerPrivate();
+      MOZ_ASSERT(worker);
+      principal = worker->GetPrincipal();
+    }
+    MOZ_ASSERT(principal);
+
+    nsCOMPtr<nsIServiceWorkerManager> swm =
+      mozilla::services::GetServiceWorkerManager();
+    nsresult rv = swm->Unregister(principal, cb, mScope);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      cb->UnregisterFailed();
+    }
+
+    return NS_OK;
+  }
+};
 } // anonymous namespace
 
 void
 ServiceWorkerRegistrationMainThread::Update()
 {
   UpdateInternal(mScope);
 }
 
@@ -345,21 +491,17 @@ ServiceWorkerRegistrationMainThread::Unr
 
   nsAutoCString uriSpec;
   aRv = scopeURI->GetSpecIgnoringRef(uriSpec);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   nsCOMPtr<nsIServiceWorkerManager> swm =
-    do_GetService(SERVICEWORKERMANAGER_CONTRACTID, &rv);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(rv);
-    return nullptr;
-  }
+    mozilla::services::GetServiceWorkerManager();
 
   nsRefPtr<Promise> promise = Promise::Create(go, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   nsRefPtr<UnregisterCallback> cb = new UnregisterCallback(promise);
 
@@ -473,10 +615,36 @@ ServiceWorkerRegistrationWorkerThread::U
   WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
 #endif
   nsCOMPtr<nsIRunnable> r = new UpdateRunnable(mScope);
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r)));
 }
 
+already_AddRefed<Promise>
+ServiceWorkerRegistrationWorkerThread::Unregister(ErrorResult& aRv)
+{
+  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(worker);
+  worker->AssertIsOnWorkerThread();
+
+  if (!worker->IsServiceWorker()) {
+    // For other workers, the registration probably originated from
+    // getRegistration(), so we may have to validate origin etc. Let's do this
+    // this later.
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(worker->GlobalScope(), aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsRefPtr<StartUnregisterRunnable> r = new StartUnregisterRunnable(worker, promise, mScope);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r)));
+
+  return promise.forget();
+}
+
 } // dom namespace
 } // mozilla namespace
--- a/dom/workers/ServiceWorkerRegistration.h
+++ b/dom/workers/ServiceWorkerRegistration.h
@@ -162,21 +162,17 @@ public:
   ServiceWorkerRegistrationWorkerThread(const nsAString& aScope)
     : ServiceWorkerRegistrationBase(nullptr, aScope)
   {}
 
   void
   Update();
 
   already_AddRefed<Promise>
-  Unregister(ErrorResult& aRv)
-  {
-    MOZ_CRASH("FIXME");
-    return nullptr;
-  }
+  Unregister(ErrorResult& aRv);
 
   JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   already_AddRefed<workers::ServiceWorker>
   GetInstalling() override;
 
   already_AddRefed<workers::ServiceWorker>
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -8,22 +8,24 @@ support-files =
   parse_error_worker.js
   activate_event_error_worker.js
   install_event_worker.js
   install_event_error_worker.js
   simpleregister/index.html
   simpleregister/ready.html
   controller/index.html
   unregister/index.html
+  unregister/unregister.html
   workerUpdate/update.html
   sw_clients/simple.html
   sw_clients/service_worker_controlled.html
   sw_clients/focus_stealing_client.html
   match_all_worker.js
   match_all_advanced_worker.js
+  worker_unregister.js
   worker_update.js
   message_posting_worker.js
   fetch/index.html
   fetch/fetch_worker_script.js
   fetch/fetch_tests.js
   fetch/deliver-gzip.sjs
   fetch/real-file.txt
   fetch/context/index.html
@@ -79,16 +81,17 @@ skip-if = os != "linux" # Bug 1136780
 [test_https_fetch.html]
 [test_https_fetch_cloned_response.html]
 [test_match_all.html]
 [test_match_all_advanced.html]
 [test_install_event.html]
 [test_navigator.html]
 [test_scopes.html]
 [test_controller.html]
+[test_workerUnregister.html]
 [test_workerUpdate.html]
 [test_post_message.html]
 [test_post_message_advanced.html]
 [test_post_message_source.html]
 [test_match_all_client_properties.html]
 [test_close.html]
 [test_serviceworker_interfaces.html]
 [test_serviceworker_not_sharedworker.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_workerUnregister.html
@@ -0,0 +1,82 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 982728 - Test ServiceWorkerGlobalScope.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>
+<div id="container"></div>
+<script class="testbody" type="text/javascript">
+
+  function simpleRegister() {
+    return navigator.serviceWorker.register("worker_unregister.js", { scope: "unregister/" }).then(function(swr) {
+      if (swr.installing) {
+        return new Promise(function(resolve, reject) {
+          swr.installing.onstatechange = function(e) {
+            if (swr.waiting) {
+              swr.waiting.onstatechange = function(e) {
+                if (swr.active) {
+                  resolve();
+                } else if (swr.waiting && swr.waiting.state == "redundant") {
+                  reject("Should not go into redundant");
+                }
+              }
+            } else {
+              if (swr.active) {
+                resolve();
+              } else {
+                reject("No waiting and no active!");
+              }
+            }
+          }
+        });
+      } else {
+        return Promise.reject("Installing should be non-null");
+      }
+    });
+  }
+
+  function waitForMessages(sw) {
+    var p = new Promise(function(resolve, reject) {
+      window.onmessage = function(e) {
+        if (e.data === "DONE") {
+          ok(true, "The worker has unregistered itself");
+        } else if (e.data === "ERROR") {
+          ok(false, "The worker has unregistered itself");
+        } else if (e.data === "FINISH") {
+          resolve();
+        }
+      }
+    });
+
+    var frame = document.createElement("iframe");
+    frame.setAttribute("src", "unregister/unregister.html");
+    document.body.appendChild(frame);
+
+    return p;
+  }
+
+  function runTest() {
+    simpleRegister().then(waitForMessages).catch(function(e) {
+      ok(false, "Something went wrong.");
+    }).then(function() {
+      SimpleTest.finish();
+    });
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+  ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/unregister/unregister.html
@@ -0,0 +1,22 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test worker::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>
+<script type="text/javascript">
+
+  navigator.serviceWorker.onmessage = function(e) { parent.postMessage(e.data, "*"); }
+  navigator.serviceWorker.controller.postMessage("GO");
+
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/worker_unregister.js
@@ -0,0 +1,16 @@
+onmessage = function(e) {
+  clients.matchAll().then(function(c) {
+    if (c.length === 0) {
+      // We cannot proceed.
+      return;
+    }
+
+    registration.unregister().then(function() {
+      c[0].postMessage('DONE');
+    }, function() {
+      c[0].postMessage('ERROR');
+    }).then(function() {
+      c[0].postMessage('FINISH');
+    });
+  });
+}