From 4fad78d9c2f8437a2375b92c64e7951230cbbde7 Mon Sep 17 00:00:00 2001 From: Nikhil Marathe Date: Sun, 20 Jul 2014 23:25:44 -0700 Subject: [PATCH] Bug 984048 - Part 7 - Documents register themselves with corresponding ServiceWorkerInfo. r=bz --HG-- extra : transplant_source : %FD%9BD%8C%E4a%A9%CB9%17L%EE%E38%11%A1t%8A%3Bs --- content/base/src/nsDocument.cpp | 24 ++++ content/base/src/nsDocument.h | 4 + .../base/nsIServiceWorkerManager.idl | 18 ++- dom/workers/ServiceWorkerManager.cpp | 116 ++++++++++++------ dom/workers/ServiceWorkerManager.h | 78 +++++++----- 5 files changed, 171 insertions(+), 69 deletions(-) diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index 8d6f6a644dbf..ae4dd69c9c21 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -69,6 +69,7 @@ #include "mozilla/dom/TreeWalker.h" #include "nsIServiceManager.h" +#include "nsIServiceWorkerManager.h" #include "nsContentCID.h" #include "nsError.h" @@ -4465,6 +4466,24 @@ nsDocument::SetScriptGlobalObject(nsIScriptGlobalObject *aScriptGlobalObject) if (mTemplateContentsOwner && mTemplateContentsOwner != this) { mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject); } + + nsCOMPtr 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 swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID); + if (swm) { + swm->MaybeStartControlling(this); + mMaybeServiceWorkerControlled = true; + } + } } nsIScriptGlobalObject* @@ -8483,6 +8502,11 @@ nsDocument::Destroy() mRegistry = nullptr; + nsCOMPtr 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(this)); diff --git a/content/base/src/nsDocument.h b/content/base/src/nsDocument.h index 60d70defbfa9..13df36289bb8 100644 --- a/content/base/src/nsDocument.h +++ b/content/base/src/nsDocument.h @@ -1742,6 +1742,10 @@ private: nsCOMPtr mMasterDocument; nsRefPtr 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; diff --git a/dom/interfaces/base/nsIServiceWorkerManager.idl b/dom/interfaces/base/nsIServiceWorkerManager.idl index d741aeb16599..ba4a6487e2de 100644 --- a/dom/interfaces/base/nsIServiceWorkerManager.idl +++ b/dom/interfaces/base/nsIServiceWorkerManager.idl @@ -5,9 +5,10 @@ #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 @@ -20,6 +21,21 @@ interface nsIServiceWorkerManager : nsISupports [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); }; diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index be38156f9d64..8b01ce2d01ca 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -11,7 +11,6 @@ #include "jsapi.h" -#include "mozilla/Preferences.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/DOMError.h" #include "mozilla/dom/ErrorEvent.h" @@ -299,12 +298,15 @@ FinishFetchOnMainThreadRunnable::Run() } ServiceWorkerRegistration::ServiceWorkerRegistration(const nsACString& aScope) - : mScope(aScope), + : mControlledDocumentsCounter(0), + mScope(aScope), mPendingUninstall(false) { } ServiceWorkerRegistration::~ServiceWorkerRegistration() -{ } +{ + MOZ_ASSERT(!IsControllingDocuments()); +} ////////////////////////// // ServiceWorkerManager // @@ -360,18 +362,16 @@ public: NS_IMETHODIMP Run() { - nsCString domain; - nsresult rv = mScriptURI->GetHost(domain); - if (NS_WARN_IF(NS_FAILED(rv))) { - mPromise->MaybeReject(rv); - return NS_OK; - } - nsRefPtr swm = ServiceWorkerManager::GetInstance(); - nsRefPtr domainInfo; - // XXXnsm: This pattern can be refactored if we end up using it - // often enough. - if (!swm->mDomainMap.Get(domain, getter_AddRefs(domainInfo))) { + nsRefPtr 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; + } + domainInfo = new ServiceWorkerManager::ServiceWorkerDomainInfo; swm->mDomainMap.Put(domain, domainInfo); } @@ -380,7 +380,7 @@ public: 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; @@ -399,12 +399,12 @@ public: // 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 info = registration->Newest(); + if (info) { nsRefPtr serviceWorker; nsresult rv = swm->CreateServiceWorkerForWindow(mWindow, - info.GetScriptSpec(), + info->GetScriptSpec(), registration->mScope, getter_AddRefs(serviceWorker)); @@ -563,14 +563,14 @@ ServiceWorkerManager::Update(ServiceWorkerRegistration* aRegistration, 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(); @@ -657,7 +657,7 @@ ServiceWorkerManager::FinishFetch(ServiceWorkerRegistration* aRegistration, ResolveRegisterPromises(aRegistration, aRegistration->mScriptSpec); - ServiceWorkerInfo info(aRegistration->mScriptSpec); + nsRefPtr info = new ServiceWorkerInfo(aRegistration->mScriptSpec); Install(aRegistration, info); } @@ -680,14 +680,8 @@ ServiceWorkerManager::HandleError(JSContext* aCx, return; } - nsCString domain; - rv = uri->GetHost(domain); - if (NS_WARN_IF(NS_FAILED(rv))) { - return; - } - - nsRefPtr domainInfo; - if (!mDomainMap.Get(domain, getter_AddRefs(domainInfo))) { + nsRefPtr domainInfo = GetDomainInfo(uri); + if (!domainInfo) { return; } @@ -760,7 +754,7 @@ public: AssertIsOnMainThread(); // FIXME(nsm): Change installing worker state to redundant. // FIXME(nsm): Fire statechange. - mRegistration->mInstallingWorker.Invalidate(); + mRegistration->mInstallingWorker = nullptr; return NS_OK; } }; @@ -881,7 +875,7 @@ private: void ServiceWorkerManager::Install(ServiceWorkerRegistration* aRegistration, - ServiceWorkerInfo aServiceWorkerInfo) + ServiceWorkerInfo* aServiceWorkerInfo) { AssertIsOnMainThread(); aRegistration->mInstallingWorker = aServiceWorkerInfo; @@ -891,12 +885,12 @@ ServiceWorkerManager::Install(ServiceWorkerRegistration* aRegistration, nsRefPtr 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; } @@ -936,12 +930,11 @@ 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. @@ -995,8 +988,8 @@ ServiceWorkerManager::CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow, already_AddRefed ServiceWorkerManager::GetServiceWorkerRegistration(nsPIDOMWindow* aWindow) { - nsCOMPtr documentURI = aWindow->GetDocumentURI(); - return GetServiceWorkerRegistration(documentURI); + nsCOMPtr document = aWindow->GetExtantDoc(); + return GetServiceWorkerRegistration(document); } already_AddRefed @@ -1158,6 +1151,53 @@ ServiceWorkerManager::GetDomainInfo(const nsCString& aURL) return GetDomainInfo(uri); } +void +ServiceWorkerManager::MaybeStartControlling(nsIDocument* aDoc) +{ + AssertIsOnMainThread(); + if (!Preferences::GetBool("dom.serviceWorkers.enabled")) { + return; + } + + nsRefPtr domainInfo = GetDomainInfo(aDoc); + if (!domainInfo) { + return; + } + + nsRefPtr 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 domainInfo = GetDomainInfo(aDoc); + if (!domainInfo) { + return; + } + + nsRefPtr 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) { diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h index 98b4762371ad..872f80c80015 100644 --- a/dom/workers/ServiceWorkerManager.h +++ b/dom/workers/ServiceWorkerManager.h @@ -10,6 +10,7 @@ #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" @@ -72,35 +73,22 @@ private: * _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; + + ~ServiceWorkerInfo() + { } + public: - - bool - IsValid() const - { - return !mScriptSpec.IsVoid(); - } - - void - Invalidate() - { - mScriptSpec.SetIsVoid(true); - } + NS_INLINE_DECL_REFCOUNTING(ServiceWorkerInfo) const nsCString& GetScriptSpec() const { - MOZ_ASSERT(IsValid()); return mScriptSpec; } - ServiceWorkerInfo() - { - Invalidate(); - } - explicit ServiceWorkerInfo(const nsACString& aScriptSpec) : mScriptSpec(aScriptSpec) { } @@ -110,6 +98,8 @@ public: // non-ISupports classes. class ServiceWorkerRegistration MOZ_FINAL : public nsISupports { + uint32_t mControlledDocumentsCounter; + virtual ~ServiceWorkerRegistration(); public: @@ -120,9 +110,9 @@ public: // the URLs of the following three workers. nsCString mScriptSpec; - ServiceWorkerInfo mCurrentWorker; - ServiceWorkerInfo mWaitingWorker; - ServiceWorkerInfo mInstallingWorker; + nsRefPtr mCurrentWorker; + nsRefPtr mWaitingWorker; + nsRefPtr mInstallingWorker; nsAutoPtr mUpdatePromise; nsRefPtr mUpdateInstance; @@ -147,16 +137,37 @@ public: explicit ServiceWorkerRegistration(const nsACString& aScope); - ServiceWorkerInfo - Newest() const + already_AddRefed + Newest() { - if (mInstallingWorker.IsValid()) { - return mInstallingWorker; - } else if (mWaitingWorker.IsValid()) { - return mWaitingWorker; + nsRefPtr 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; } }; @@ -186,6 +197,11 @@ public: static ServiceWorkerManager* FactoryCreate() { + AssertIsOnMainThread(); + if (!Preferences::GetBool("dom.serviceWorkers.enabled")) { + return nullptr; + } + ServiceWorkerManager* res = new ServiceWorkerManager; NS_ADDREF(res); return res; @@ -216,6 +232,8 @@ public: // The containers inform the SWM on creation and destruction. nsTObserverArray mServiceWorkerContainers; + nsRefPtrHashtable mControlledDocuments; + ServiceWorkerDomainInfo() { } @@ -290,7 +308,7 @@ private: void Install(ServiceWorkerRegistration* aRegistration, - ServiceWorkerInfo aServiceWorkerInfo); + ServiceWorkerInfo* aServiceWorkerInfo); NS_IMETHOD CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow,