diff --git a/dom/serviceworkers/ServiceWorkerPrivate.cpp b/dom/serviceworkers/ServiceWorkerPrivate.cpp index 904494b6895e..d10a7ddb4a15 100644 --- a/dom/serviceworkers/ServiceWorkerPrivate.cpp +++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp @@ -1947,7 +1947,7 @@ void ServiceWorkerPrivate::StoreISupports(nsISupports* aSupports) { MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(mWorkerPrivate); + MOZ_DIAGNOSTIC_ASSERT(mWorkerPrivate); MOZ_ASSERT(!mSupportsArray.Contains(aSupports)); mSupportsArray.AppendElement(aSupports); diff --git a/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp b/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp index 3c25768fd793..81f7b6e55539 100644 --- a/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp +++ b/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp @@ -25,6 +25,7 @@ #include "nsServiceManagerUtils.h" #include "ServiceWorker.h" #include "ServiceWorkerManager.h" +#include "ServiceWorkerPrivate.h" #include "ServiceWorkerRegistration.h" #include "nsIDocument.h" @@ -133,7 +134,7 @@ namespace { void UpdateInternal(nsIPrincipal* aPrincipal, - const nsAString& aScope, + const nsACString& aScope, ServiceWorkerUpdateFinishCallback* aCallback) { MOZ_ASSERT(NS_IsMainThread()); @@ -146,7 +147,7 @@ UpdateInternal(nsIPrincipal* aPrincipal, return; } - swm->Update(aPrincipal, NS_ConvertUTF16toUTF8(aScope), aCallback); + swm->Update(aPrincipal, aScope, aCallback); } class MainThreadUpdateCallback final : public ServiceWorkerUpdateFinishCallback @@ -281,12 +282,52 @@ public: class SWRUpdateRunnable final : public Runnable { + class TimerCallback final : public nsITimerCallback + { + RefPtr mPrivate; + RefPtr mRunnable; + + public: + TimerCallback(ServiceWorkerPrivate* aPrivate, + Runnable* aRunnable) + : mPrivate(aPrivate) + , mRunnable(aRunnable) + { + MOZ_ASSERT(mPrivate); + MOZ_ASSERT(aRunnable); + } + + nsresult + Notify(nsITimer* aTimer) override + { + mRunnable->Run(); + mPrivate->RemoveISupports(aTimer); + + return NS_OK; + } + + NS_DECL_THREADSAFE_ISUPPORTS + + private: + ~TimerCallback() + { } + }; + public: - SWRUpdateRunnable(PromiseWorkerProxy* aPromiseProxy, const nsAString& aScope) + explicit SWRUpdateRunnable(PromiseWorkerProxy* aPromiseProxy) : Runnable("dom::SWRUpdateRunnable") , 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 Run() override @@ -307,20 +348,65 @@ public: } MOZ_ASSERT(principal); + RefPtr 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 registration = + swm->GetRegistration(principal, mDescriptor.Scope()); + if (NS_WARN_IF(!registration)) { + return NS_OK; + } + + RefPtr 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 cb = new TimerCallback(worker->WorkerPrivate(), this); + Result, nsresult> result = + NS_NewTimerWithCallback(cb, delay, nsITimer::TYPE_ONE_SHOT, + SystemGroup::EventTargetFor(TaskCategory::Other)); + + nsCOMPtr 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 cb = new WorkerThreadUpdateCallback(mPromiseProxy); - UpdateInternal(principal, mScope, cb); + UpdateInternal(principal, mDescriptor.Scope(), cb); return NS_OK; } private: ~SWRUpdateRunnable() - {} + { + MOZ_ASSERT(NS_IsMainThread()); + } RefPtr mPromiseProxy; - const nsString mScope; + const ServiceWorkerDescriptor mDescriptor; + bool mDelayed; }; +NS_IMPL_ISUPPORTS(SWRUpdateRunnable::TimerCallback, nsITimerCallback) + class UnregisterCallback final : public nsIServiceWorkerUnregisterCallback { PromiseWindowProxy mPromise; @@ -533,7 +619,7 @@ ServiceWorkerRegistrationMainThread::Update(ErrorResult& aRv) RefPtr cb = new MainThreadUpdateCallback(mOuter->GetOwner(), promise); - UpdateInternal(doc->NodePrincipal(), mScope, cb); + UpdateInternal(doc->NodePrincipal(), NS_ConvertUTF16toUTF8(mScope), cb); return promise.forget(); } @@ -848,7 +934,7 @@ ServiceWorkerRegistrationWorkerThread::Update(ErrorResult& aRv) return nullptr; } - RefPtr r = new SWRUpdateRunnable(proxy, mScope); + RefPtr r = new SWRUpdateRunnable(proxy); MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(r.forget())); return promise.forget(); diff --git a/dom/serviceworkers/ServiceWorkerRegistrationInfo.cpp b/dom/serviceworkers/ServiceWorkerRegistrationInfo.cpp index 4be02dc67467..baad25d5102c 100644 --- a/dom/serviceworkers/ServiceWorkerRegistrationInfo.cpp +++ b/dom/serviceworkers/ServiceWorkerRegistrationInfo.cpp @@ -87,6 +87,7 @@ ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo( : mPrincipal(aPrincipal) , mDescriptor(aPrincipal, aScope, aUpdateViaCache) , mControlledClientsCounter(0) + , mDelayMultiplier(0) , mUpdateState(NoUpdate) , mCreationTime(PR_Now()) , mCreationTimeStamp(TimeStamp::Now()) @@ -709,5 +710,25 @@ ServiceWorkerRegistrationInfo::Descriptor() const 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 mozilla diff --git a/dom/serviceworkers/ServiceWorkerRegistrationInfo.h b/dom/serviceworkers/ServiceWorkerRegistrationInfo.h index dd7c4fda278b..0e0736953d2d 100644 --- a/dom/serviceworkers/ServiceWorkerRegistrationInfo.h +++ b/dom/serviceworkers/ServiceWorkerRegistrationInfo.h @@ -23,6 +23,7 @@ class ServiceWorkerRegistrationInfo final nsTArray> mListeners; uint32_t mControlledClientsCounter; + uint32_t mDelayMultiplier; enum { @@ -94,6 +95,7 @@ public: StartControllingClient() { ++mControlledClientsCounter; + mDelayMultiplier = 0; } void @@ -211,6 +213,9 @@ public: const ServiceWorkerRegistrationDescriptor& Descriptor() const; + uint32_t + GetUpdateDelay(); + private: // Roughly equivalent to [[Update Registration State algorithm]]. Make sure // this is called *before* updating SW instances' state, otherwise they diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 924f8823ab06..533029f80061 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -174,6 +174,10 @@ pref("dom.serviceWorkers.idle_timeout", 30000); // The amount of time (milliseconds) service workers can be kept running using waitUntil promises. 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 pref("dom.serviceWorkers.testUpdateOverOneDay", false);