Bug 1227932 - Fix Service Workers SoftUpdate and registration.update code paths. r=ehsan

This patch splits the code paths for registration.update and soft update
since they have different behaviour. Next, it changes ServiceWorkerRegisterJob
to use just one callback and just prevents soft update from queuing a new
task if another one is pending.
This commit is contained in:
Catalin Badea 2015-11-26 19:03:10 +02:00
parent c8536302c2
commit cdf98cfc81
4 changed files with 111 additions and 145 deletions

View File

@ -375,6 +375,7 @@ ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(const nsACString& a
, mScope(aScope)
, mPrincipal(aPrincipal)
, mLastUpdateCheckTime(0)
, mUpdating(false)
, mPendingUninstall(false)
{}
@ -863,7 +864,7 @@ class ServiceWorkerRegisterJob final : public ServiceWorkerJob,
nsCString mScope;
nsCString mScriptSpec;
RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
nsTArray<RefPtr<ServiceWorkerUpdateFinishCallback>> mCallbacks;
RefPtr<ServiceWorkerUpdateFinishCallback> mCallback;
nsCOMPtr<nsIPrincipal> mPrincipal;
RefPtr<ServiceWorkerInfo> mUpdateAndInstallInfo;
nsCOMPtr<nsILoadGroup> mLoadGroup;
@ -892,6 +893,7 @@ public:
: ServiceWorkerJob(aQueue)
, mScope(aScope)
, mScriptSpec(aScriptSpec)
, mCallback(aCallback)
, mPrincipal(aPrincipal)
, mLoadGroup(aLoadGroup)
, mJobType(REGISTER_JOB)
@ -900,8 +902,6 @@ public:
AssertIsOnMainThread();
MOZ_ASSERT(mLoadGroup);
MOZ_ASSERT(aCallback);
mCallbacks.AppendElement(aCallback);
}
// [[Update]]
@ -910,13 +910,11 @@ public:
ServiceWorkerUpdateFinishCallback* aCallback)
: ServiceWorkerJob(aQueue)
, mRegistration(aRegistration)
, mCallback(aCallback)
, mJobType(UPDATE_JOB)
, mCanceled(false)
{
AssertIsOnMainThread();
MOZ_ASSERT(aCallback);
mCallbacks.AppendElement(aCallback);
}
bool
@ -925,16 +923,6 @@ public:
return true;
}
void
AppendCallback(ServiceWorkerUpdateFinishCallback* aCallback)
{
AssertIsOnMainThread();
MOZ_ASSERT(aCallback);
MOZ_ASSERT(!mCallbacks.Contains(aCallback));
mCallbacks.AppendElement(aCallback);
}
void
Cancel()
{
@ -986,10 +974,6 @@ public:
swm->StoreRegistration(mPrincipal, mRegistration);
} else {
MOZ_ASSERT(mJobType == UPDATE_JOB);
MOZ_ASSERT(mRegistration);
MOZ_ASSERT(mRegistration->mUpdateJob == nullptr);
mRegistration->mUpdateJob = this;
}
Update();
@ -1121,8 +1105,6 @@ public:
Fail(ErrorResult& aRv)
{
AssertIsOnMainThread();
MOZ_ASSERT(mCallbacks.Length());
// With cancellation support, we may only be running with one reference
// from another object like a stream loader or something.
RefPtr<ServiceWorkerRegisterJob> kungFuDeathGrip = this;
@ -1155,15 +1137,11 @@ public:
aRv.ThrowTypeError<MSG_SW_INSTALL_ERROR>(scriptSpec, scope);
}
for (uint32_t i = 1; i < mCallbacks.Length(); ++i) {
ErrorResult rv;
aRv.CloneTo(rv);
mCallbacks[i]->UpdateFailed(rv);
rv.SuppressException();
if (mCallback) {
mCallback->UpdateFailed(aRv);
mCallback = nullptr;
}
mCallbacks[0]->UpdateFailed(aRv);
// In case the callback does not consume the exception
aRv.SuppressException();
@ -1179,7 +1157,6 @@ public:
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
swm->MaybeRemoveRegistration(mRegistration);
// Ensures that the job can't do anything useful from this point on.
mRegistration->mUpdateJob = nullptr;
mRegistration = nullptr;
Done(origStatus);
}
@ -1195,11 +1172,14 @@ public:
void
ContinueInstall()
{
AssertIsOnMainThread();
// mRegistration will be null if we have already Fail()ed.
if (!mRegistration) {
return;
}
mRegistration->mUpdating = false;
// Even if we are canceled, ensure integrity of mSetOfScopesBeingUpdated
// first.
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
@ -1275,12 +1255,16 @@ private:
void
Update()
{
AssertIsOnMainThread();
// Since Update() is called synchronously from Start(), we can assert this.
MOZ_ASSERT(!mCanceled);
MOZ_ASSERT(mRegistration);
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(this, &ServiceWorkerRegisterJob::ContinueUpdate);
NS_DispatchToMainThread(r);
mRegistration->mUpdating = true;
}
// Aspects of (actually the whole algorithm) of [[Update]] after
@ -1323,12 +1307,11 @@ private:
Succeed()
{
AssertIsOnMainThread();
MOZ_ASSERT(mCallbacks.Length());
for (uint32_t i = 0; i < mCallbacks.Length(); ++i) {
mCallbacks[i]->UpdateSucceeded(mRegistration);
// We don't have a callback for soft updates.
if (mCallback) {
mCallback->UpdateSucceeded(mRegistration);
mCallback = nullptr;
}
mCallbacks.Clear();
}
void
@ -1386,13 +1369,13 @@ private:
void
Done(nsresult aStatus)
{
ServiceWorkerJob::Done(aStatus);
AssertIsOnMainThread();
if (mJobType == UPDATE_JOB && mRegistration) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mRegistration->mUpdateJob);
mRegistration->mUpdateJob = nullptr;
if (mRegistration) {
mRegistration->mUpdating = false;
}
ServiceWorkerJob::Done(aStatus);
}
};
@ -2664,23 +2647,6 @@ ServiceWorkerRegistrationInfo::NotifyListenersOnChange()
}
}
bool
ServiceWorkerRegistrationInfo::IsUpdating() const
{
MOZ_ASSERT(NS_IsMainThread());
return mUpdateJob != nullptr;
}
void
ServiceWorkerRegistrationInfo::AppendUpdateCallback(ServiceWorkerUpdateFinishCallback* aCallback)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aCallback);
MOZ_ASSERT(mUpdateJob);
mUpdateJob->AppendCallback(aCallback);
}
void
ServiceWorkerManager::LoadRegistration(
const ServiceWorkerRegistrationData& aRegistration)
@ -3527,55 +3493,15 @@ ServiceWorkerManager::InvalidateServiceWorkerRegistrationWorker(ServiceWorkerReg
}
void
ServiceWorkerManager::SoftUpdate(nsIPrincipal* aPrincipal,
const nsACString& aScope,
ServiceWorkerUpdateFinishCallback* aCallback)
{
MOZ_ASSERT(aPrincipal);
nsAutoCString scopeKey;
nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
SoftUpdate(scopeKey, aScope, aCallback);
}
void
ServiceWorkerManager::SoftUpdate(const PrincipalOriginAttributes& aOriginAttributes,
const nsACString& aScope,
ServiceWorkerUpdateFinishCallback* aCallback)
ServiceWorkerManager::SoftUpdate(const OriginAttributes& aOriginAttributes,
const nsACString& aScope)
{
AssertIsOnMainThread();
nsAutoCString scopeKey;
aOriginAttributes.CreateSuffix(scopeKey);
SoftUpdate(scopeKey, aScope, aCallback);
}
namespace {
// Empty callback. Only use when you really want to ignore errors.
class EmptyUpdateFinishCallback final : public ServiceWorkerUpdateFinishCallback
{
public:
void
UpdateSucceeded(ServiceWorkerRegistrationInfo* aInfo) override
{}
void
UpdateFailed(ErrorResult& aStatus) override
{}
};
} // anonymous namespace
void
ServiceWorkerManager::SoftUpdate(const nsACString& aScopeKey,
const nsACString& aScope,
ServiceWorkerUpdateFinishCallback* aCallback)
{
RefPtr<ServiceWorkerRegistrationInfo> registration =
GetRegistration(aScopeKey, aScope);
GetRegistration(scopeKey, aScope);
if (NS_WARN_IF(!registration)) {
return;
}
@ -3601,28 +3527,71 @@ ServiceWorkerManager::SoftUpdate(const nsACString& aScopeKey,
// "Set registration's registering script url to newestWorker's script url."
registration->mScriptSpec = newest->ScriptSpec();
ServiceWorkerJobQueue* queue =
GetOrCreateJobQueue(aScopeKey, aScope);
MOZ_ASSERT(queue);
// "If the registration queue for registration is empty, invoke Update algorithm,
// or its equivalent, with client, registration as its argument."
// TODO(catalinb): We don't implement the force bypass cache flag.
// See: https://github.com/slightlyoff/ServiceWorker/issues/759
if (!registration->mUpdating) {
ServiceWorkerJobQueue* queue = GetOrCreateJobQueue(scopeKey, aScope);
MOZ_ASSERT(queue);
RefPtr<ServiceWorkerUpdateFinishCallback> cb(aCallback);
if (!cb) {
cb = new EmptyUpdateFinishCallback();
RefPtr<ServiceWorkerRegisterJob> job =
new ServiceWorkerRegisterJob(queue, registration, nullptr);
queue->Append(job);
}
}
void
ServiceWorkerManager::Update(nsIPrincipal* aPrincipal,
const nsACString& aScope,
ServiceWorkerUpdateFinishCallback* aCallback)
{
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(aCallback);
nsAutoCString scopeKey;
nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
RefPtr<ServiceWorkerRegistrationInfo> registration =
GetRegistration(scopeKey, aScope);
if (NS_WARN_IF(!registration)) {
return;
}
// "If registration's uninstalling flag is set, abort these steps."
if (registration->mPendingUninstall) {
return;
}
// "Let newestWorker be the result of running Get Newest Worker algorithm
// passing registration as its argument.
// If newestWorker is null, return a promise rejected with "InvalidStateError"
RefPtr<ServiceWorkerInfo> newest = registration->Newest();
if (!newest) {
ErrorResult error(NS_ERROR_DOM_INVALID_STATE_ERR);
aCallback->UpdateFailed(error);
// In case the callback does not consume the exception
error.SuppressException();
return;
}
// "Set registration's registering script url to newestWorker's script url."
registration->mScriptSpec = newest->ScriptSpec();
ServiceWorkerJobQueue* queue =
GetOrCreateJobQueue(scopeKey, aScope);
MOZ_ASSERT(queue);
// "Invoke Update algorithm, or its equivalent, with client, registration as
// its argument."
if (registration->IsUpdating()) {
// This is used to reduce burst of update events. If there is an update
// job in queue when we try to create a new one, drop current one and
// merge the callback function to existing update job.
// See. https://github.com/slightlyoff/ServiceWorker/issues/759
registration->AppendUpdateCallback(cb);
} else {
RefPtr<ServiceWorkerRegisterJob> job =
new ServiceWorkerRegisterJob(queue, registration, cb);
queue->Append(job);
}
RefPtr<ServiceWorkerRegisterJob> job =
new ServiceWorkerRegisterJob(queue, registration, aCallback);
queue->Append(job);
}
namespace {

View File

@ -47,11 +47,9 @@ class ServiceWorker;
class ServiceWorkerClientInfo;
class ServiceWorkerInfo;
class ServiceWorkerJob;
class ServiceWorkerRegisterJob;
class ServiceWorkerJobQueue;
class ServiceWorkerManagerChild;
class ServiceWorkerPrivate;
class ServiceWorkerUpdateFinishCallback;
class ServiceWorkerRegistrationInfo final
: public nsIServiceWorkerRegistrationInfo
@ -79,7 +77,11 @@ public:
uint64_t mLastUpdateCheckTime;
RefPtr<ServiceWorkerRegisterJob> mUpdateJob;
// According to the spec, Soft Update shouldn't queue an update job
// if the registration queue is not empty. Because our job queue
// works slightly different, we use a flag to determine if the registration
// is already updating.
bool mUpdating;
// When unregister() is called on a registration, it is not immediately
// removed since documents may be controlled. It is marked as
@ -149,12 +151,6 @@ public:
void
NotifyListenersOnChange();
bool
IsUpdating() const;
void
AppendUpdateCallback(ServiceWorkerUpdateFinishCallback* aCallback);
};
class ServiceWorkerUpdateFinishCallback
@ -373,14 +369,13 @@ public:
ErrorResult& aRv);
void
SoftUpdate(nsIPrincipal* aPrincipal,
const nsACString& aScope,
ServiceWorkerUpdateFinishCallback* aCallback = nullptr);
Update(nsIPrincipal* aPrincipal,
const nsACString& aScope,
ServiceWorkerUpdateFinishCallback* aCallback);
void
SoftUpdate(const PrincipalOriginAttributes& aOriginAttributes,
const nsACString& aScope,
ServiceWorkerUpdateFinishCallback* aCallback = nullptr);
SoftUpdate(const OriginAttributes& aOriginAttributes,
const nsACString& aScope);
void
PropagateSoftUpdate(const PrincipalOriginAttributes& aOriginAttributes,
@ -489,11 +484,6 @@ private:
void
MaybeRemoveRegistrationInfo(const nsACString& aScopeKey);
void
SoftUpdate(const nsACString& aScopeKey,
const nsACString& aScope,
ServiceWorkerUpdateFinishCallback* aCallback = nullptr);
already_AddRefed<ServiceWorkerRegistrationInfo>
GetRegistration(const nsACString& aScopeKey,
const nsACString& aScope) const;

View File

@ -42,7 +42,7 @@ ServiceWorkerManagerChild::RecvNotifySoftUpdate(
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
MOZ_ASSERT(swm);
swm->SoftUpdate(aOriginAttributes, NS_ConvertUTF16toUTF8(aScope), nullptr);
swm->SoftUpdate(aOriginAttributes, NS_ConvertUTF16toUTF8(aScope));
return true;
}

View File

@ -266,8 +266,7 @@ UpdateInternal(nsIPrincipal* aPrincipal,
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
MOZ_ASSERT(swm);
// The spec defines ServiceWorkerRegistration.update() exactly as Soft Update.
swm->SoftUpdate(aPrincipal, NS_ConvertUTF16toUTF8(aScope), aCallback);
swm->Update(aPrincipal, NS_ConvertUTF16toUTF8(aScope), aCallback);
}
class MainThreadUpdateCallback final : public ServiceWorkerUpdateFinishCallback
@ -393,14 +392,22 @@ public:
AssertIsOnMainThread();
ErrorResult result;
MutexAutoLock lock(mPromiseProxy->Lock());
if (mPromiseProxy->CleanedUp()) {
return NS_OK;
nsCOMPtr<nsIPrincipal> principal;
// UpdateInternal may try to reject the promise synchronously leading
// to a deadlock.
{
MutexAutoLock lock(mPromiseProxy->Lock());
if (mPromiseProxy->CleanedUp()) {
return NS_OK;
}
principal = mPromiseProxy->GetWorkerPrivate()->GetPrincipal();
}
MOZ_ASSERT(principal);
RefPtr<WorkerThreadUpdateCallback> cb =
new WorkerThreadUpdateCallback(mPromiseProxy);
UpdateInternal(mPromiseProxy->GetWorkerPrivate()->GetPrincipal(), mScope, cb);
UpdateInternal(principal, mScope, cb);
return NS_OK;
}