Bug 982726 - Patch 1.1 - Service Worker: ServiceWorkerClients and Client interfaces with getServiced(). r=baku
authorCatalin Badea <cbadea@mozilla.com>
Mon, 27 Oct 2014 12:03:00 +0100
changeset 214024 ed211a407dde887c2fab6788004ecc655ee635d7
parent 214023 a2b7b389d59380a4dcce0a0eace5e9527cfe9aa6
child 214025 736c29dc81cefb022e12325995f2f36ba18f70c0
push id51397
push usercbook@mozilla.com
push dateWed, 05 Nov 2014 07:42:01 +0000
treeherdermozilla-inbound@a1a0c1573df1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs982726
milestone36.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 982726 - Patch 1.1 - Service Worker: ServiceWorkerClients and Client interfaces with getServiced(). r=baku
dom/bindings/Bindings.conf
dom/webidl/ServiceWorkerClient.webidl
dom/webidl/ServiceWorkerClients.webidl
dom/webidl/ServiceWorkerGlobalScope.webidl
dom/webidl/moz.build
dom/workers/ServiceWorkerClient.cpp
dom/workers/ServiceWorkerClient.h
dom/workers/ServiceWorkerClients.cpp
dom/workers/ServiceWorkerClients.h
dom/workers/ServiceWorkerManager.cpp
dom/workers/ServiceWorkerManager.h
dom/workers/WorkerScope.cpp
dom/workers/WorkerScope.h
dom/workers/moz.build
dom/workers/test/serviceworkers/get_serviced_worker.js
dom/workers/test/serviceworkers/mochitest.ini
dom/workers/test/serviceworkers/sw_clients/simple.html
dom/workers/test/serviceworkers/test_get_serviced.html
dom/workers/test/serviceworkers/test_unregister.html
dom/workers/test/serviceworkers/unregister/index.html
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -946,16 +946,26 @@ DOMInterfaces = {
     'nativeType': 'nsScreen',
 },
 
 'ServiceWorker': {
     'nativeType': 'mozilla::dom::workers::ServiceWorker',
     'headerFile': 'mozilla/dom/workers/bindings/ServiceWorker.h',
 },
 
+'ServiceWorkerClient': {
+    'nativeType': 'mozilla::dom::workers::ServiceWorkerClient',
+    'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerClient.h',
+},
+
+'ServiceWorkerClients': {
+    'nativeType': 'mozilla::dom::workers::ServiceWorkerClients',
+    'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerClients.h',
+},
+
 'ServiceWorkerGlobalScope': {
     'headerFile': 'mozilla/dom/WorkerScope.h',
     'workers': True,
 },
 
 'SharedWorker': {
     'nativeType': 'mozilla::dom::workers::SharedWorker',
     'headerFile': 'mozilla/dom/workers/bindings/SharedWorker.h',
new file mode 100644
--- /dev/null
+++ b/dom/webidl/ServiceWorkerClient.webidl
@@ -0,0 +1,14 @@
+/* -*- 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/.
+ *
+ * The origin of this IDL file is
+ * http://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html
+ *
+ */
+
+[Exposed=ServiceWorker]
+interface ServiceWorkerClient {
+  readonly attribute unsigned long id;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/ServiceWorkerClients.webidl
@@ -0,0 +1,19 @@
+/* -*- 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/.
+ *
+ * The origin of this IDL file is
+ * http://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html
+ *
+ */
+
+[Exposed=ServiceWorker]
+interface ServiceWorkerClients {
+  // A list of client objects, identifiable by ID, that correspond to windows
+  // (or workers) that are "controlled" by this SW
+  [Throws]
+  Promise<sequence<ServiceWorkerClient>?> getServiced();
+  [Throws]
+  Promise<any> reloadAll();
+};
--- a/dom/webidl/ServiceWorkerGlobalScope.webidl
+++ b/dom/webidl/ServiceWorkerGlobalScope.webidl
@@ -11,20 +11,17 @@
  */
 
 [Global=(Worker,ServiceWorker),
  Exposed=ServiceWorker]
 interface ServiceWorkerGlobalScope : WorkerGlobalScope {
   // FIXME(nsm): Bug 982725
   // readonly attribute CacheList caches;
 
-  // FIXME(nsm): Bug 982726
-  // A container for a list of window objects, identifiable by ID, that
-  // correspond to windows (or workers) that are "controlled" by this SW
-  // readonly attribute ServiceWorkerClients clients;
+  readonly attribute ServiceWorkerClients clients;
 
   [Unforgeable] readonly attribute DOMString scope;
 
   // FIXME(nsm): Bug 995484
   // ResponsePromise<any> fetch((Request or [EnsureUTF16] DOMString) request);
 
   void update();
 
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -353,16 +353,18 @@ WEBIDL_FILES = [
     'RTCSessionDescription.webidl',
     'RTCStatsReport.webidl',
     'Screen.webidl',
     'ScriptProcessorNode.webidl',
     'ScrollAreaEvent.webidl',
     'ScrollBoxObject.webidl',
     'Selection.webidl',
     'ServiceWorker.webidl',
+    'ServiceWorkerClient.webidl',
+    'ServiceWorkerClients.webidl',
     'ServiceWorkerContainer.webidl',
     'ServiceWorkerGlobalScope.webidl',
     'ServiceWorkerRegistration.webidl',
     'SettingChangeNotification.webidl',
     'SettingsManager.webidl',
     'ShadowRoot.webidl',
     'SharedWorker.webidl',
     'SharedWorkerGlobalScope.webidl',
new file mode 100644
--- /dev/null
+++ b/dom/workers/ServiceWorkerClient.cpp
@@ -0,0 +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 "ServiceWorkerClient.h"
+
+#include "mozilla/dom/ServiceWorkerClientBinding.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::dom::workers;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ServiceWorkerClient, mOwner)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerClient)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ServiceWorkerClient)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerClient)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject*
+ServiceWorkerClient::WrapObject(JSContext* aCx)
+{
+  return ServiceWorkerClientBinding::Wrap(aCx, this);
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/workers/ServiceWorkerClient.h
@@ -0,0 +1,58 @@
+/* -*- 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/.
+ */
+
+#ifndef mozilla_dom_workers_serviceworkerclient_h
+#define mozilla_dom_workers_serviceworkerclient_h
+
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+
+namespace workers {
+
+class ServiceWorkerClient MOZ_FINAL : public nsISupports,
+                                      public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ServiceWorkerClient)
+
+  ServiceWorkerClient(nsISupports* aOwner, uint64_t aId)
+    : mOwner(aOwner),
+      mId(aId)
+  {
+  }
+
+  uint32_t Id() const
+  {
+    return mId;
+  }
+
+  nsISupports* GetParentObject() const
+  {
+    return mOwner;
+  }
+
+  JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+private:
+  ~ServiceWorkerClient()
+  {
+  }
+
+  nsCOMPtr<nsISupports> mOwner;
+  uint64_t mId;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkerclient_h
new file mode 100644
--- /dev/null
+++ b/dom/workers/ServiceWorkerClients.cpp
@@ -0,0 +1,228 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* 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 "ServiceWorkerClient.h"
+#include "ServiceWorkerClients.h"
+#include "ServiceWorkerManager.h"
+
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "WorkerScope.h"
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ServiceWorkerClientsBinding.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::workers;
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerClients)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ServiceWorkerClients)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ServiceWorkerClients, mWorkerScope)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerClients)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+ServiceWorkerClients::ServiceWorkerClients(ServiceWorkerGlobalScope* aWorkerScope)
+  : mWorkerScope(aWorkerScope)
+{
+  MOZ_ASSERT(mWorkerScope);
+}
+
+JSObject*
+ServiceWorkerClients::WrapObject(JSContext* aCx)
+{
+  return ServiceWorkerClientsBinding::Wrap(aCx, this);
+}
+
+namespace {
+
+// Helper class used for passing the promise between threads while
+// keeping the worker alive.
+class PromiseHolder MOZ_FINAL : public WorkerFeature
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PromiseHolder)
+
+public:
+  PromiseHolder(WorkerPrivate* aWorkerPrivate,
+                Promise* aPromise)
+    : mWorkerPrivate(aWorkerPrivate),
+      mPromise(aPromise),
+      mClean(false)
+  {
+    MOZ_ASSERT(mWorkerPrivate);
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    MOZ_ASSERT(mPromise);
+
+    if (NS_WARN_IF(!mWorkerPrivate->AddFeature(mWorkerPrivate->GetJSContext(), this))) {
+      // Worker has been canceled and will go away.
+      // The ResolvePromiseWorkerRunnable won't run, so we can set mPromise to
+      // nullptr.
+      mPromise = nullptr;
+      mClean = true;
+    }
+  }
+
+  Promise*
+  Get() const
+  {
+    return mPromise;
+  }
+
+  void
+  Clean()
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+
+    if (mClean) {
+      return;
+    }
+
+    mPromise = nullptr;
+    mWorkerPrivate->RemoveFeature(mWorkerPrivate->GetJSContext(), this);
+    mClean = true;
+  }
+
+  bool
+  Notify(JSContext* aCx, Status aStatus)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+
+    if (aStatus > Running) {
+      Clean();
+    }
+
+    return true;
+  }
+
+private:
+  ~PromiseHolder()
+  {
+    MOZ_ASSERT(mClean);
+  }
+
+  WorkerPrivate* mWorkerPrivate;
+  nsRefPtr<Promise> mPromise;
+
+  bool mClean;
+};
+
+class ResolvePromiseWorkerRunnable MOZ_FINAL : public WorkerRunnable
+{
+  nsRefPtr<PromiseHolder> mPromiseHolder;
+  nsAutoPtr<nsTArray<uint64_t>> mValue;
+
+public:
+  ResolvePromiseWorkerRunnable(WorkerPrivate* aWorkerPrivate,
+                               PromiseHolder* aPromiseHolder,
+                               nsAutoPtr<nsTArray<uint64_t>>& aValue)
+    : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount),
+      mPromiseHolder(aPromiseHolder),
+      mValue(aValue)
+  {
+    AssertIsOnMainThread();
+  }
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    aWorkerPrivate->AssertIsOnWorkerThread();
+
+    Promise* promise = mPromiseHolder->Get();
+    MOZ_ASSERT(promise);
+
+    nsTArray<nsRefPtr<ServiceWorkerClient>> ret;
+    for (size_t i = 0; i < mValue->Length(); i++) {
+      ret.AppendElement(nsRefPtr<ServiceWorkerClient>(
+            new ServiceWorkerClient(promise->GetParentObject(),
+                                    mValue->ElementAt(i))));
+    }
+    promise->MaybeResolve(ret);
+
+    // release the reference on the worker thread.
+    mPromiseHolder->Clean();
+
+    return true;
+  }
+};
+
+class GetServicedRunnable MOZ_FINAL : public nsRunnable
+{
+  WorkerPrivate* mWorkerPrivate;
+  nsCString mScope;
+  nsRefPtr<PromiseHolder> mPromiseHolder;
+public:
+  GetServicedRunnable(WorkerPrivate* aWorkerPrivate,
+                      Promise* aPromise,
+                      const nsCString& aScope)
+    : mWorkerPrivate(aWorkerPrivate),
+      mScope(aScope)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    aWorkerPrivate->AssertIsOnWorkerThread();
+    mPromiseHolder = new PromiseHolder(aWorkerPrivate, aPromise);
+  }
+
+  NS_IMETHOD
+  Run() MOZ_OVERRIDE
+  {
+    AssertIsOnMainThread();
+
+    nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+    nsAutoPtr<nsTArray<uint64_t>> result(new nsTArray<uint64_t>());
+
+    swm->GetServicedClients(mScope, result);
+    nsRefPtr<ResolvePromiseWorkerRunnable> r =
+      new ResolvePromiseWorkerRunnable(mWorkerPrivate, mPromiseHolder, result);
+
+    AutoSafeJSContext cx;
+    r->Dispatch(cx);
+    return NS_OK;
+  }
+};
+
+} // anonymous namespace
+
+already_AddRefed<Promise>
+ServiceWorkerClients::GetServiced(ErrorResult& aRv)
+{
+  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(workerPrivate);
+  workerPrivate->AssertIsOnWorkerThread();
+
+  DOMString scope;
+  mWorkerScope->GetScope(scope);
+
+  nsRefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  nsRefPtr<GetServicedRunnable> r =
+    new GetServicedRunnable(workerPrivate, promise, NS_ConvertUTF16toUTF8(scope));
+  nsresult rv = NS_DispatchToMainThread(r);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+  }
+
+  return promise.forget();
+}
+
+// FIXME(catalinb): Bug 1045257 - Implement ReloadAll
+already_AddRefed<Promise>
+ServiceWorkerClients::ReloadAll(ErrorResult& aRv)
+{
+  nsRefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+  return promise.forget();
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/workers/ServiceWorkerClients.h
@@ -0,0 +1,56 @@
+/* -*- 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/.
+ */
+
+#ifndef mozilla_dom_workers_serviceworkerclients_h
+#define mozilla_dom_workers_serviceworkerclients_h
+
+#include "nsAutoPtr.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class Promise;
+
+namespace workers {
+
+class ServiceWorkerGlobalScope;
+
+class ServiceWorkerClients MOZ_FINAL : public nsISupports,
+                                       public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ServiceWorkerClients)
+
+  ServiceWorkerClients(ServiceWorkerGlobalScope* aWorkerScope);
+
+  already_AddRefed<Promise> GetServiced(ErrorResult& aRv);
+  already_AddRefed<Promise> ReloadAll(ErrorResult& aRv);
+
+  JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+  ServiceWorkerGlobalScope* GetParentObject() const
+  {
+    return mWorkerScope;
+  }
+
+private:
+  ~ServiceWorkerClients()
+  {
+  }
+
+  nsRefPtr<ServiceWorkerGlobalScope> mWorkerScope;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkerclients_h
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -19,16 +19,17 @@
 
 #include "nsContentUtils.h"
 #include "nsNetUtil.h"
 #include "nsProxyRelease.h"
 #include "nsTArray.h"
 
 #include "RuntimeService.h"
 #include "ServiceWorker.h"
+#include "ServiceWorkerClient.h"
 #include "ServiceWorkerRegistration.h"
 #include "ServiceWorkerEvents.h"
 #include "WorkerInlines.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
 
 using namespace mozilla;
@@ -65,16 +66,17 @@ UpdatePromise::ResolveAllPromises(const 
   RuntimeService* rs = RuntimeService::GetOrCreateService();
   MOZ_ASSERT(rs);
 
   nsTArray<WeakPtr<Promise>> array;
   array.SwapElements(mPromises);
   for (uint32_t i = 0; i < array.Length(); ++i) {
     WeakPtr<Promise>& pendingPromise = array.ElementAt(i);
     if (pendingPromise) {
+      nsRefPtr<Promise> kungfuDeathGrip = pendingPromise.get();
       nsCOMPtr<nsIGlobalObject> go =
         do_QueryInterface(pendingPromise->GetParentObject());
       MOZ_ASSERT(go);
 
       AutoSafeJSContext cx;
       JS::Rooted<JSObject*> global(cx, go->GetGlobalJSObject());
       JSAutoCompartment ac(cx, global);
 
@@ -88,18 +90,17 @@ UpdatePromise::ResolveAllPromises(const 
                                             getter_AddRefs(serviceWorker));
       if (NS_WARN_IF(NS_FAILED(rv))) {
         pendingPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
         continue;
       }
 
       // Since ServiceWorkerRegistration is only exposed to windows we can be
       // certain about this cast.
-      nsCOMPtr<nsPIDOMWindow> window =
-        do_QueryInterface(pendingPromise->GetParentObject());
+      nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(go);
       nsRefPtr<ServiceWorkerRegistration> swr =
         new ServiceWorkerRegistration(window, NS_ConvertUTF8toUTF16(aScope));
 
       pendingPromise->MaybeResolve(swr);
     }
   }
 }
 
@@ -2136,9 +2137,59 @@ ServiceWorkerManager::InvalidateServiceW
         continue;
       }
 
       target->InvalidateWorkerReference(aWhichOnes);
     }
   }
 }
 
+namespace {
+
+class MOZ_STACK_CLASS FilterRegistrationData
+{
+public:
+  FilterRegistrationData(nsTArray<uint64_t>* aDocuments,
+                     ServiceWorkerRegistrationInfo* aRegistration)
+  : mDocuments(aDocuments),
+    mRegistration(aRegistration)
+  {
+  }
+
+  nsTArray<uint64_t>* mDocuments;
+  nsRefPtr<ServiceWorkerRegistrationInfo> mRegistration;
+};
+
+static PLDHashOperator
+EnumControlledDocuments(nsISupports* aKey,
+                        ServiceWorkerRegistrationInfo* aRegistration,
+                        void* aData)
+{
+  FilterRegistrationData* data = static_cast<FilterRegistrationData*>(aData);
+  if (data->mRegistration != aRegistration) {
+    return PL_DHASH_NEXT;
+  }
+  nsCOMPtr<nsIDocument> document = do_QueryInterface(aKey);
+  if (!document || !document->GetInnerWindow()) {
+      return PL_DHASH_NEXT;
+  }
+
+  data->mDocuments->AppendElement(document->GetInnerWindow()->WindowID());
+  return PL_DHASH_NEXT;
+}
+
+} // anonymous namespace
+
+void
+ServiceWorkerManager::GetServicedClients(const nsCString& aScope,
+                                     nsTArray<uint64_t>* aControlledDocuments)
+{
+  nsRefPtr<ServiceWorkerDomainInfo> domainInfo = GetDomainInfo(aScope);
+  nsRefPtr<ServiceWorkerRegistrationInfo> registration =
+    domainInfo->GetRegistration(aScope);
+  MOZ_ASSERT(registration);
+  FilterRegistrationData data(aControlledDocuments, registration);
+
+  domainInfo->mControlledDocuments.EnumerateRead(EnumControlledDocuments,
+                                                 &data);
+}
+
 END_WORKERS_NAMESPACE
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -312,16 +312,20 @@ public:
               const nsAString& aWorkerURL,
               nsString aMessage,
               nsString aFilename,
               nsString aLine,
               uint32_t aLineNumber,
               uint32_t aColumnNumber,
               uint32_t aFlags);
 
+  void
+  GetServicedClients(const nsCString& aScope,
+                     nsTArray<uint64_t>* aControlledDocuments);
+
   static already_AddRefed<ServiceWorkerManager>
   GetInstance();
 
 private:
   ServiceWorkerManager();
   ~ServiceWorkerManager();
 
   void
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -354,16 +354,24 @@ SharedWorkerGlobalScope::WrapGlobalObjec
   JS::CompartmentOptions options;
   mWorkerPrivate->CopyJSCompartmentOptions(options);
 
   return SharedWorkerGlobalScopeBinding_workers::Wrap(aCx, this, this, options,
                                                       GetWorkerPrincipal(),
                                                       true);
 }
 
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope,
+                                   mClients)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServiceWorkerGlobalScope)
+NS_INTERFACE_MAP_END_INHERITING(WorkerGlobalScope)
+
+NS_IMPL_ADDREF_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope)
+NS_IMPL_RELEASE_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope)
+
 ServiceWorkerGlobalScope::ServiceWorkerGlobalScope(WorkerPrivate* aWorkerPrivate,
                                                    const nsACString& aScope)
   : WorkerGlobalScope(aWorkerPrivate),
     mScope(NS_ConvertUTF8toUTF16(aScope))
 {
 }
 
 JSObject*
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -6,16 +6,18 @@
 #ifndef mozilla_dom_workerscope_h__
 #define mozilla_dom_workerscope_h__
 
 #include "Workers.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/Headers.h"
 #include "mozilla/dom/RequestBinding.h"
 
+#include "ServiceWorkerClients.h"
+
 namespace mozilla {
 namespace dom {
 
 class Console;
 class Function;
 class Promise;
 class RequestOrScalarValueString;
 
@@ -165,19 +167,25 @@ public:
   }
 
   IMPL_EVENT_HANDLER(connect)
 };
 
 class ServiceWorkerGlobalScope MOZ_FINAL : public WorkerGlobalScope
 {
   const nsString mScope;
+  nsRefPtr<ServiceWorkerClients> mClients;
+
   ~ServiceWorkerGlobalScope() { }
 
 public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerGlobalScope,
+                                           WorkerGlobalScope)
+
   ServiceWorkerGlobalScope(WorkerPrivate* aWorkerPrivate, const nsACString& aScope);
 
   virtual JSObject*
   WrapGlobalObject(JSContext* aCx) MOZ_OVERRIDE;
 
   void
   GetScope(DOMString& aScope) const
   {
@@ -194,16 +202,25 @@ public:
   Update()
   {
     // FIXME(nsm): Bug 982728
   }
 
   already_AddRefed<Promise>
   Unregister(ErrorResult& aRv);
 
+  ServiceWorkerClients*
+  Clients() {
+    if (!mClients) {
+      mClients = new ServiceWorkerClients(this);
+    }
+
+    return mClients;
+  }
+
   IMPL_EVENT_HANDLER(activate)
   IMPL_EVENT_HANDLER(beforeevicted)
   IMPL_EVENT_HANDLER(evicted)
   IMPL_EVENT_HANDLER(fetch)
   IMPL_EVENT_HANDLER(install)
   IMPL_EVENT_HANDLER(message)
 };
 
--- a/dom/workers/moz.build
+++ b/dom/workers/moz.build
@@ -25,16 +25,18 @@ EXPORTS.mozilla.dom.workers.bindings += 
     'DataStore.h',
     'DataStoreCursor.h',
     'FileReaderSync.h',
     'Location.h',
     'MessagePort.h',
     'Navigator.h',
     'Performance.h',
     'ServiceWorker.h',
+    'ServiceWorkerClient.h',
+    'ServiceWorkerClients.h',
     'SharedWorker.h',
     'URL.h',
     'WorkerFeature.h',
     'XMLHttpRequest.h',
     'XMLHttpRequestUpload.h',
 ]
 
 XPIDL_MODULE = 'dom_workers'
@@ -53,16 +55,18 @@ UNIFIED_SOURCES += [
     'MessagePort.cpp',
     'Navigator.cpp',
     'Performance.cpp',
     'Principal.cpp',
     'RegisterBindings.cpp',
     'RuntimeService.cpp',
     'ScriptLoader.cpp',
     'ServiceWorker.cpp',
+    'ServiceWorkerClient.cpp',
+    'ServiceWorkerClients.cpp',
     'ServiceWorkerContainer.cpp',
     'ServiceWorkerEvents.cpp',
     'ServiceWorkerManager.cpp',
     'ServiceWorkerRegistration.cpp',
     'SharedWorker.cpp',
     'URL.cpp',
     'WorkerDebuggerManager.cpp',
     'WorkerPrivate.cpp',
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/get_serviced_worker.js
@@ -0,0 +1,16 @@
+function loop() {
+  self.clients.getServiced().then(function(result) {
+    setTimeout(loop, 0);
+  });
+}
+
+onactivate = function(e) {
+  // spam getServiced until the worker is closed.
+  loop();
+}
+
+onclose = function(e) {
+  for (var i = 0; i < 100; ++i) {
+    self.clients.getServiced();
+  }
+}
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -5,15 +5,18 @@ support-files =
   worker2.js
   worker3.js
   parse_error_worker.js
   install_event_worker.js
   simpleregister/index.html
   simpleregister/ready.html
   controller/index.html
   unregister/index.html
+  sw_clients/simple.html
+  get_serviced_worker.js
 
+[test_get_serviced.html]
 [test_installation_simple.html]
 [test_install_event.html]
 [test_navigator.html]
 [test_scopes.html]
 [test_controller.html]
 [test_unregister.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sw_clients/simple.html
@@ -0,0 +1,25 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 982726 - test get_serviced not crashing</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">
+
+  window.onload = function() {
+    opener.postMessage("READY", "*");
+  }
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_get_serviced.html
@@ -0,0 +1,57 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 982726 - test get_serviced not crashing</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">
+  // get_serviced_worker will call getServiced until the worker shuts down.
+  // Test passes if the browser doesn't crash on leaked promise objects.
+  var controlled_window;
+  function simpleRegister() {
+    return navigator.serviceWorker.register("get_serviced_worker.js", { scope: "./sw_clients/" });
+  }
+
+  function openWindow() {
+    var p = new Promise(function(resolve, reject) {
+      window.onmessage = function(e) {
+        if (e.data === "READY") {
+          resolve();
+        }
+      }
+    });
+
+    controlled_window = window.open("sw_clients/simple.html");
+    return p;
+  }
+
+  function runTest() {
+    simpleRegister()
+      .then(openWindow).catch(function(e) {
+        ok(false, "Some test failed with error " + e);
+      }).then(function() {
+        ok(true, "Didn't crash on resolving getServiced promises while worker shuts down.");
+        SimpleTest.finish();
+        controlled_window.close();
+      });
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+  ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
--- a/dom/workers/test/serviceworkers/test_unregister.html
+++ b/dom/workers/test/serviceworkers/test_unregister.html
@@ -11,16 +11,17 @@
 </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() {
+        info("simpleRegister() just before calling register");
     return navigator.serviceWorker.register("worker.js", { scope: "unregister/" });
   }
 
   function testControlled() {
     var testPromise = new Promise(function(res, rej) {
       window.onmessage = function(e) {
         if (!("controlled" in e.data)) {
           ok(false, "Something went wrong.");
@@ -43,16 +44,17 @@
     return testPromise.then(function() {
       div.removeChild(ifr);
     });
   }
 
   function unregister() {
     return navigator.serviceWorker.getRegistration("unregister/")
            .then(function(reg) {
+             info("getRegistration() succeeded " + reg.scope);
              return reg.unregister().then(function(v) {
                ok(v, "Unregister should resolve to true");
              }, function(e) {
                ok(false, "Unregister failed with " + e.name);
              });
            });
   }
 
@@ -79,19 +81,35 @@
 
     return testPromise.then(function() {
       div.removeChild(ifr);
     });
   }
 
   function runTest() {
     simpleRegister()
+      .then(function(v) {
+        info("simpleRegister() promise resolved");
+      })
       .then(testControlled)
+      .then(function(v) {
+        info("testControlled() promise resolved");
+      }, function(e) {
+        info("testControlled() promise rejected " + e);
+      })
       .then(unregister)
+      .then(function(v) {
+        info("unregister() promise resolved");
+      })
       .then(testUncontrolled)
+      .then(function(v) {
+        info("testUncontrolled() promise resolved");
+      }, function(e) {
+        info("testUncontrolled() promise rejected " + e);
+      })
       .then(function() {
         SimpleTest.finish();
       }).catch(function(e) {
         ok(false, "Some test failed with error " + e);
         SimpleTest.finish();
       });
   }
 
--- a/dom/workers/test/serviceworkers/unregister/index.html
+++ b/dom/workers/test/serviceworkers/unregister/index.html
@@ -15,26 +15,30 @@
 <pre id="test"></pre>
 <script class="testbody" type="text/javascript">
 
   if (!parent) {
     info("unregister/index.html should not to be launched directly!");
   }
 
   var tId = setTimeout(function() {
+    info("tId timeout!");
     parent.postMessage({ controlled: false }, "*");
     tId = null;
   }, 2000);
 
   navigator.serviceWorker.ready.then(function() {
+  info("Got ready");
     if (tId == null) {
+    info("tId was null");
       parent.postMessage("FAIL!!!", "*");
       return;
     }
 
     clearTimeout(tId);
+    info("tId was non-null");
     parent.postMessage({ controlled: true }, "*");
   });
 
 </script>
 </pre>
 </body>
 </html>