Bug 984048: Part 1 - ServiceWorkerManager Register(). r=ehsan
authorNikhil Marathe <nsm.nikhil@gmail.com>
Tue, 19 Nov 2013 15:15:02 -0800
changeset 198434 9d19364fe875a7b8f3961e0f0e6b790770f1e05b
parent 198433 20361da59e6d366ecfda89fca469e22578c822ff
child 198435 ed3a1590134957c7fe576bd97644f463a34f5d25
push id5990
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:40:24 +0000
treeherdermozilla-aurora@0796197efbc9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs984048
milestone32.0a1
Bug 984048: Part 1 - ServiceWorkerManager Register(). r=ehsan
dom/interfaces/base/moz.build
dom/interfaces/base/nsIServiceWorkerManager.idl
dom/workers/ServiceWorker.cpp
dom/workers/ServiceWorker.h
dom/workers/ServiceWorkerContainer.cpp
dom/workers/ServiceWorkerContainer.h
dom/workers/ServiceWorkerManager.cpp
dom/workers/ServiceWorkerManager.h
dom/workers/moz.build
dom/workers/test/serviceworkers/mochitest.ini
dom/workers/test/serviceworkers/test_installation_simple.html
layout/build/nsLayoutCID.h
layout/build/nsLayoutModule.cpp
--- a/dom/interfaces/base/moz.build
+++ b/dom/interfaces/base/moz.build
@@ -26,16 +26,17 @@ XPIDL_SOURCES += [
     'nsIDOMScreen.idl',
     'nsIDOMWindow.idl',
     'nsIDOMWindowCollection.idl',
     'nsIDOMWindowUtils.idl',
     'nsIFocusManager.idl',
     'nsIFrameRequestCallback.idl',
     'nsIIdleObserver.idl',
     'nsIQueryContentEventResult.idl',
+    'nsIServiceWorkerManager.idl',
     'nsIStructuredCloneContainer.idl',
     'nsITabChild.idl',
     'nsITabParent.idl',
 ]
 
 if CONFIG['MOZ_DISABLE_CRYPTOLEGACY']:
     XPIDL_SOURCES += [
         'nsIDOMCrypto.idl',
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -0,0 +1,21 @@
+/* -*- 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 "domstubs.idl"
+
+[uuid(d9539ecb-6665-452c-bae7-4e42f25d23aa)]
+interface nsIServiceWorkerManager : nsISupports
+{
+  // Returns a Promise
+  nsISupports register(in nsIDOMWindow aWindow, in DOMString aScope, in DOMString aScriptURI);
+
+  // Returns a Promise
+  nsISupports unregister(in nsIDOMWindow aWindow, in DOMString aScope);
+
+};
+
+%{ C++
+#define SERVICEWORKERMANAGER_CONTRACTID "@mozilla.org/serviceworkers/manager;1"
+%}
--- a/dom/workers/ServiceWorker.cpp
+++ b/dom/workers/ServiceWorker.cpp
@@ -39,8 +39,18 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(Servi
 
 JSObject*
 ServiceWorker::WrapObject(JSContext* aCx)
 {
   AssertIsOnMainThread();
 
   return ServiceWorkerBinding::Wrap(aCx, this);
 }
+
+WorkerPrivate*
+ServiceWorker::GetWorkerPrivate() const
+{
+  // At some point in the future, this may be optimized to terminate a worker
+  // that hasn't been used in a certain amount of time or when there is memory
+  // pressure or similar.
+  MOZ_ASSERT(mSharedWorker);
+  return mSharedWorker->GetWorkerPrivate();
+}
--- a/dom/workers/ServiceWorker.h
+++ b/dom/workers/ServiceWorker.h
@@ -47,16 +47,19 @@ public:
   }
 
   void
   GetUrl(nsString& aURL) const
   {
     aURL = mURL;
   }
 
+  WorkerPrivate*
+  GetWorkerPrivate() const;
+
 private:
   // This class can only be created from the RuntimeService.
   ServiceWorker(nsPIDOMWindow* aWindow, SharedWorker* aSharedWorker);
 
   // This class is reference-counted and will be destroyed from Release().
   ~ServiceWorker();
 
   ServiceWorkerState mState;
--- a/dom/workers/ServiceWorkerContainer.cpp
+++ b/dom/workers/ServiceWorkerContainer.cpp
@@ -1,19 +1,22 @@
 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
 /* vim: set ts=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 "ServiceWorkerContainer.h"
 
+#include "nsIDocument.h"
+#include "nsIServiceWorkerManager.h"
 #include "nsPIDOMWindow.h"
 
 #include "nsCycleCollectionParticipant.h"
+#include "nsServiceManagerUtils.h"
 
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ServiceWorkerContainerBinding.h"
 #include "mozilla/dom/workers/bindings/ServiceWorker.h"
 
 namespace mozilla {
 namespace dom {
 namespace workers {
@@ -32,28 +35,56 @@ ServiceWorkerContainer::WrapObject(JSCon
   return ServiceWorkerContainerBinding::Wrap(aCx, this);
 }
 
 already_AddRefed<Promise>
 ServiceWorkerContainer::Register(const nsAString& aScriptURL,
                                  const RegistrationOptionList& aOptions,
                                  ErrorResult& aRv)
 {
-  // FIXME(nsm): Bug 984048
-  aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
-  return nullptr;
+  nsCOMPtr<nsISupports> promise;
+
+  nsresult rv;
+  nsCOMPtr<nsIServiceWorkerManager> swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  aRv = swm->Register(mWindow, aOptions.mScope, aScriptURL, 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>
 ServiceWorkerContainer::Unregister(const nsAString& aScope,
                                    ErrorResult& aRv)
 {
-  // FIXME(nsm): Bug 984048
-  aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
-  return nullptr;
+  nsCOMPtr<nsISupports> promise;
+
+  nsresult rv;
+  nsCOMPtr<nsIServiceWorkerManager> swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  aRv = swm->Unregister(mWindow, aScope, getter_AddRefs(promise));
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> ret = static_cast<Promise*>(promise.get());
+  MOZ_ASSERT(ret);
+  return ret.forget();
 }
 
 already_AddRefed<workers::ServiceWorker>
 ServiceWorkerContainer::GetInstalling()
 {
   // FIXME(nsm): Bug 1002570
   return nullptr;
 }
--- a/dom/workers/ServiceWorkerContainer.h
+++ b/dom/workers/ServiceWorkerContainer.h
@@ -31,16 +31,17 @@ public:
   IMPL_EVENT_HANDLER(updatefound)
   IMPL_EVENT_HANDLER(currentchange)
   IMPL_EVENT_HANDLER(reloadpage)
   IMPL_EVENT_HANDLER(error)
 
   explicit ServiceWorkerContainer(nsPIDOMWindow* aWindow)
     : mWindow(aWindow)
   {
+    // FIXME(nsm): Bug 983497. Here the NSW should hook into SWM to be notified of events.
     SetIsDOMBinding();
   }
 
   nsPIDOMWindow*
   GetParentObject() const
   {
     return mWindow;
   }
@@ -77,17 +78,19 @@ public:
 
   // Testing only.
   void
   GetControllingWorkerScriptURLForPath(const nsAString& aPath,
                                        nsString& aScriptURL,
                                        ErrorResult& aRv);
 private:
   ~ServiceWorkerContainer()
-  { }
+  {
+    // FIXME(nsm): Bug 983497. Unhook from events.
+  }
 
   nsCOMPtr<nsPIDOMWindow> mWindow;
 };
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
 
new file mode 100644
--- /dev/null
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -0,0 +1,306 @@
+/* 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 "ServiceWorkerManager.h"
+
+#include "nsIDocument.h"
+#include "nsPIDOMWindow.h"
+
+#include "jsapi.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "nsContentUtils.h"
+#include "nsCxPusher.h"
+#include "nsNetUtil.h"
+#include "nsTArray.h"
+
+#include "RuntimeService.h"
+#include "ServiceWorker.h"
+#include "WorkerInlines.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+BEGIN_WORKERS_NAMESPACE
+
+//////////////////////////
+// ServiceWorkerManager //
+//////////////////////////
+
+NS_IMPL_ADDREF(ServiceWorkerManager)
+NS_IMPL_RELEASE(ServiceWorkerManager)
+
+NS_INTERFACE_MAP_BEGIN(ServiceWorkerManager)
+  NS_INTERFACE_MAP_ENTRY(nsIServiceWorkerManager)
+  if (aIID.Equals(NS_GET_IID(ServiceWorkerManager)))
+    foundInterface = static_cast<nsIServiceWorkerManager*>(this);
+  else
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIServiceWorkerManager)
+NS_INTERFACE_MAP_END
+
+ServiceWorkerManager::ServiceWorkerManager()
+{
+}
+
+ServiceWorkerManager::~ServiceWorkerManager()
+{
+  // The map will assert if it is not empty when destroyed.
+  mDomainMap.EnumerateRead(CleanupServiceWorkerInformation, nullptr);
+  mDomainMap.Clear();
+}
+
+/* static */ PLDHashOperator
+ServiceWorkerManager::CleanupServiceWorkerInformation(const nsACString& aDomain,
+                                                      ServiceWorkerDomainInfo* aDomainInfo,
+                                                      void *aUnused)
+{
+  aDomainInfo->mServiceWorkerRegistrations.Clear();
+  return PL_DHASH_NEXT;
+}
+
+/*
+ * Implements the async aspects of the register algorithm.
+ */
+class RegisterRunnable : public nsRunnable
+{
+  nsCOMPtr<nsPIDOMWindow> mWindow;
+  const nsCString mScope;
+  nsCOMPtr<nsIURI> mScriptURI;
+  nsRefPtr<Promise> mPromise;
+public:
+  RegisterRunnable(nsPIDOMWindow* aWindow, const nsCString aScope,
+                   nsIURI* aScriptURI, Promise* aPromise)
+    : mWindow(aWindow), mScope(aScope), mScriptURI(aScriptURI),
+      mPromise(aPromise)
+  { }
+
+  NS_IMETHODIMP
+  Run()
+  {
+    nsCString domain;
+    nsresult rv = mScriptURI->GetHost(domain);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mPromise->MaybeReject(rv);
+      return NS_OK;
+    }
+
+    nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+    ServiceWorkerManager::ServiceWorkerDomainInfo* domainInfo =
+      swm->mDomainMap.Get(domain);
+    // FIXME(nsm): Refactor this pattern.
+    if (!swm->mDomainMap.Get(domain, &domainInfo)) {
+      domainInfo = new ServiceWorkerManager::ServiceWorkerDomainInfo;
+      swm->mDomainMap.Put(domain, domainInfo);
+    }
+
+    nsRefPtr<ServiceWorkerRegistration> registration = domainInfo->GetRegistration(mScope);
+
+    nsCString spec;
+    rv = mScriptURI->GetSpec(spec);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mPromise->MaybeReject(rv);
+      return NS_OK;
+    }
+
+    if (registration) {
+      registration->mPendingUninstall = false;
+      if (spec.Equals(registration->mScriptSpec)) {
+        // FIXME(nsm): Force update on Shift+Reload. Algorithm not specified for
+        // that yet.
+
+        // There is an existing update in progress. Resolve with whatever it
+        // results in.
+        if (registration->HasUpdatePromise()) {
+          registration->AddUpdatePromiseObserver(mPromise);
+          return NS_OK;
+        }
+
+        // There is no update in progress and since SW updating is upto the UA, we
+        // will not update right now. Simply resolve with whatever worker we
+        // have.
+        ServiceWorkerInfo info = registration->Newest();
+        if (info.IsValid()) {
+          nsRefPtr<ServiceWorker> serviceWorker;
+          nsresult rv =
+            swm->CreateServiceWorkerForWindow(mWindow,
+                                              info.GetScriptSpec(),
+                                              registration->mScope,
+                                              getter_AddRefs(serviceWorker));
+
+          if (NS_WARN_IF(NS_FAILED(rv))) {
+            return NS_ERROR_FAILURE;
+          }
+
+          mPromise->MaybeResolve(serviceWorker);
+          return NS_OK;
+        }
+      }
+    } else {
+      registration = domainInfo->CreateNewRegistration(mScope);
+    }
+
+    registration->mScriptSpec = spec;
+
+    // FIXME(nsm): Call Update. Same bug, different patch.
+    // For now if the registration reaches this spot, the promise remains
+    // unresolved.
+    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();
+  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;
+  }
+
+  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.
+
+  nsresult rv;
+  // FIXME(nsm): Bug 1003991. Disable check when devtools are open.
+  if (!Preferences::GetBool("dom.serviceWorkers.testing.enabled")) {
+    bool isHttps;
+    rv = documentURI->SchemeIs("https", &isHttps);
+    if (NS_FAILED(rv) || !isHttps) {
+      NS_WARNING("ServiceWorker registration from insecure websites is not allowed.");
+      return NS_ERROR_DOM_SECURITY_ERR;
+    }
+  }
+
+  nsCOMPtr<nsIPrincipal> documentPrincipal;
+  if (window->GetExtantDoc()) {
+    documentPrincipal = window->GetExtantDoc()->NodePrincipal();
+  } else {
+    documentPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1");
+  }
+
+  nsCOMPtr<nsIURI> scriptURI;
+  rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptURL, nullptr, documentURI);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // https://github.com/slightlyoff/ServiceWorker/issues/262
+  // allowIfInheritsPrincipal: allow data: URLs for now.
+  rv = documentPrincipal->CheckMayLoad(scriptURI, true /* report */,
+                                       true /* allowIfInheritsPrincipal */);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsCOMPtr<nsIURI> scopeURI;
+  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;
+  }
+
+  nsCString cleanedScope;
+  rv = scopeURI->GetSpec(cleanedScope);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsRefPtr<nsIRunnable> registerRunnable =
+    new RegisterRunnable(window, cleanedScope, scriptURI, promise);
+  promise.forget(aPromise);
+  return NS_DispatchToCurrentThread(registerRunnable);
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::Update(ServiceWorkerRegistration* aRegistration,
+                             nsPIDOMWindow* aWindow)
+{
+  // FIXME(nsm): Same bug, different patch.
+  return NS_OK;
+}
+
+// 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.
+
+  return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+}
+
+/* static */
+already_AddRefed<ServiceWorkerManager>
+ServiceWorkerManager::GetInstance()
+{
+  nsCOMPtr<nsIServiceWorkerManager> swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID);
+  nsRefPtr<ServiceWorkerManager> concrete = do_QueryObject(swm);
+  return concrete.forget();
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow,
+                                                   const nsACString& aScriptSpec,
+                                                   const nsACString& aScope,
+                                                   ServiceWorker** aServiceWorker)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aWindow);
+
+  RuntimeService* rs = RuntimeService::GetOrCreateService();
+  nsRefPtr<ServiceWorker> serviceWorker;
+
+  nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(aWindow);
+
+  AutoSafeJSContext cx;
+  JS::Rooted<JSObject*> jsGlobal(cx, sgo->GetGlobalJSObject());
+  JSAutoCompartment ac(cx, jsGlobal);
+
+  GlobalObject global(cx, jsGlobal);
+  nsresult rv = rs->CreateServiceWorker(global,
+                                        NS_ConvertUTF8toUTF16(aScriptSpec),
+                                        aScope,
+                                        getter_AddRefs(serviceWorker));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  serviceWorker.forget(aServiceWorker);
+  return rv;
+}
+
+END_WORKERS_NAMESPACE
new file mode 100644
--- /dev/null
+++ b/dom/workers/ServiceWorkerManager.h
@@ -0,0 +1,211 @@
+/* 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_serviceworkermanager_h
+#define mozilla_dom_workers_serviceworkermanager_h
+
+#include "nsIServiceWorkerManager.h"
+#include "nsCOMPtr.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Promise.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsTWeakRef.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+class ServiceWorker;
+
+/*
+ * Wherever the spec treats a worker instance and a description of said worker
+ * as the same thing; i.e. "Resolve foo with
+ * _GetNewestWorker(serviceWorkerRegistration)", we represent the description
+ * by this class and spawn a ServiceWorker in the right global when required.
+ */
+class ServiceWorkerInfo
+{
+  const nsCString mScriptSpec;
+public:
+
+  bool
+  IsValid() const
+  {
+    return !mScriptSpec.IsVoid();
+  }
+
+  const nsCString&
+  GetScriptSpec() const
+  {
+    MOZ_ASSERT(IsValid());
+    return mScriptSpec;
+  }
+
+  ServiceWorkerInfo()
+  { }
+
+  explicit ServiceWorkerInfo(const nsACString& aScriptSpec)
+    : mScriptSpec(aScriptSpec)
+  { }
+};
+
+class ServiceWorkerRegistration
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING(ServiceWorkerRegistration)
+
+  nsCString mScope;
+  // The scriptURL for the registration. This may be completely different from
+  // the URLs of the following three workers.
+  nsCString mScriptSpec;
+
+  ServiceWorkerInfo mCurrentWorker;
+  ServiceWorkerInfo mWaitingWorker;
+  ServiceWorkerInfo mInstallingWorker;
+
+  bool mHasUpdatePromise;
+
+  void
+  AddUpdatePromiseObserver(Promise* aPromise)
+  {
+    // FIXME(nsm): Same bug, different patch.
+  }
+
+  bool
+  HasUpdatePromise()
+  {
+    return mHasUpdatePromise;
+  }
+
+  // When unregister() is called on a registration, it is not immediately
+  // removed since documents may be controlled. It is marked as
+  // pendingUninstall and when all controlling documents go away, removed.
+  bool mPendingUninstall;
+
+  explicit ServiceWorkerRegistration(const nsACString& aScope)
+    : mScope(aScope),
+      mHasUpdatePromise(false),
+      mPendingUninstall(false)
+  { }
+
+  ServiceWorkerInfo
+  Newest() const
+  {
+    if (mInstallingWorker.IsValid()) {
+      return mInstallingWorker;
+    } else if (mWaitingWorker.IsValid()) {
+      return mWaitingWorker;
+    } else {
+      return mCurrentWorker;
+    }
+  }
+};
+
+#define NS_SERVICEWORKERMANAGER_IMPL_IID                 \
+{ /* f4f8755a-69ca-46e8-a65d-775745535990 */             \
+  0xf4f8755a,                                            \
+  0x69ca,                                                \
+  0x46e8,                                                \
+  { 0xa6, 0x5d, 0x77, 0x57, 0x45, 0x53, 0x59, 0x90 }     \
+}
+
+/*
+ * The ServiceWorkerManager is a per-process global that deals with the
+ * installation, querying and event dispatch of ServiceWorkers for all the
+ * origins in the process.
+ */
+class ServiceWorkerManager MOZ_FINAL : public nsIServiceWorkerManager
+{
+  friend class RegisterRunnable;
+
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISERVICEWORKERMANAGER
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_SERVICEWORKERMANAGER_IMPL_IID)
+
+  static ServiceWorkerManager* FactoryCreate()
+  {
+    ServiceWorkerManager* res = new ServiceWorkerManager;
+    NS_ADDREF(res);
+    return res;
+  }
+
+  /*
+   * This struct is used for passive ServiceWorker management.
+   * Actively running ServiceWorkers use the SharedWorker infrastructure in
+   * RuntimeService for execution and lifetime management.
+   */
+  struct ServiceWorkerDomainInfo
+  {
+    // Scope to registration.
+    nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerRegistration> mServiceWorkerRegistrations;
+
+    ServiceWorkerDomainInfo()
+    { }
+
+    already_AddRefed<ServiceWorkerRegistration>
+    GetRegistration(const nsCString& aScope) const
+    {
+      nsRefPtr<ServiceWorkerRegistration> reg;
+      mServiceWorkerRegistrations.Get(aScope, getter_AddRefs(reg));
+      return reg.forget();
+    }
+
+    ServiceWorkerRegistration*
+    CreateNewRegistration(const nsCString& aScope)
+    {
+      ServiceWorkerRegistration* registration =
+        new ServiceWorkerRegistration(aScope);
+      // From now on ownership of registration is with
+      // mServiceWorkerRegistrations.
+      mServiceWorkerRegistrations.Put(aScope, registration);
+      return registration;
+    }
+  };
+
+  nsClassHashtable<nsCStringHashKey, ServiceWorkerDomainInfo> mDomainMap;
+
+  // FIXME(nsm): What do we do if a page calls for register("/foo_worker.js", { scope: "/*"
+  // }) and then another page calls register("/bar_worker.js", { scope: "/*" })
+  // while the install is in progress. The async install steps for register
+  // bar_worker.js could finish before foo_worker.js, but bar_worker still has
+  // to be the winning controller.
+  // FIXME(nsm): Move this into per domain?
+
+  static already_AddRefed<ServiceWorkerManager>
+  GetInstance();
+
+private:
+  ServiceWorkerManager();
+  ~ServiceWorkerManager();
+
+  NS_IMETHOD
+  Update(ServiceWorkerRegistration* aRegistration, nsPIDOMWindow* aWindow);
+
+  NS_IMETHODIMP
+  CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow,
+                               const nsACString& aScriptSpec,
+                               const nsACString& aScope,
+                               ServiceWorker** aServiceWorker);
+
+  static PLDHashOperator
+  CleanupServiceWorkerInformation(const nsACString& aDomain,
+                                  ServiceWorkerDomainInfo* aDomainInfo,
+                                  void *aUnused);
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(ServiceWorkerManager,
+                              NS_SERVICEWORKERMANAGER_IMPL_IID);
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkermanager_h
--- a/dom/workers/moz.build
+++ b/dom/workers/moz.build
@@ -10,16 +10,17 @@ TEST_DIRS += ['test']
 EXPORTS.mozilla.dom += [
     'ServiceWorkerContainer.h',
     'WorkerPrivate.h',
     'WorkerRunnable.h',
     'WorkerScope.h',
 ]
 
 EXPORTS.mozilla.dom.workers += [
+    'ServiceWorkerManager.h',
     'Workers.h',
 ]
 
 # Stuff needed for the bindings, not really public though.
 EXPORTS.mozilla.dom.workers.bindings += [
     'DataStore.h',
     'DataStoreCursor.h',
     'FileReaderSync.h',
@@ -45,16 +46,17 @@ SOURCES += [
     'Navigator.cpp',
     'Principal.cpp',
     'RegisterBindings.cpp',
     'RuntimeService.cpp',
     'ScriptLoader.cpp',
     'ServiceWorker.cpp',
     'ServiceWorkerContainer.cpp',
     'ServiceWorkerEvents.cpp',
+    'ServiceWorkerManager.cpp',
     'SharedWorker.cpp',
     'URL.cpp',
     'WorkerPrivate.cpp',
     'WorkerRunnable.cpp',
     'WorkerScope.cpp',
     'XMLHttpRequest.cpp',
     'XMLHttpRequestUpload.cpp',
 ]
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -1,1 +1,2 @@
+[test_installation_simple.html]
 [test_navigator.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_installation_simple.html
@@ -0,0 +1,79 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 930348 - test stub Navigator ServiceWorker utilities.</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() {
+    var p = navigator.serviceWorker.register("/fake_worker.js");
+    ok(p instanceof Promise, "register() should return a Promise");
+    return Promise.resolve();
+  }
+
+  function sameOriginWorker() {
+    p = navigator.serviceWorker.register("http://some-other-origin/worker.js");
+    return p.then(function(w) {
+      ok(false, "Worker from different origin should fail");
+    }, function(e) {
+      ok(e.name === "SecurityError", "Should fail with a SecurityError");
+    });
+  }
+
+  function sameOriginScope() {
+    p = navigator.serviceWorker.register("worker.js", { scope: "http://www.example.com/*" });
+    return p.then(function(w) {
+      ok(false, "Worker controlling scope for different origin should fail");
+    }, function(e) {
+      ok(e.name === "SecurityError", "Should fail with a SecurityError");
+    });
+  }
+
+  function httpsOnly() {
+    var promise = new Promise(function(resolve) {
+      SpecialPowers.pushPrefEnv({'set': [["dom.serviceWorkers.testing.enabled", false]] }, resolve);
+    });
+
+    return promise.then(function() {
+      return navigator.serviceWorker.register("/worker.js");
+    }).then(function(w) {
+      ok(false, "non-HTTPS pages cannot register ServiceWorkers");
+    }, function(e) {
+      ok(e.name === "SecurityError", "Should fail with a SecurityError");
+    });
+  }
+
+  function runTest() {
+    simpleRegister()
+      .then(sameOriginWorker)
+      .then(sameOriginScope)
+      .then(httpsOnly)
+      // 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.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+  ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
--- a/layout/build/nsLayoutCID.h
+++ b/layout/build/nsLayoutCID.h
@@ -77,9 +77,13 @@
 // {3160e271-138d-4cc7-9d63-6429f16957c7}
 #define DOMREQUEST_SERVICE_CID \
 { 0x3160e271, 0x138d, 0x4cc7, { 0x9d, 0x63, 0x64, 0x29, 0xf1, 0x69, 0x57, 0xc7 } }
 
 // {5a75c25a-5e7e-4d90-8f7c-07eb15cc0aa8}
 #define QUOTA_MANAGER_CID \
 { 0x5a75c25a, 0x5e7e, 0x4d90, { 0x8f, 0x7c, 0x07, 0xeb, 0x15, 0xcc, 0x0a, 0xa8 } }
 
+// {c74bde32-bcc7-4840-8430-c733351b212a}
+#define SERVICEWORKERMANAGER_CID \
+{ 0xc74bde32, 0xbcc7, 0x4840, { 0x84, 0x30, 0xc7, 0x33, 0x35, 0x1b, 0x21, 0x2a } }
+
 #endif /* nsLayoutCID_h__ */
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -89,16 +89,17 @@
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/DOMRequest.h"
 #include "mozilla/dom/indexedDB/IndexedDatabaseManager.h"
 #include "mozilla/dom/network/TCPSocketChild.h"
 #include "mozilla/dom/network/TCPSocketParent.h"
 #include "mozilla/dom/network/TCPServerSocketChild.h"
 #include "mozilla/dom/network/UDPSocketChild.h"
 #include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "mozilla/OSFileConstants.h"
 #include "mozilla/Services.h"
 
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/FakeSpeechRecognitionService.h"
 #include "mozilla/dom/nsSynthVoiceRegistry.h"
 #endif
 
@@ -249,16 +250,17 @@ static void Shutdown();
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::dom::mobilemessage;
 using namespace mozilla::dom::telephony;
 using mozilla::dom::alarm::AlarmHalService;
 using mozilla::dom::indexedDB::IndexedDatabaseManager;
 using mozilla::dom::power::PowerManagerService;
 using mozilla::dom::quota::QuotaManager;
+using mozilla::dom::workers::ServiceWorkerManager;
 using mozilla::dom::TCPSocketChild;
 using mozilla::dom::TCPSocketParent;
 using mozilla::dom::TCPServerSocketChild;
 using mozilla::dom::UDPSocketChild;
 using mozilla::dom::time::TimeService;
 using mozilla::net::StreamingProtocolControllerService;
 using mozilla::gmp::GeckoMediaPluginService;
 
@@ -289,16 +291,18 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(DOMSessio
 NS_GENERIC_FACTORY_CONSTRUCTOR(DOMLocalStorageManager)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsChannelPolicy)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(IndexedDatabaseManager,
                                          IndexedDatabaseManager::FactoryCreate)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(DOMRequestService,
                                          DOMRequestService::FactoryCreate)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(QuotaManager,
                                          QuotaManager::FactoryCreate)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(ServiceWorkerManager,
+                                         ServiceWorkerManager::FactoryCreate)
 #ifdef MOZ_WIDGET_GONK
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(SystemWorkerManager,
                                          SystemWorkerManager::FactoryCreate)
 #endif
 #ifdef MOZ_B2G_BT
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(BluetoothService,
                                          BluetoothService::FactoryCreate)
 #endif
@@ -717,16 +721,17 @@ NS_DEFINE_NAMED_CID(NS_XMLHTTPREQUEST_CI
 NS_DEFINE_NAMED_CID(NS_DOMPARSER_CID);
 NS_DEFINE_NAMED_CID(NS_DOMSESSIONSTORAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_DOMLOCALSTORAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_DOMJSON_CID);
 NS_DEFINE_NAMED_CID(NS_TEXTEDITOR_CID);
 NS_DEFINE_NAMED_CID(INDEXEDDB_MANAGER_CID);
 NS_DEFINE_NAMED_CID(DOMREQUEST_SERVICE_CID);
 NS_DEFINE_NAMED_CID(QUOTA_MANAGER_CID);
+NS_DEFINE_NAMED_CID(SERVICEWORKERMANAGER_CID);
 #ifdef MOZ_WIDGET_GONK
 NS_DEFINE_NAMED_CID(SYSTEMWORKERMANAGER_CID);
 #endif
 #ifdef MOZ_B2G_BT
 NS_DEFINE_NAMED_CID(BLUETOOTHSERVICE_CID);
 #endif
 #ifdef MOZ_WIDGET_GONK
 NS_DEFINE_NAMED_CID(NS_AUDIOMANAGER_CID);
@@ -1007,16 +1012,17 @@ static const mozilla::Module::CIDEntry k
   { &kNS_XPCEXCEPTION_CID, false, nullptr, ExceptionConstructor },
   { &kNS_DOMSESSIONSTORAGEMANAGER_CID, false, nullptr, DOMSessionStorageManagerConstructor },
   { &kNS_DOMLOCALSTORAGEMANAGER_CID, false, nullptr, DOMLocalStorageManagerConstructor },
   { &kNS_DOMJSON_CID, false, nullptr, NS_NewJSON },
   { &kNS_TEXTEDITOR_CID, false, nullptr, nsPlaintextEditorConstructor },
   { &kINDEXEDDB_MANAGER_CID, false, nullptr, IndexedDatabaseManagerConstructor },
   { &kDOMREQUEST_SERVICE_CID, false, nullptr, DOMRequestServiceConstructor },
   { &kQUOTA_MANAGER_CID, false, nullptr, QuotaManagerConstructor },
+  { &kSERVICEWORKERMANAGER_CID, false, nullptr, ServiceWorkerManagerConstructor },
 #ifdef MOZ_WIDGET_GONK
   { &kSYSTEMWORKERMANAGER_CID, true, nullptr, SystemWorkerManagerConstructor },
 #endif
 #ifdef MOZ_B2G_BT
   { &kBLUETOOTHSERVICE_CID, true, nullptr, BluetoothServiceConstructor },
 #endif
 #ifdef MOZ_WIDGET_GONK
   { &kNS_AUDIOMANAGER_CID, true, nullptr, AudioManagerConstructor },
@@ -1164,16 +1170,17 @@ static const mozilla::Module::ContractID
   // Keeping the old ContractID for backward compatibility
   { "@mozilla.org/dom/storagemanager;1", &kNS_DOMLOCALSTORAGEMANAGER_CID },
   { "@mozilla.org/dom/sessionStorage-manager;1", &kNS_DOMSESSIONSTORAGEMANAGER_CID },
   { "@mozilla.org/dom/json;1", &kNS_DOMJSON_CID },
   { "@mozilla.org/editor/texteditor;1", &kNS_TEXTEDITOR_CID },
   { INDEXEDDB_MANAGER_CONTRACTID, &kINDEXEDDB_MANAGER_CID },
   { DOMREQUEST_SERVICE_CONTRACTID, &kDOMREQUEST_SERVICE_CID },
   { QUOTA_MANAGER_CONTRACTID, &kQUOTA_MANAGER_CID },
+  { SERVICEWORKERMANAGER_CONTRACTID, &kSERVICEWORKERMANAGER_CID },
 #ifdef MOZ_WIDGET_GONK
   { SYSTEMWORKERMANAGER_CONTRACTID, &kSYSTEMWORKERMANAGER_CID },
 #endif
 #ifdef MOZ_B2G_BT
   { BLUETOOTHSERVICE_CONTRACTID, &kBLUETOOTHSERVICE_CID },
 #endif
 #ifdef MOZ_WIDGET_GONK
   { NS_AUDIOMANAGER_CONTRACTID, &kNS_AUDIOMANAGER_CID },