Bug 1432846 - Delay update runnables from service workers that don't control any clients. r=bkelly

This commit is contained in:
Catalin Badea 2018-02-13 19:02:58 +02:00
parent 549e302e25
commit d116fe517c
5 changed files with 127 additions and 11 deletions

View File

@ -1947,7 +1947,7 @@ void
ServiceWorkerPrivate::StoreISupports(nsISupports* aSupports) ServiceWorkerPrivate::StoreISupports(nsISupports* aSupports)
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mWorkerPrivate); MOZ_DIAGNOSTIC_ASSERT(mWorkerPrivate);
MOZ_ASSERT(!mSupportsArray.Contains(aSupports)); MOZ_ASSERT(!mSupportsArray.Contains(aSupports));
mSupportsArray.AppendElement(aSupports); mSupportsArray.AppendElement(aSupports);

View File

@ -25,6 +25,7 @@
#include "nsServiceManagerUtils.h" #include "nsServiceManagerUtils.h"
#include "ServiceWorker.h" #include "ServiceWorker.h"
#include "ServiceWorkerManager.h" #include "ServiceWorkerManager.h"
#include "ServiceWorkerPrivate.h"
#include "ServiceWorkerRegistration.h" #include "ServiceWorkerRegistration.h"
#include "nsIDocument.h" #include "nsIDocument.h"
@ -133,7 +134,7 @@ namespace {
void void
UpdateInternal(nsIPrincipal* aPrincipal, UpdateInternal(nsIPrincipal* aPrincipal,
const nsAString& aScope, const nsACString& aScope,
ServiceWorkerUpdateFinishCallback* aCallback) ServiceWorkerUpdateFinishCallback* aCallback)
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
@ -146,7 +147,7 @@ UpdateInternal(nsIPrincipal* aPrincipal,
return; return;
} }
swm->Update(aPrincipal, NS_ConvertUTF16toUTF8(aScope), aCallback); swm->Update(aPrincipal, aScope, aCallback);
} }
class MainThreadUpdateCallback final : public ServiceWorkerUpdateFinishCallback class MainThreadUpdateCallback final : public ServiceWorkerUpdateFinishCallback
@ -281,12 +282,52 @@ public:
class SWRUpdateRunnable final : public Runnable class SWRUpdateRunnable final : public Runnable
{ {
class TimerCallback final : public nsITimerCallback
{
RefPtr<ServiceWorkerPrivate> mPrivate;
RefPtr<Runnable> mRunnable;
public:
TimerCallback(ServiceWorkerPrivate* aPrivate,
Runnable* aRunnable)
: mPrivate(aPrivate)
, mRunnable(aRunnable)
{
MOZ_ASSERT(mPrivate);
MOZ_ASSERT(aRunnable);
}
NS_IMETHOD
Notify(nsITimer *aTimer) override
{
mRunnable->Run();
mPrivate->RemoveISupports(aTimer);
return NS_OK;
}
NS_DECL_THREADSAFE_ISUPPORTS
private:
~TimerCallback()
{ }
};
public: public:
SWRUpdateRunnable(PromiseWorkerProxy* aPromiseProxy, const nsAString& aScope) explicit SWRUpdateRunnable(PromiseWorkerProxy* aPromiseProxy)
: Runnable("dom::SWRUpdateRunnable") : Runnable("dom::SWRUpdateRunnable")
, mPromiseProxy(aPromiseProxy) , mPromiseProxy(aPromiseProxy)
, mScope(aScope) , mDescriptor(aPromiseProxy->GetWorkerPrivate()->GetServiceWorkerDescriptor())
{} , mDelayed(false)
{
MOZ_ASSERT(mPromiseProxy);
// This runnable is used for update calls originating from a worker thread,
// which may be delayed in some cases.
MOZ_ASSERT(mPromiseProxy->GetWorkerPrivate()->IsServiceWorker());
MOZ_ASSERT(mPromiseProxy->GetWorkerPrivate());
mPromiseProxy->GetWorkerPrivate()->AssertIsOnWorkerThread();
}
NS_IMETHOD NS_IMETHOD
Run() override Run() override
@ -307,20 +348,65 @@ public:
} }
MOZ_ASSERT(principal); MOZ_ASSERT(principal);
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (NS_WARN_IF(!swm)) {
return NS_OK;
}
// This will delay update jobs originating from a service worker thread.
// We don't currently handle ServiceWorkerRegistration.update() from other
// worker types. Also, we assume this registration matches self.registration
// on the service worker global. This is ok for now because service worker globals
// are the only worker contexts where we expose ServiceWorkerRegistration.
RefPtr<ServiceWorkerRegistrationInfo> registration =
swm->GetRegistration(principal, mDescriptor.Scope());
if (NS_WARN_IF(!registration)) {
return NS_OK;
}
RefPtr<ServiceWorkerInfo> worker = registration->GetByDescriptor(mDescriptor);
uint32_t delay = registration->GetUpdateDelay();
// if we have a timer object, it means we've already been delayed once.
if (delay && !mDelayed) {
nsCOMPtr<nsITimerCallback> cb = new TimerCallback(worker->WorkerPrivate(), this);
Result<nsCOMPtr<nsITimer>, nsresult> result =
NS_NewTimerWithCallback(cb, delay, nsITimer::TYPE_ONE_SHOT,
SystemGroup::EventTargetFor(TaskCategory::Other));
nsCOMPtr<nsITimer> timer = result.unwrapOr(nullptr);
if (NS_WARN_IF(!timer)) {
return NS_OK;
}
mDelayed = true;
// We're storing the timer object on the calling service worker's private.
// ServiceWorkerPrivate will drop the reference if the worker terminates,
// which will cancel the timer.
worker->WorkerPrivate()->StoreISupports(timer);
return NS_OK;
}
RefPtr<WorkerThreadUpdateCallback> cb = RefPtr<WorkerThreadUpdateCallback> cb =
new WorkerThreadUpdateCallback(mPromiseProxy); new WorkerThreadUpdateCallback(mPromiseProxy);
UpdateInternal(principal, mScope, cb); UpdateInternal(principal, mDescriptor.Scope(), cb);
return NS_OK; return NS_OK;
} }
private: private:
~SWRUpdateRunnable() ~SWRUpdateRunnable()
{} {
MOZ_ASSERT(NS_IsMainThread());
}
RefPtr<PromiseWorkerProxy> mPromiseProxy; RefPtr<PromiseWorkerProxy> mPromiseProxy;
const nsString mScope; const ServiceWorkerDescriptor mDescriptor;
bool mDelayed;
}; };
NS_IMPL_ISUPPORTS(SWRUpdateRunnable::TimerCallback, nsITimerCallback)
class UnregisterCallback final : public nsIServiceWorkerUnregisterCallback class UnregisterCallback final : public nsIServiceWorkerUnregisterCallback
{ {
PromiseWindowProxy mPromise; PromiseWindowProxy mPromise;
@ -533,7 +619,7 @@ ServiceWorkerRegistrationMainThread::Update(ErrorResult& aRv)
RefPtr<MainThreadUpdateCallback> cb = RefPtr<MainThreadUpdateCallback> cb =
new MainThreadUpdateCallback(mOuter->GetOwner(), promise); new MainThreadUpdateCallback(mOuter->GetOwner(), promise);
UpdateInternal(doc->NodePrincipal(), mScope, cb); UpdateInternal(doc->NodePrincipal(), NS_ConvertUTF16toUTF8(mScope), cb);
return promise.forget(); return promise.forget();
} }
@ -848,7 +934,7 @@ ServiceWorkerRegistrationWorkerThread::Update(ErrorResult& aRv)
return nullptr; return nullptr;
} }
RefPtr<SWRUpdateRunnable> r = new SWRUpdateRunnable(proxy, mScope); RefPtr<SWRUpdateRunnable> r = new SWRUpdateRunnable(proxy);
MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(r.forget())); MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(r.forget()));
return promise.forget(); return promise.forget();

View File

@ -87,6 +87,7 @@ ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(
: mPrincipal(aPrincipal) : mPrincipal(aPrincipal)
, mDescriptor(aPrincipal, aScope, aUpdateViaCache) , mDescriptor(aPrincipal, aScope, aUpdateViaCache)
, mControlledClientsCounter(0) , mControlledClientsCounter(0)
, mDelayMultiplier(0)
, mUpdateState(NoUpdate) , mUpdateState(NoUpdate)
, mCreationTime(PR_Now()) , mCreationTime(PR_Now())
, mCreationTimeStamp(TimeStamp::Now()) , mCreationTimeStamp(TimeStamp::Now())
@ -709,5 +710,25 @@ ServiceWorkerRegistrationInfo::Descriptor() const
return mDescriptor; return mDescriptor;
} }
uint32_t
ServiceWorkerRegistrationInfo::GetUpdateDelay()
{
uint32_t delay = Preferences::GetInt("dom.serviceWorkers.update_delay",
1000);
// This can potentially happen if you spam registration->Update(). We don't
// want to wrap to a lower value.
if (mDelayMultiplier >= INT_MAX / (delay ? delay : 1)) {
return INT_MAX;
}
delay *= mDelayMultiplier;
if (!mControlledClientsCounter && mDelayMultiplier < (INT_MAX / 30)) {
mDelayMultiplier = (mDelayMultiplier ? mDelayMultiplier : 1) * 30;
}
return delay;
}
} // namespace dom } // namespace dom
} // namespace mozilla } // namespace mozilla

View File

@ -23,6 +23,7 @@ class ServiceWorkerRegistrationInfo final
nsTArray<nsCOMPtr<nsIServiceWorkerRegistrationInfoListener>> mListeners; nsTArray<nsCOMPtr<nsIServiceWorkerRegistrationInfoListener>> mListeners;
uint32_t mControlledClientsCounter; uint32_t mControlledClientsCounter;
uint32_t mDelayMultiplier;
enum enum
{ {
@ -94,6 +95,7 @@ public:
StartControllingClient() StartControllingClient()
{ {
++mControlledClientsCounter; ++mControlledClientsCounter;
mDelayMultiplier = 0;
} }
void void
@ -211,6 +213,9 @@ public:
const ServiceWorkerRegistrationDescriptor& const ServiceWorkerRegistrationDescriptor&
Descriptor() const; Descriptor() const;
uint32_t
GetUpdateDelay();
private: private:
// Roughly equivalent to [[Update Registration State algorithm]]. Make sure // Roughly equivalent to [[Update Registration State algorithm]]. Make sure
// this is called *before* updating SW instances' state, otherwise they // this is called *before* updating SW instances' state, otherwise they

View File

@ -174,6 +174,10 @@ pref("dom.serviceWorkers.idle_timeout", 30000);
// The amount of time (milliseconds) service workers can be kept running using waitUntil promises. // The amount of time (milliseconds) service workers can be kept running using waitUntil promises.
pref("dom.serviceWorkers.idle_extended_timeout", 300000); pref("dom.serviceWorkers.idle_extended_timeout", 300000);
// The amount of time (milliseconds) an update request is delayed when triggered
// by a service worker that doesn't control any clients.
pref("dom.serviceWorkers.update_delay", 1000);
// Enable test for 24 hours update, service workers will always treat last update check time is over 24 hours // Enable test for 24 hours update, service workers will always treat last update check time is over 24 hours
pref("dom.serviceWorkers.testUpdateOverOneDay", false); pref("dom.serviceWorkers.testUpdateOverOneDay", false);