Bug 982726 - Patch 1 - Service Worker: ServiceWorkerClients and Client interfaces with getServiced(). r=baku
☠☠ backed out by d323ede35f89 ☠ ☠
authorCatalin Badea <cbadea@mozilla.com>
Wed, 27 Aug 2014 10:24:09 -0700
changeset 226517 9244a16c3258f57039b0b1cfbd0cd24239b822b7
parent 226516 64c820a2607d2930234b3933aa7989d4e53ec36e
child 226518 05acfcc7a34119085fc8ad4582e5420c96658c80
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs982726
milestone35.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 - 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
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1053,16 +1053,26 @@ DOMInterfaces = {
     ]
 },
 
 '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();
   void unregister();
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -320,16 +320,18 @@ WEBIDL_FILES = [
     'RTCRtpSender.webidl',
     'RTCSessionDescription.webidl',
     'RTCStatsReport.webidl',
     'Screen.webidl',
     'ScriptProcessorNode.webidl',
     'ScrollAreaEvent.webidl',
     'Selection.webidl',
     'ServiceWorker.webidl',
+    'ServiceWorkerClient.webidl',
+    'ServiceWorkerClients.webidl',
     'ServiceWorkerContainer.webidl',
     'ServiceWorkerGlobalScope.webidl',
     'ServiceWorkerRegistration.webidl',
     'SettingsManager.webidl',
     'ShadowRoot.webidl',
     'SharedWorker.webidl',
     'SharedWorkerGlobalScope.webidl',
     'SimpleGestureEvent.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,59 @@
+/* -*- 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)
+  {
+    SetIsDOMBinding();
+  }
+
+  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,229 @@
+/* -*- 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);
+  SetIsDOMBinding();
+}
+
+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;
@@ -2168,9 +2169,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
@@ -313,16 +313,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
@@ -341,16 +341,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
@@ -3,16 +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/. */
 
 #ifndef mozilla_dom_workerscope_h__
 #define mozilla_dom_workerscope_h__
 
 #include "Workers.h"
 #include "mozilla/DOMEventTargetHelper.h"
+#include "ServiceWorkerClients.h"
 
 namespace mozilla {
 namespace dom {
 
 class Console;
 class Function;
 
 } // namespace dom
@@ -158,19 +159,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
   {
@@ -190,16 +197,25 @@ public:
   }
 
   void
   Unregister()
   {
     // FIXME(nsm): Bug 982728
   }
 
+  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
@@ -24,16 +24,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',
 ]
 
 SOURCES += [
@@ -46,16 +48,18 @@ 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',
     'WorkerPrivate.cpp',
     'WorkerRunnable.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>
+