Bug 1131352 - Part 2: Add ServiceWorkerGlobalScope skipWaiting(). r=nsm, r=baku

This commit is contained in:
Jose Antonio Olivera Ortega 2015-06-02 07:12:00 -04:00
Родитель 1fde4d00db
Коммит 2d3a0be044
9 изменённых файлов: 344 добавлений и 22 удалений

Просмотреть файл

@ -16,6 +16,9 @@ interface ServiceWorkerGlobalScope : WorkerGlobalScope {
readonly attribute Clients clients;
readonly attribute ServiceWorkerRegistration registration;
[Throws]
Promise<boolean> skipWaiting();
attribute EventHandler oninstall;
attribute EventHandler onactivate;
attribute EventHandler onfetch;

Просмотреть файл

@ -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<ServiceWorkerInfo> 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<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
swm->InvalidateServiceWorkerRegistrationWorker(this, WhichServiceWorker::ACTIVE_WORKER);
}
void
ServiceWorkerRegistrationInfo::Activate()
{
nsRefPtr<ServiceWorkerInfo> activatingWorker = mWaitingWorker;
nsRefPtr<ServiceWorkerInfo> exitingWorker = mActiveWorker;
nsRefPtr<ServiceWorkerManager> 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<ServiceWorkerManager> 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<ServiceWorkerRegistrationInfo> 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)
{

Просмотреть файл

@ -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<ServiceWorker*, 1> 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<ServiceWorkerManager>
GetInstance();
void LoadRegistrations(
const nsTArray<ServiceWorkerRegistrationData>& aRegistrations);
void
LoadRegistrations(const nsTArray<ServiceWorkerRegistrationData>& aRegistrations);
// Used by remove() and removeAll() when clearing history.
// MUST ONLY BE CALLED FROM UnregisterIfMatchesHost!

Просмотреть файл

@ -487,6 +487,125 @@ ServiceWorkerGlobalScope::Registration()
return mRegistration;
}
namespace {
class SkipWaitingResultRunnable final : public WorkerRunnable
{
nsRefPtr<PromiseWorkerProxy> 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<PromiseWorkerProxy> mPromiseProxy;
nsCString mScope;
public:
WorkerScopeSkipWaitingRunnable(PromiseWorkerProxy* aPromiseProxy,
const nsCString& aScope)
: mPromiseProxy(aPromiseProxy)
, mScope(aScope)
{
MOZ_ASSERT(aPromiseProxy);
}
NS_IMETHODIMP
Run() override
{
AssertIsOnMainThread();
nsRefPtr<ServiceWorkerManager> 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<SkipWaitingResultRunnable> 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<PromiseWorkerProxyControlRunnable> releaseRunnable =
new PromiseWorkerProxyControlRunnable(workerPrivate, mPromiseProxy);
if (!releaseRunnable->Dispatch(cx)) {
NS_RUNTIMEABORT("Failed to dispatch Claim control runnable.");
}
return NS_OK;
}
};
}
already_AddRefed<Promise>
ServiceWorkerGlobalScope::SkipWaiting(ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(mWorkerPrivate->IsServiceWorker());
nsRefPtr<Promise> promise = Promise::Create(this, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
nsRefPtr<PromiseWorkerProxy> 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<WorkerScopeSkipWaitingRunnable> 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)

Просмотреть файл

@ -227,6 +227,9 @@ public:
ServiceWorkerRegistrationWorkerThread*
Registration();
already_AddRefed<Promise>
SkipWaiting(ErrorResult& aRv);
IMPL_EVENT_HANDLER(activate)
IMPL_EVENT_HANDLER(beforeevicted)
IMPL_EVENT_HANDLER(evicted)

Просмотреть файл

@ -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]

Просмотреть файл

@ -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());
});

Просмотреть файл

@ -0,0 +1,37 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>Bug 1131352 - Add ServiceWorkerGlobalScope skipWaiting()</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script class="testbody" type="text/javascript">
if (!parent) {
info("skip_waiting_scope/index.html shouldn't be launched directly!");
}
navigator.serviceWorker.ready.then(function() {
parent.postMessage("READY", "*");
});
navigator.serviceWorker.oncontrollerchange = function() {
parent.postMessage({
event: "controllerchange",
controllerScriptURL: navigator.serviceWorker.controller &&
navigator.serviceWorker.controller.scriptURL
}, "*");
}
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,95 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>Bug 1131352 - Add ServiceWorkerGlobalScope skipWaiting()</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script class="testbody" type="text/javascript">
var registration, iframe, content;
function start() {
return navigator.serviceWorker.register("worker.js",
{scope: "./skip_waiting_scope/"});
}
function waitForActivated(swr) {
registration = swr;
var promise = new Promise(function(resolve, reject) {
window.onmessage = function(e) {
if (e.data === "READY") {
ok(true, "Active worker is activated now");
resolve();
} else {
ok(false, "Wrong value. Somenting went wrong");
resolve();
}
}
});
iframe = document.createElement("iframe");
iframe.setAttribute("src", "skip_waiting_scope/index.html");
content = document.getElementById("content");
content.appendChild(iframe);
return promise;
}
function checkWhetherItSkippedWaiting() {
var promise = new Promise(function(resolve, reject) {
window.onmessage = function (evt) {
if (evt.data.event === "controllerchange") {
ok(evt.data.controllerScriptURL.match("skip_waiting_installed_worker"),
"The controller changed after skiping the waiting step");
resolve();
} else {
ok(false, "Wrong value. Somenting went wrong");
resolve();
}
};
});
navigator.serviceWorker.register("skip_waiting_installed_worker.js",
{scope: "./skip_waiting_scope/"})
.then(swr => {
registration = swr;
});
return promise;
}
function clean() {
content.removeChild(iframe);
return registration.unregister();
}
function runTest() {
start()
.then(waitForActivated)
.then(checkWhetherItSkippedWaiting)
.then(clean)
.catch(function(e) {
ok(false, "Some test failed with error " + e);
}).then(SimpleTest.finish);
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [
["dom.serviceWorkers.exemptFromPerDomainMax", true],
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true]
]}, runTest);
</script>
</pre>
</body>
</html>