From 2d3a0be044c6618e533ebadde5fb8e9155f0df0f Mon Sep 17 00:00:00 2001 From: Jose Antonio Olivera Ortega Date: Tue, 2 Jun 2015 07:12:00 -0400 Subject: [PATCH] Bug 1131352 - Part 2: Add ServiceWorkerGlobalScope skipWaiting(). r=nsm, r=baku --- dom/webidl/ServiceWorkerGlobalScope.webidl | 3 + dom/workers/ServiceWorkerManager.cpp | 70 ++++++++--- dom/workers/ServiceWorkerManager.h | 30 ++++- dom/workers/WorkerScope.cpp | 119 ++++++++++++++++++ dom/workers/WorkerScope.h | 3 + dom/workers/test/serviceworkers/mochitest.ini | 3 + .../skip_waiting_installed_worker.js | 6 + .../skip_waiting_scope/index.html | 37 ++++++ .../serviceworkers/test_skip_waiting.html | 95 ++++++++++++++ 9 files changed, 344 insertions(+), 22 deletions(-) create mode 100644 dom/workers/test/serviceworkers/skip_waiting_installed_worker.js create mode 100644 dom/workers/test/serviceworkers/skip_waiting_scope/index.html create mode 100644 dom/workers/test/serviceworkers/test_skip_waiting.html diff --git a/dom/webidl/ServiceWorkerGlobalScope.webidl b/dom/webidl/ServiceWorkerGlobalScope.webidl index 954105103254..1068c703d91d 100644 --- a/dom/webidl/ServiceWorkerGlobalScope.webidl +++ b/dom/webidl/ServiceWorkerGlobalScope.webidl @@ -16,6 +16,9 @@ interface ServiceWorkerGlobalScope : WorkerGlobalScope { readonly attribute Clients clients; readonly attribute ServiceWorkerRegistration registration; + [Throws] + Promise skipWaiting(); + attribute EventHandler oninstall; attribute EventHandler onactivate; attribute EventHandler onfetch; diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index ad24c365d6c9..ce42bea4c569 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -1135,8 +1135,11 @@ private: swm->InvalidateServiceWorkerRegistrationWorker(mRegistration, WhichServiceWorker::INSTALLING_WORKER | WhichServiceWorker::WAITING_WORKER); - // FIXME(nsm): Bug 982711 Deal with activateImmediately. - NS_WARN_IF_FALSE(!aActivateImmediately, "Immediate activation using replace() is not supported yet"); + // "If registration's waiting worker's skip waiting flag is set" + if (mRegistration->mWaitingWorker->SkipWaitingFlag()) { + mRegistration->PurgeActiveWorker(); + } + Done(NS_OK); // Activate() is invoked out of band of atomic. mRegistration->TryToActivate(); @@ -1466,7 +1469,7 @@ LifecycleEventWorkerRunnable::DispatchLifecycleEvent(JSContext* aCx, WorkerPriva void ServiceWorkerRegistrationInfo::TryToActivate() { - if (!IsControllingDocuments()) { + if (!IsControllingDocuments() || mWaitingWorker->SkipWaitingFlag()) { Activate(); } } @@ -1477,28 +1480,36 @@ ContinueActivateTask::ContinueAfterWorkerEvent(bool aSuccess, bool aActivateImme mRegistration->FinishActivate(aSuccess); } +void +ServiceWorkerRegistrationInfo::PurgeActiveWorker() +{ + nsRefPtr exitingWorker = mActiveWorker.forget(); + if (!exitingWorker) + return; + + // FIXME(jaoo): Bug 1170543 - Wait for exitingWorker to finish and terminate it. + exitingWorker->UpdateState(ServiceWorkerState::Redundant); + nsresult rv = serviceWorkerScriptCache::PurgeCache(mPrincipal, + exitingWorker->CacheName()); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to purge the activating cache."); + } + nsRefPtr swm = ServiceWorkerManager::GetInstance(); + swm->InvalidateServiceWorkerRegistrationWorker(this, WhichServiceWorker::ACTIVE_WORKER); +} + void ServiceWorkerRegistrationInfo::Activate() { nsRefPtr activatingWorker = mWaitingWorker; - nsRefPtr exitingWorker = mActiveWorker; - - nsRefPtr swm = ServiceWorkerManager::GetInstance(); - swm->InvalidateServiceWorkerRegistrationWorker(this, WhichServiceWorker::WAITING_WORKER | WhichServiceWorker::ACTIVE_WORKER); if (!activatingWorker) { return; } - if (exitingWorker) { - // FIXME(nsm): Wait for worker. - // Terminate worker - exitingWorker->UpdateState(ServiceWorkerState::Redundant); - nsresult rv = serviceWorkerScriptCache::PurgeCache(mPrincipal, - exitingWorker->CacheName()); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to purge the activating cache."); - } - } + PurgeActiveWorker(); + + nsRefPtr swm = ServiceWorkerManager::GetInstance(); + swm->InvalidateServiceWorkerRegistrationWorker(this, WhichServiceWorker::WAITING_WORKER); mActiveWorker = activatingWorker.forget(); mWaitingWorker = nullptr; @@ -3678,6 +3689,31 @@ ServiceWorkerManager::ClaimClients(nsIPrincipal* aPrincipal, return NS_OK; } +nsresult +ServiceWorkerManager::SetSkipWaitingFlag(const nsCString& aScope, + uint64_t aServiceWorkerID) +{ + nsRefPtr registration = GetRegistration(aScope); + if (!registration) { + return NS_ERROR_FAILURE; + } + + if (registration->mInstallingWorker && + (registration->mInstallingWorker->ID() == aServiceWorkerID)) { + registration->mInstallingWorker->SetSkipWaitingFlag(); + } else if (registration->mWaitingWorker && + (registration->mWaitingWorker->ID() == aServiceWorkerID)) { + registration->mWaitingWorker->SetSkipWaitingFlag(); + if (registration->mWaitingWorker->State() == ServiceWorkerState::Installed) { + registration->TryToActivate(); + } + } else { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + void ServiceWorkerManager::FireControllerChange(ServiceWorkerRegistrationInfo* aRegistration) { diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h index 6e57b9e958e5..a7a0ab45daa8 100644 --- a/dom/workers/ServiceWorkerManager.h +++ b/dom/workers/ServiceWorkerManager.h @@ -117,6 +117,9 @@ public: void Clear(); + void + PurgeActiveWorker(); + void TryToActivate(); @@ -151,6 +154,7 @@ private: // There is a high chance of there being at least one ServiceWorker // associated with this all the time. nsAutoTArray mInstances; + bool mSkipWaitingFlag; ~ServiceWorkerInfo() { } @@ -181,14 +185,27 @@ public: mScriptSpec = aSpec; } - explicit ServiceWorkerInfo(ServiceWorkerRegistrationInfo* aReg, - const nsACString& aScriptSpec, - const nsAString& aCacheName) + bool SkipWaitingFlag() const + { + AssertIsOnMainThread(); + return mSkipWaitingFlag; + } + + void SetSkipWaitingFlag() + { + AssertIsOnMainThread(); + mSkipWaitingFlag = true; + } + + ServiceWorkerInfo(ServiceWorkerRegistrationInfo* aReg, + const nsACString& aScriptSpec, + const nsAString& aCacheName) : mRegistration(aReg) , mScriptSpec(aScriptSpec) , mCacheName(aCacheName) , mState(ServiceWorkerState::EndGuard_) , mServiceWorkerID(GetNextID()) + , mSkipWaitingFlag(false) { MOZ_ASSERT(mRegistration); MOZ_ASSERT(!aCacheName.IsEmpty()); @@ -338,11 +355,14 @@ public: nsresult ClaimClients(nsIPrincipal* aPrincipal, const nsCString& aScope, uint64_t aId); + nsresult + SetSkipWaitingFlag(const nsCString& aScope, uint64_t aServiceWorkerID); + static already_AddRefed GetInstance(); - void LoadRegistrations( - const nsTArray& aRegistrations); + void + LoadRegistrations(const nsTArray& aRegistrations); // Used by remove() and removeAll() when clearing history. // MUST ONLY BE CALLED FROM UnregisterIfMatchesHost! diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index 4e50652995c3..4cd8dd44b03e 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -487,6 +487,125 @@ ServiceWorkerGlobalScope::Registration() return mRegistration; } +namespace { + +class SkipWaitingResultRunnable final : public WorkerRunnable +{ + nsRefPtr mPromiseProxy; + +public: + SkipWaitingResultRunnable(WorkerPrivate* aWorkerPrivate, + PromiseWorkerProxy* aPromiseProxy) + : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount) + , mPromiseProxy(aPromiseProxy) + { + AssertIsOnMainThread(); + } + + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + Promise* promise = mPromiseProxy->GetWorkerPromise(); + MOZ_ASSERT(promise); + + promise->MaybeResolve(JS::UndefinedHandleValue); + + // Release the reference on the worker thread. + mPromiseProxy->CleanUp(aCx); + + return true; + } +}; + +class WorkerScopeSkipWaitingRunnable final : public nsRunnable +{ + nsRefPtr mPromiseProxy; + nsCString mScope; + +public: + WorkerScopeSkipWaitingRunnable(PromiseWorkerProxy* aPromiseProxy, + const nsCString& aScope) + : mPromiseProxy(aPromiseProxy) + , mScope(aScope) + { + MOZ_ASSERT(aPromiseProxy); + } + + NS_IMETHODIMP + Run() override + { + AssertIsOnMainThread(); + nsRefPtr swm = ServiceWorkerManager::GetInstance(); + MOZ_ASSERT(swm); + + MutexAutoLock lock(mPromiseProxy->GetCleanUpLock()); + if (mPromiseProxy->IsClean()) { + return NS_OK; + } + WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + swm->SetSkipWaitingFlag(mScope, workerPrivate->ServiceWorkerID()); + + nsRefPtr runnable = + new SkipWaitingResultRunnable(workerPrivate, mPromiseProxy); + + AutoJSAPI jsapi; + jsapi.Init(); + JSContext* cx = jsapi.cx(); + if (runnable->Dispatch(cx)) { + return NS_OK; + } + + // Dispatch to worker thread failed because the worker is shutting down. + // Use a control runnable to release the runnable on the worker thread. + nsRefPtr releaseRunnable = + new PromiseWorkerProxyControlRunnable(workerPrivate, mPromiseProxy); + + if (!releaseRunnable->Dispatch(cx)) { + NS_RUNTIMEABORT("Failed to dispatch Claim control runnable."); + } + + return NS_OK; + } +}; + +} + +already_AddRefed +ServiceWorkerGlobalScope::SkipWaiting(ErrorResult& aRv) +{ + mWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(mWorkerPrivate->IsServiceWorker()); + + nsRefPtr promise = Promise::Create(this, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + nsRefPtr promiseProxy = + PromiseWorkerProxy::Create(mWorkerPrivate, promise); + if (!promiseProxy->GetWorkerPromise()) { + // Don't dispatch if adding the worker feature failed. + promise->MaybeResolve(JS::UndefinedHandleValue); + return promise.forget(); + } + + nsRefPtr runnable = + new WorkerScopeSkipWaitingRunnable(promiseProxy, + NS_ConvertUTF16toUTF8(mScope)); + + aRv = NS_DispatchToMainThread(runnable); + if (NS_WARN_IF(aRv.Failed())) { + promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + } + + return promise.forget(); +} + WorkerDebuggerGlobalScope::WorkerDebuggerGlobalScope( WorkerPrivate* aWorkerPrivate) : mWorkerPrivate(aWorkerPrivate) diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h index aaa12dd51a9b..8e1a1e422274 100644 --- a/dom/workers/WorkerScope.h +++ b/dom/workers/WorkerScope.h @@ -227,6 +227,9 @@ public: ServiceWorkerRegistrationWorkerThread* Registration(); + already_AddRefed + SkipWaiting(ErrorResult& aRv); + IMPL_EVENT_HANDLER(activate) IMPL_EVENT_HANDLER(beforeevicted) IMPL_EVENT_HANDLER(evicted) diff --git a/dom/workers/test/serviceworkers/mochitest.ini b/dom/workers/test/serviceworkers/mochitest.ini index e4ff9722812b..74aef00347f0 100644 --- a/dom/workers/test/serviceworkers/mochitest.ini +++ b/dom/workers/test/serviceworkers/mochitest.ini @@ -107,6 +107,8 @@ support-files = sw_clients/refresher_cached_compressed.html sw_clients/refresher_cached_compressed.html^headers^ strict_mode_error.js + skip_waiting_installed_worker.js + skip_waiting_scope/index.html [test_unregister.html] [test_installation_simple.html] @@ -147,4 +149,5 @@ support-files = [test_app_protocol.html] [test_claim_fetch.html] [test_force_refresh.html] +[test_skip_waiting.html] [test_strict_mode_error.html] diff --git a/dom/workers/test/serviceworkers/skip_waiting_installed_worker.js b/dom/workers/test/serviceworkers/skip_waiting_installed_worker.js new file mode 100644 index 000000000000..68573f1003a7 --- /dev/null +++ b/dom/workers/test/serviceworkers/skip_waiting_installed_worker.js @@ -0,0 +1,6 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +self.addEventListener('install', evt => { + evt.waitUntil(self.skipWaiting()); +}); diff --git a/dom/workers/test/serviceworkers/skip_waiting_scope/index.html b/dom/workers/test/serviceworkers/skip_waiting_scope/index.html new file mode 100644 index 000000000000..b8a64d512eaa --- /dev/null +++ b/dom/workers/test/serviceworkers/skip_waiting_scope/index.html @@ -0,0 +1,37 @@ + + + + + Bug 1131352 - Add ServiceWorkerGlobalScope skipWaiting() + + + + +

+ +

+
+
+
+
diff --git a/dom/workers/test/serviceworkers/test_skip_waiting.html b/dom/workers/test/serviceworkers/test_skip_waiting.html
new file mode 100644
index 000000000000..7707d6035054
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_skip_waiting.html
@@ -0,0 +1,95 @@
+
+
+
+
+  Bug 1131352 - Add ServiceWorkerGlobalScope skipWaiting()
+  
+  
+
+
+

+ +

+
+
+
+