diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index 2a51c7daa36..07fbf2dc6da 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -3240,6 +3240,8 @@ ServiceWorkerManager::PrepareFetchEvent(const OriginAttributes& aOriginAttribute // This should only happen if IsAvailable() returned true. MOZ_ASSERT(registration->mActiveWorker); serviceWorker = registration->mActiveWorker; + + AddNavigationInterception(serviceWorker->Scope(), aChannel); } if (NS_WARN_IF(aRv.Failed()) || !serviceWorker) { @@ -4190,6 +4192,76 @@ ServiceWorkerManager::AddRegisteringDocument(const nsACString& aScope, list->AppendElement(do_GetWeakReference(aDoc)); } +class ServiceWorkerManager::InterceptionReleaseHandle final : public nsISupports +{ + const nsCString mScope; + + // Weak reference to channel is safe, because the channel holds a + // reference to this object. Also, the pointer is only used for + // comparison purposes. + nsIInterceptedChannel* mChannel; + + ~InterceptionReleaseHandle() + { + RefPtr swm = ServiceWorkerManager::GetInstance(); + swm->RemoveNavigationInterception(mScope, mChannel); + } + +public: + InterceptionReleaseHandle(const nsACString& aScope, + nsIInterceptedChannel* aChannel) + : mScope(aScope) + , mChannel(aChannel) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aScope.IsEmpty()); + MOZ_ASSERT(mChannel); + } + + NS_DECL_ISUPPORTS +}; + +NS_IMPL_ISUPPORTS0(ServiceWorkerManager::InterceptionReleaseHandle); + +void +ServiceWorkerManager::AddNavigationInterception(const nsACString& aScope, + nsIInterceptedChannel* aChannel) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(!aScope.IsEmpty()); + MOZ_ASSERT(aChannel); + + InterceptionList* list = + mNavigationInterceptions.LookupOrAdd(aScope); + MOZ_ASSERT(list); + MOZ_ASSERT(!list->Contains(aChannel)); + + nsCOMPtr releaseHandle = + new InterceptionReleaseHandle(aScope, aChannel); + aChannel->SetReleaseHandle(releaseHandle); + + list->AppendElement(aChannel); +} + +void +ServiceWorkerManager::RemoveNavigationInterception(const nsACString& aScope, + nsIInterceptedChannel* aChannel) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aChannel); + InterceptionList* list = + mNavigationInterceptions.Get(aScope); + if (list) { + MOZ_ALWAYS_TRUE(list->RemoveElement(aChannel)); + MOZ_ASSERT(!list->Contains(aChannel)); + if (list->IsEmpty()) { + list = nullptr; + nsAutoPtr doomed; + mNavigationInterceptions.RemoveAndForget(aScope, doomed); + } + } +} + NS_IMPL_ISUPPORTS(ServiceWorkerInfo, nsIServiceWorkerInfo) NS_IMETHODIMP diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h index 1d94ec2e074..c51822f973a 100644 --- a/dom/workers/ServiceWorkerManager.h +++ b/dom/workers/ServiceWorkerManager.h @@ -328,6 +328,18 @@ public: typedef nsTArray> WeakDocumentList; nsClassHashtable mRegisteringDocuments; + // Track all intercepted navigation channels for a given scope. Channels are + // placed in the appropriate list before dispatch the FetchEvent to the worker + // thread and removed once FetchEvent processing dispatches back to the main + // thread. + // + // Note: Its safe to use weak references here because a RAII-style callback + // is registered with the channel before its added to this list. We + // are guaranteed the callback will fire before and remove the ref + // from this list before the channel is destroyed. + typedef nsTArray InterceptionList; + nsClassHashtable mNavigationInterceptions; + bool IsAvailable(const OriginAttributes& aOriginAttributes, nsIURI* aURI); @@ -622,6 +634,16 @@ private: void AddRegisteringDocument(const nsACString& aScope, nsIDocument* aDoc); + + class InterceptionReleaseHandle; + + void + AddNavigationInterception(const nsACString& aScope, + nsIInterceptedChannel* aChannel); + + void + RemoveNavigationInterception(const nsACString& aScope, + nsIInterceptedChannel* aChannel); }; } // namespace workers diff --git a/modules/libjar/InterceptedJARChannel.cpp b/modules/libjar/InterceptedJARChannel.cpp index a47c2624793..c52e373b46c 100644 --- a/modules/libjar/InterceptedJARChannel.cpp +++ b/modules/libjar/InterceptedJARChannel.cpp @@ -56,6 +56,7 @@ InterceptedJARChannel::ResetInterception() mSynthesizedInput = nullptr; mChannel->ResetInterception(); + mReleaseHandle = nullptr; mChannel = nullptr; return NS_OK; } @@ -93,6 +94,7 @@ InterceptedJARChannel::FinishSynthesizedResponse(const nsACString& aFinalURLSpec mChannel->OverrideWithSynthesizedResponse(mSynthesizedInput, mContentType); mResponseBody = nullptr; + mReleaseHandle = nullptr; mChannel = nullptr; return NS_OK; } @@ -109,6 +111,7 @@ InterceptedJARChannel::Cancel(nsresult aStatus) nsresult rv = mChannel->Cancel(aStatus); NS_ENSURE_SUCCESS(rv, rv); mResponseBody = nullptr; + mReleaseHandle = nullptr; mChannel = nullptr; return NS_OK; } @@ -129,6 +132,16 @@ InterceptedJARChannel::GetConsoleReportCollector(nsIConsoleReportCollector**) return NS_ERROR_NOT_AVAILABLE; } +NS_IMETHODIMP +InterceptedJARChannel::SetReleaseHandle(nsISupports* aHandle) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mReleaseHandle); + MOZ_ASSERT(aHandle); + mReleaseHandle = aHandle; + return NS_OK; +} + void InterceptedJARChannel::NotifyController() { diff --git a/modules/libjar/InterceptedJARChannel.h b/modules/libjar/InterceptedJARChannel.h index f1dd2e78e7b..63fc79ba93f 100644 --- a/modules/libjar/InterceptedJARChannel.h +++ b/modules/libjar/InterceptedJARChannel.h @@ -41,6 +41,8 @@ class InterceptedJARChannel : public nsIInterceptedChannel // The stream to write the body of the synthesized response. nsCOMPtr mResponseBody; + nsCOMPtr mReleaseHandle; + // The content type of the synthesized response. nsCString mContentType; diff --git a/netwerk/base/nsINetworkInterceptController.idl b/netwerk/base/nsINetworkInterceptController.idl index 3e9ff02018c..7e59c6cae5f 100644 --- a/netwerk/base/nsINetworkInterceptController.idl +++ b/netwerk/base/nsINetworkInterceptController.idl @@ -29,7 +29,7 @@ class ChannelInfo; * which do not implement nsIChannel. */ -[scriptable, uuid(231bb567-90e1-4973-9728-7dab93ab29a8)] +[scriptable, uuid(64439e24-eda5-4f39-9a7e-162c4b5e0150)] interface nsIInterceptedChannel : nsISupports { /** @@ -100,6 +100,14 @@ interface nsIInterceptedChannel : nsISupports return reporter.forget(); } %} + + /** + * Allow the ServiceWorkerManager to set an RAII-style object on the + * intercepted channel that should be released once the channel is + * torn down. + */ + [noscript] + void setReleaseHandle(in nsISupports aHandle); }; /** diff --git a/netwerk/protocol/http/InterceptedChannel.cpp b/netwerk/protocol/http/InterceptedChannel.cpp index c36351955d5..b608c568e12 100644 --- a/netwerk/protocol/http/InterceptedChannel.cpp +++ b/netwerk/protocol/http/InterceptedChannel.cpp @@ -118,6 +118,16 @@ InterceptedChannelBase::GetConsoleReportCollector(nsIConsoleReportCollector** aC return NS_OK; } +NS_IMETHODIMP +InterceptedChannelBase::SetReleaseHandle(nsISupports* aHandle) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mReleaseHandle); + MOZ_ASSERT(aHandle); + mReleaseHandle = aHandle; + return NS_OK; +} + InterceptedChannelChrome::InterceptedChannelChrome(nsHttpChannel* aChannel, nsINetworkInterceptController* aController, nsICacheEntry* aEntry) @@ -172,6 +182,7 @@ InterceptedChannelChrome::ResetInterception() nsresult rv = mChannel->StartRedirectChannelToURI(uri, nsIChannelEventSink::REDIRECT_INTERNAL); NS_ENSURE_SUCCESS(rv, rv); + mReleaseHandle = nullptr; mChannel = nullptr; return NS_OK; } @@ -265,6 +276,7 @@ InterceptedChannelChrome::FinishSynthesizedResponse(const nsACString& aFinalURLS NS_ENSURE_SUCCESS(rv, rv); } } + mReleaseHandle = nullptr; mChannel = nullptr; return NS_OK; } @@ -284,6 +296,7 @@ InterceptedChannelChrome::Cancel(nsresult aStatus) // to cancel which will provide OnStart/OnStopRequest to the channel. nsresult rv = mChannel->AsyncAbort(aStatus); NS_ENSURE_SUCCESS(rv, rv); + mReleaseHandle = nullptr; return NS_OK; } @@ -349,6 +362,7 @@ InterceptedChannelContent::ResetInterception() mSynthesizedInput = nullptr; mChannel->ResetInterception(); + mReleaseHandle = nullptr; mChannel = nullptr; return NS_OK; } @@ -407,6 +421,7 @@ InterceptedChannelContent::FinishSynthesizedResponse(const nsACString& aFinalURL } mResponseBody = nullptr; + mReleaseHandle = nullptr; mChannel = nullptr; mStreamListener = nullptr; return NS_OK; @@ -427,6 +442,7 @@ InterceptedChannelContent::Cancel(nsresult aStatus) // to cancel which will provide OnStart/OnStopRequest to the channel. nsresult rv = mChannel->AsyncAbort(aStatus); NS_ENSURE_SUCCESS(rv, rv); + mReleaseHandle = nullptr; mChannel = nullptr; mStreamListener = nullptr; return NS_OK; diff --git a/netwerk/protocol/http/InterceptedChannel.h b/netwerk/protocol/http/InterceptedChannel.h index f12b2e99d6f..61e196c5454 100644 --- a/netwerk/protocol/http/InterceptedChannel.h +++ b/netwerk/protocol/http/InterceptedChannel.h @@ -37,6 +37,7 @@ protected: Maybe> mSynthesizedResponseHead; nsCOMPtr mReportCollector; + nsCOMPtr mReleaseHandle; void EnsureSynthesizedResponse(); void DoNotifyController(); @@ -55,6 +56,7 @@ public: NS_IMETHOD GetResponseBody(nsIOutputStream** aOutput) override; NS_IMETHOD GetConsoleReportCollector(nsIConsoleReportCollector** aCollectorOut) override; + NS_IMETHOD SetReleaseHandle(nsISupports* aHandle) override; }; class InterceptedChannelChrome : public InterceptedChannelBase