Bug 984048 - Part 7 - Documents register themselves with corresponding ServiceWorkerInfo. r=bz
authorNikhil Marathe <nsm.nikhil@gmail.com>
Sun, 20 Jul 2014 23:25:44 -0700
changeset 217160 c50f8822c94ec38f3b94aeee7baa7041595c235c
parent 217159 bd974f99638b2753fba77a5fbe39c4f31d60a8fb
child 217161 0d32479e8b65a66b04a1715fe4f9df9c4fc0ee4c
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs984048
milestone33.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 984048 - Part 7 - Documents register themselves with corresponding ServiceWorkerInfo. r=bz
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
dom/interfaces/base/nsIServiceWorkerManager.idl
dom/workers/ServiceWorkerManager.cpp
dom/workers/ServiceWorkerManager.h
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -64,16 +64,17 @@
 #include "nsRange.h"
 #include "nsIDOMText.h"
 #include "nsIDOMComment.h"
 #include "mozilla/dom/DocumentType.h"
 #include "mozilla/dom/NodeIterator.h"
 #include "mozilla/dom/TreeWalker.h"
 
 #include "nsIServiceManager.h"
+#include "nsIServiceWorkerManager.h"
 
 #include "nsContentCID.h"
 #include "nsError.h"
 #include "nsPresShell.h"
 #include "nsPresContext.h"
 #include "nsIJSON.h"
 #include "nsThreadUtils.h"
 #include "nsNodeInfoManager.h"
@@ -4460,16 +4461,34 @@ nsDocument::SetScriptGlobalObject(nsIScr
   // doing the initial document load and don't want to fire the event for this
   // change.
   mVisibilityState = GetVisibilityState();
 
   // The global in the template contents owner document should be the same.
   if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
     mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
   }
+
+  nsCOMPtr<nsIChannel> channel = GetChannel();
+  if (!mMaybeServiceWorkerControlled && channel) {
+    nsLoadFlags loadFlags = 0;
+    channel->GetLoadFlags(&loadFlags);
+    // If we are shift-reloaded, don't associate with a ServiceWorker.
+    // FIXME(nsm): Bug 1041339.
+    if (loadFlags & nsIRequest::LOAD_BYPASS_CACHE) {
+      NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
+      return;
+    }
+
+    nsCOMPtr<nsIServiceWorkerManager> swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID);
+    if (swm) {
+      swm->MaybeStartControlling(this);
+      mMaybeServiceWorkerControlled = true;
+    }
+  }
 }
 
 nsIScriptGlobalObject*
 nsDocument::GetScriptHandlingObjectInternal() const
 {
   MOZ_ASSERT(!mScriptGlobalObject,
              "Do not call this when mScriptGlobalObject is set!");
   if (mHasHadDefaultView) {
@@ -8478,16 +8497,21 @@ nsDocument::Destroy()
 
   // Shut down our external resource map.  We might not need this for
   // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
   // tearing down all those frame trees right now is the right thing to do.
   mExternalResourceMap.Shutdown();
 
   mRegistry = nullptr;
 
+  nsCOMPtr<nsIServiceWorkerManager> swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID);
+  if (swm) {
+    swm->MaybeStopControlling(this);
+  }
+
   // XXX We really should let cycle collection do this, but that currently still
   //     leaks (see https://bugzilla.mozilla.org/show_bug.cgi?id=406684).
   ReleaseWrapper(static_cast<nsINode*>(this));
 }
 
 void
 nsDocument::RemovedFromDocShell()
 {
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -1737,16 +1737,20 @@ private:
   nsrefcnt mStackRefCnt;
   bool mNeedsReleaseAfterStackRefCntRelease;
 
   CSPErrorQueue mCSPWebConsoleErrorQueue;
 
   nsCOMPtr<nsIDocument> mMasterDocument;
   nsRefPtr<mozilla::dom::ImportManager> mImportManager;
 
+  // Set to true when the document is possibly controlled by the ServiceWorker.
+  // Used to prevent multiple requests to ServiceWorkerManager.
+  bool mMaybeServiceWorkerControlled;
+
 #ifdef DEBUG
 public:
   bool mWillReparent;
 #endif
 };
 
 class nsDocumentOnStack
 {
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -1,29 +1,45 @@
 /* -*- 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"
 
+interface nsIDocument;
 interface nsIURI;
 
-[uuid(6117cdf1-cb10-42a3-9901-4f1bab7ffa4d)]
+[uuid(6e1382f4-3cbc-435f-8ce0-70175f6eb400)]
 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);
 
   // aTarget MUST be a ServiceWorkerContainer.
   [noscript] void AddContainerEventListener(in nsIURI aPageURI, in nsIDOMEventTarget aTarget);
   [noscript] void RemoveContainerEventListener(in nsIURI aPageURI, in nsIDOMEventTarget aTarget);
 
+  /**
+   * Call this to request that document `aDoc` be controlled by a ServiceWorker
+   * if a registration exists for it's scope.
+   *
+   * This MUST only be called once per document!
+   */
+  [notxpcom,nostdcall] void MaybeStartControlling(in nsIDocument aDoc);
+
+  /**
+   * Documents that have called MaybeStartControlling() should call this when
+   * they are destroyed. This function may be called multiple times, and is
+   * idempotent.
+   */
+  [notxpcom,nostdcall] void MaybeStopControlling(in nsIDocument aDoc);
+
   // Testing
   DOMString getScopeForUrl(in DOMString path);
 };
 
 %{ C++
 #define SERVICEWORKERMANAGER_CONTRACTID "@mozilla.org/serviceworkers/manager;1"
 %}
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -6,17 +6,16 @@
 
 #include "nsIDOMEventTarget.h"
 #include "nsIDocument.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsPIDOMWindow.h"
 
 #include "jsapi.h"
 
-#include "mozilla/Preferences.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/ErrorEvent.h"
 #include "mozilla/dom/InstallEventBinding.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 
 #include "nsContentUtils.h"
 #include "nsCxPusher.h"
@@ -294,22 +293,25 @@ NS_IMETHODIMP
 FinishFetchOnMainThreadRunnable::Run()
 {
   AssertIsOnMainThread();
   mUpdateInstance->FetchDone();
   return NS_OK;
 }
 
 ServiceWorkerRegistration::ServiceWorkerRegistration(const nsACString& aScope)
-  : mScope(aScope),
+  : mControlledDocumentsCounter(0),
+    mScope(aScope),
     mPendingUninstall(false)
 { }
 
 ServiceWorkerRegistration::~ServiceWorkerRegistration()
-{ }
+{
+  MOZ_ASSERT(!IsControllingDocuments());
+}
 
 //////////////////////////
 // ServiceWorkerManager //
 //////////////////////////
 
 NS_IMPL_ADDREF(ServiceWorkerManager)
 NS_IMPL_RELEASE(ServiceWorkerManager)
 
@@ -355,37 +357,35 @@ public:
                    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();
+    nsRefPtr<ServiceWorkerManager::ServiceWorkerDomainInfo> domainInfo = swm->GetDomainInfo(mScriptURI);
+    if (!domainInfo) {
+      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();
-    nsRefPtr<ServiceWorkerManager::ServiceWorkerDomainInfo> domainInfo;
-    // XXXnsm: This pattern can be refactored if we end up using it
-    // often enough.
-    if (!swm->mDomainMap.Get(domain, getter_AddRefs(domainInfo))) {
       domainInfo = new ServiceWorkerManager::ServiceWorkerDomainInfo;
       swm->mDomainMap.Put(domain, domainInfo);
     }
 
     nsRefPtr<ServiceWorkerRegistration> registration =
       domainInfo->GetRegistration(mScope);
 
     nsCString spec;
-    rv = mScriptURI->GetSpec(spec);
+    nsresult 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)) {
@@ -394,22 +394,22 @@ public:
         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<ServiceWorkerInfo> info = registration->Newest();
+        if (info) {
           nsRefPtr<ServiceWorker> serviceWorker;
           nsresult rv =
             swm->CreateServiceWorkerForWindow(mWindow,
-                                              info.GetScriptSpec(),
+                                              info->GetScriptSpec(),
                                               registration->mScope,
                                               getter_AddRefs(serviceWorker));
 
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return NS_ERROR_FAILURE;
           }
 
           mPromise->MaybeResolve(serviceWorker);
@@ -558,24 +558,24 @@ ServiceWorkerManager::Update(ServiceWork
   if (aRegistration->HasUpdatePromise()) {
     NS_WARNING("Already had a UpdatePromise. Aborting that one!");
     RejectUpdatePromiseObservers(aRegistration, NS_ERROR_DOM_ABORT_ERR);
     MOZ_ASSERT(aRegistration->mUpdateInstance);
     aRegistration->mUpdateInstance->Abort();
     aRegistration->mUpdateInstance = nullptr;
   }
 
-  if (aRegistration->mInstallingWorker.IsValid()) {
+  if (aRegistration->mInstallingWorker) {
     // FIXME(nsm): Terminate the worker. We still haven't figured out worker
     // instance ownership when not associated with a window, so let's wait on
     // this.
     // FIXME(nsm): We should be setting the state on the actual worker
     // instance.
     // FIXME(nsm): Fire "statechange" on installing worker instance.
-    aRegistration->mInstallingWorker.Invalidate();
+    aRegistration->mInstallingWorker = nullptr;
   }
 
   aRegistration->mUpdatePromise = new UpdatePromise();
   // FIXME(nsm): Bug 931249. If we don't need to fetch & install, resolve
   // promise and skip this.
   // FIXME(nsm): Bug 931249. Force cache update if > 1 day.
 
   aRegistration->mUpdateInstance =
@@ -652,17 +652,17 @@ ServiceWorkerManager::FinishFetch(Servic
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     RejectUpdatePromiseObservers(aRegistration, rv);
     return;
   }
 
   ResolveRegisterPromises(aRegistration, aRegistration->mScriptSpec);
 
-  ServiceWorkerInfo info(aRegistration->mScriptSpec);
+  nsRefPtr<ServiceWorkerInfo> info = new ServiceWorkerInfo(aRegistration->mScriptSpec);
   Install(aRegistration, info);
 }
 
 void
 ServiceWorkerManager::HandleError(JSContext* aCx,
                                   const nsACString& aScope,
                                   const nsAString& aWorkerURL,
                                   nsString aMessage,
@@ -675,24 +675,18 @@ ServiceWorkerManager::HandleError(JSCont
   AssertIsOnMainThread();
 
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NS_NewURI(getter_AddRefs(uri), aScope, nullptr, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
-  nsCString domain;
-  rv = uri->GetHost(domain);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return;
-  }
-
-  nsRefPtr<ServiceWorkerDomainInfo> domainInfo;
-  if (!mDomainMap.Get(domain, getter_AddRefs(domainInfo))) {
+  nsRefPtr<ServiceWorkerDomainInfo> domainInfo = GetDomainInfo(uri);
+  if (!domainInfo) {
     return;
   }
 
   nsCString scope;
   scope.Assign(aScope);
   nsRefPtr<ServiceWorkerRegistration> registration = domainInfo->GetRegistration(scope);
   MOZ_ASSERT(registration);
 
@@ -755,17 +749,17 @@ public:
   }
 
   NS_IMETHOD
   Run() MOZ_OVERRIDE
   {
     AssertIsOnMainThread();
     // FIXME(nsm): Change installing worker state to redundant.
     // FIXME(nsm): Fire statechange.
-    mRegistration->mInstallingWorker.Invalidate();
+    mRegistration->mInstallingWorker = nullptr;
     return NS_OK;
   }
 };
 
 /*
  * Used to handle InstallEvent::waitUntil() and proceed with installation.
  */
 class FinishInstallHandler MOZ_FINAL : public PromiseNativeHandler
@@ -876,32 +870,32 @@ private:
       new FinishInstallHandler(mRegistration);
     waitUntilPromise->AppendNativeHandler(handler);
     return true;
   }
 };
 
 void
 ServiceWorkerManager::Install(ServiceWorkerRegistration* aRegistration,
-                              ServiceWorkerInfo aServiceWorkerInfo)
+                              ServiceWorkerInfo* aServiceWorkerInfo)
 {
   AssertIsOnMainThread();
   aRegistration->mInstallingWorker = aServiceWorkerInfo;
 
   nsMainThreadPtrHandle<ServiceWorkerRegistration> handle =
     new nsMainThreadPtrHolder<ServiceWorkerRegistration>(aRegistration);
 
   nsRefPtr<ServiceWorker> serviceWorker;
   nsresult rv =
-    CreateServiceWorker(aServiceWorkerInfo.GetScriptSpec(),
+    CreateServiceWorker(aServiceWorkerInfo->GetScriptSpec(),
                         aRegistration->mScope,
                         getter_AddRefs(serviceWorker));
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRegistration->mInstallingWorker.Invalidate();
+    aRegistration->mInstallingWorker = nullptr;
     return;
   }
 
   nsRefPtr<InstallEventRunnable> r =
     new InstallEventRunnable(serviceWorker->GetWorkerPrivate(), handle);
 
   AutoSafeJSContext cx;
   r->Dispatch(cx);
@@ -931,22 +925,21 @@ public:
   { }
 };
 
 void
 ServiceWorkerManager::FinishInstall(ServiceWorkerRegistration* aRegistration)
 {
   AssertIsOnMainThread();
 
-  if (aRegistration->mWaitingWorker.IsValid()) {
+  if (aRegistration->mWaitingWorker) {
     // FIXME(nsm): Actually update the state of active ServiceWorker instances.
   }
 
-  aRegistration->mWaitingWorker = aRegistration->mInstallingWorker;
-  aRegistration->mInstallingWorker.Invalidate();
+  aRegistration->mWaitingWorker = aRegistration->mInstallingWorker.forget();
 
   // FIXME(nsm): Actually update state of active ServiceWorker instances to
   // installed.
   // FIXME(nsm): Fire statechange on the instances.
 
   // FIXME(nsm): Handle replace().
 
   // FIXME(nsm): Check that no document is using the registration!
@@ -990,18 +983,18 @@ ServiceWorkerManager::CreateServiceWorke
 
   serviceWorker.forget(aServiceWorker);
   return rv;
 }
 
 already_AddRefed<ServiceWorkerRegistration>
 ServiceWorkerManager::GetServiceWorkerRegistration(nsPIDOMWindow* aWindow)
 {
-  nsCOMPtr<nsIURI> documentURI = aWindow->GetDocumentURI();
-  return GetServiceWorkerRegistration(documentURI);
+  nsCOMPtr<nsIDocument> document = aWindow->GetExtantDoc();
+  return GetServiceWorkerRegistration(document);
 }
 
 already_AddRefed<ServiceWorkerRegistration>
 ServiceWorkerManager::GetServiceWorkerRegistration(nsIDocument* aDoc)
 {
   nsCOMPtr<nsIURI> documentURI = aDoc->GetDocumentURI();
   return GetServiceWorkerRegistration(documentURI);
 }
@@ -1153,16 +1146,63 @@ ServiceWorkerManager::GetDomainInfo(cons
   nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return nullptr;
   }
 
   return GetDomainInfo(uri);
 }
 
+void
+ServiceWorkerManager::MaybeStartControlling(nsIDocument* aDoc)
+{
+  AssertIsOnMainThread();
+  if (!Preferences::GetBool("dom.serviceWorkers.enabled")) {
+    return;
+  }
+
+  nsRefPtr<ServiceWorkerDomainInfo> domainInfo = GetDomainInfo(aDoc);
+  if (!domainInfo) {
+    return;
+  }
+
+  nsRefPtr<ServiceWorkerRegistration> registration =
+    GetServiceWorkerRegistration(aDoc);
+  if (registration) {
+    MOZ_ASSERT(!domainInfo->mControlledDocuments.Contains(aDoc));
+    registration->StartControllingADocument();
+    // Use the already_AddRefed<> form of Put to avoid the addref-deref since
+    // we don't need the registration pointer in this function anymore.
+    domainInfo->mControlledDocuments.Put(aDoc, registration.forget());
+  }
+}
+
+void
+ServiceWorkerManager::MaybeStopControlling(nsIDocument* aDoc)
+{
+  MOZ_ASSERT(aDoc);
+  if (!Preferences::GetBool("dom.serviceWorkers.enabled")) {
+    return;
+  }
+
+  nsRefPtr<ServiceWorkerDomainInfo> domainInfo = GetDomainInfo(aDoc);
+  if (!domainInfo) {
+    return;
+  }
+
+  nsRefPtr<ServiceWorkerRegistration> registration;
+  domainInfo->mControlledDocuments.Remove(aDoc, getter_AddRefs(registration));
+  // A document which was uncontrolled does not maintain that state itself, so
+  // it will always call MaybeStopControlling() even if there isn't an
+  // associated registration. So this check is required.
+  if (registration) {
+    registration->StopControllingADocument();
+  }
+}
+
 NS_IMETHODIMP
 ServiceWorkerManager::GetScopeForUrl(const nsAString& aUrl, nsAString& aScope)
 {
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NS_NewURI(getter_AddRefs(uri), aUrl, nullptr, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NS_ERROR_FAILURE;
   }
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -5,16 +5,17 @@
 #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/Preferences.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ServiceWorkerContainer.h"
 #include "nsRefPtrHashtable.h"
 #include "nsTArrayForwardDeclare.h"
 #include "nsTObserverArray.h"
 #include "nsTWeakRef.h"
 
@@ -67,67 +68,56 @@ private:
 };
 
 /*
  * 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
+class ServiceWorkerInfo MOZ_FINAL
 {
   nsCString mScriptSpec;
-public:
 
-  bool
-  IsValid() const
-  {
-    return !mScriptSpec.IsVoid();
-  }
+  ~ServiceWorkerInfo()
+  { }
 
-  void
-  Invalidate()
-  {
-    mScriptSpec.SetIsVoid(true);
-  }
+public:
+  NS_INLINE_DECL_REFCOUNTING(ServiceWorkerInfo)
 
   const nsCString&
   GetScriptSpec() const
   {
-    MOZ_ASSERT(IsValid());
     return mScriptSpec;
   }
 
-  ServiceWorkerInfo()
-  {
-    Invalidate();
-  }
-
   explicit ServiceWorkerInfo(const nsACString& aScriptSpec)
     : mScriptSpec(aScriptSpec)
   { }
 };
 
 // Needs to inherit from nsISupports because NS_ProxyRelease() does not support
 // non-ISupports classes.
 class ServiceWorkerRegistration MOZ_FINAL : public nsISupports
 {
+  uint32_t mControlledDocumentsCounter;
+
   virtual ~ServiceWorkerRegistration();
 
 public:
   NS_DECL_ISUPPORTS
 
   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;
+  nsRefPtr<ServiceWorkerInfo> mCurrentWorker;
+  nsRefPtr<ServiceWorkerInfo> mWaitingWorker;
+  nsRefPtr<ServiceWorkerInfo> mInstallingWorker;
 
   nsAutoPtr<UpdatePromise> mUpdatePromise;
   nsRefPtr<ServiceWorkerUpdateInstance> mUpdateInstance;
 
   void
   AddUpdatePromiseObserver(Promise* aPromise)
   {
     MOZ_ASSERT(HasUpdatePromise());
@@ -142,26 +132,47 @@ public:
 
   // 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);
 
-  ServiceWorkerInfo
-  Newest() const
+  already_AddRefed<ServiceWorkerInfo>
+  Newest()
   {
-    if (mInstallingWorker.IsValid()) {
-      return mInstallingWorker;
-    } else if (mWaitingWorker.IsValid()) {
-      return mWaitingWorker;
+    nsRefPtr<ServiceWorkerInfo> newest;
+    if (mInstallingWorker) {
+      newest = mInstallingWorker;
+    } else if (mWaitingWorker) {
+      newest = mWaitingWorker;
     } else {
-      return mCurrentWorker;
+      newest = mCurrentWorker;
     }
+
+    return newest.forget();
+  }
+
+  void
+  StartControllingADocument()
+  {
+    ++mControlledDocumentsCounter;
+  }
+
+  void
+  StopControllingADocument()
+  {
+    --mControlledDocumentsCounter;
+  }
+
+  bool
+  IsControllingDocuments() const
+  {
+    return mControlledDocumentsCounter > 0;
   }
 };
 
 #define NS_SERVICEWORKERMANAGER_IMPL_IID                 \
 { /* f4f8755a-69ca-46e8-a65d-775745535990 */             \
   0xf4f8755a,                                            \
   0x69ca,                                                \
   0x46e8,                                                \
@@ -181,16 +192,21 @@ class ServiceWorkerManager MOZ_FINAL : p
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSISERVICEWORKERMANAGER
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_SERVICEWORKERMANAGER_IMPL_IID)
 
   static ServiceWorkerManager* FactoryCreate()
   {
+    AssertIsOnMainThread();
+    if (!Preferences::GetBool("dom.serviceWorkers.enabled")) {
+      return nullptr;
+    }
+
     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
@@ -211,16 +227,18 @@ public:
     nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerRegistration> mServiceWorkerRegistrations;
 
     // This array can't be stored in ServiceWorkerRegistration because one may
     // not exist when a certain window is opened, but we still want that
     // window's container to be notified if it's in scope.
     // The containers inform the SWM on creation and destruction.
     nsTObserverArray<ServiceWorkerContainer*> mServiceWorkerContainers;
 
+    nsRefPtrHashtable<nsISupportsHashKey, ServiceWorkerRegistration> mControlledDocuments;
+
     ServiceWorkerDomainInfo()
     { }
 
     already_AddRefed<ServiceWorkerRegistration>
     GetRegistration(const nsCString& aScope) const
     {
       nsRefPtr<ServiceWorkerRegistration> reg;
       mServiceWorkerRegistrations.Get(aScope, getter_AddRefs(reg));
@@ -285,17 +303,17 @@ private:
   ServiceWorkerManager();
   ~ServiceWorkerManager();
 
   NS_IMETHOD
   Update(ServiceWorkerRegistration* aRegistration, nsPIDOMWindow* aWindow);
 
   void
   Install(ServiceWorkerRegistration* aRegistration,
-          ServiceWorkerInfo aServiceWorkerInfo);
+          ServiceWorkerInfo* aServiceWorkerInfo);
 
   NS_IMETHOD
   CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow,
                                const nsACString& aScriptSpec,
                                const nsACString& aScope,
                                ServiceWorker** aServiceWorker);
 
   NS_IMETHOD