blob: b803edf1865954db918665b644c0d8f173607f8d [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/service_worker/service_worker_registration.h"
#include <vector>
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_info.h"
#include "content/browser/service_worker/service_worker_metrics.h"
#include "content/browser/service_worker/service_worker_register_job.h"
#include "content/common/service_worker/service_worker_messages.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/browser/browser_thread.h"
namespace content {
namespace {
ServiceWorkerVersionInfo GetVersionInfo(ServiceWorkerVersion* version) {
if (!version)
return ServiceWorkerVersionInfo();
return version->GetInfo();
}
} // namespace
ServiceWorkerRegistration::ServiceWorkerRegistration(
const GURL& pattern,
int64_t registration_id,
base::WeakPtr<ServiceWorkerContextCore> context)
: pattern_(pattern),
registration_id_(registration_id),
is_deleted_(false),
is_uninstalling_(false),
is_uninstalled_(false),
should_activate_when_ready_(false),
resources_total_size_bytes_(0),
context_(context) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK_NE(kInvalidServiceWorkerRegistrationId, registration_id);
DCHECK(context_);
context_->AddLiveRegistration(this);
}
ServiceWorkerRegistration::~ServiceWorkerRegistration() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(!listeners_.might_have_observers());
if (context_)
context_->RemoveLiveRegistration(registration_id_);
if (active_version())
active_version()->RemoveListener(this);
}
ServiceWorkerVersion* ServiceWorkerRegistration::GetNewestVersion() const {
if (installing_version())
return installing_version();
if (waiting_version())
return waiting_version();
return active_version();
}
void ServiceWorkerRegistration::AddListener(Listener* listener) {
listeners_.AddObserver(listener);
}
void ServiceWorkerRegistration::RemoveListener(Listener* listener) {
listeners_.RemoveObserver(listener);
}
void ServiceWorkerRegistration::NotifyRegistrationFailed() {
FOR_EACH_OBSERVER(Listener, listeners_, OnRegistrationFailed(this));
NotifyRegistrationFinished();
}
void ServiceWorkerRegistration::NotifyUpdateFound() {
FOR_EACH_OBSERVER(Listener, listeners_, OnUpdateFound(this));
}
void ServiceWorkerRegistration::NotifyVersionAttributesChanged(
ChangedVersionAttributesMask mask) {
FOR_EACH_OBSERVER(Listener, listeners_,
OnVersionAttributesChanged(this, mask, GetInfo()));
if (mask.active_changed() || mask.waiting_changed())
NotifyRegistrationFinished();
}
ServiceWorkerRegistrationInfo ServiceWorkerRegistration::GetInfo() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
return ServiceWorkerRegistrationInfo(
pattern(), registration_id_,
is_deleted_ ? ServiceWorkerRegistrationInfo::IS_DELETED
: ServiceWorkerRegistrationInfo::IS_NOT_DELETED,
GetVersionInfo(active_version_.get()),
GetVersionInfo(waiting_version_.get()),
GetVersionInfo(installing_version_.get()), resources_total_size_bytes_);
}
void ServiceWorkerRegistration::SetActiveVersion(
const scoped_refptr<ServiceWorkerVersion>& version) {
should_activate_when_ready_ = false;
if (active_version_ == version)
return;
ChangedVersionAttributesMask mask;
if (version)
UnsetVersionInternal(version.get(), &mask);
if (active_version_)
active_version_->RemoveListener(this);
active_version_ = version;
if (active_version_)
active_version_->AddListener(this);
mask.add(ChangedVersionAttributesMask::ACTIVE_VERSION);
NotifyVersionAttributesChanged(mask);
}
void ServiceWorkerRegistration::SetWaitingVersion(
const scoped_refptr<ServiceWorkerVersion>& version) {
should_activate_when_ready_ = false;
if (waiting_version_ == version)
return;
ChangedVersionAttributesMask mask;
if (version)
UnsetVersionInternal(version.get(), &mask);
waiting_version_ = version;
mask.add(ChangedVersionAttributesMask::WAITING_VERSION);
NotifyVersionAttributesChanged(mask);
}
void ServiceWorkerRegistration::SetInstallingVersion(
const scoped_refptr<ServiceWorkerVersion>& version) {
if (installing_version_ == version)
return;
ChangedVersionAttributesMask mask;
if (version)
UnsetVersionInternal(version.get(), &mask);
installing_version_ = version;
mask.add(ChangedVersionAttributesMask::INSTALLING_VERSION);
NotifyVersionAttributesChanged(mask);
}
void ServiceWorkerRegistration::UnsetVersion(ServiceWorkerVersion* version) {
if (!version)
return;
ChangedVersionAttributesMask mask;
UnsetVersionInternal(version, &mask);
if (mask.changed())
NotifyVersionAttributesChanged(mask);
}
void ServiceWorkerRegistration::UnsetVersionInternal(
ServiceWorkerVersion* version,
ChangedVersionAttributesMask* mask) {
DCHECK(version);
if (installing_version_.get() == version) {
installing_version_ = NULL;
mask->add(ChangedVersionAttributesMask::INSTALLING_VERSION);
} else if (waiting_version_.get() == version) {
waiting_version_ = NULL;
mask->add(ChangedVersionAttributesMask::WAITING_VERSION);
} else if (active_version_.get() == version) {
active_version_->RemoveListener(this);
active_version_ = NULL;
mask->add(ChangedVersionAttributesMask::ACTIVE_VERSION);
}
}
void ServiceWorkerRegistration::ActivateWaitingVersionWhenReady() {
DCHECK(waiting_version());
should_activate_when_ready_ = true;
if (!active_version() || !active_version()->HasControllee() ||
waiting_version()->skip_waiting())
ActivateWaitingVersion();
}
void ServiceWorkerRegistration::ClaimClients() {
DCHECK(context_);
DCHECK(active_version());
for (std::unique_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
context_->GetProviderHostIterator();
!it->IsAtEnd(); it->Advance()) {
ServiceWorkerProviderHost* host = it->GetProviderHost();
if (host->IsHostToRunningServiceWorker())
continue;
if (host->controlling_version() == active_version())
continue;
if (host->MatchRegistration() == this)
host->ClaimedByRegistration(this);
}
}
void ServiceWorkerRegistration::ClearWhenReady() {
DCHECK(context_);
if (is_uninstalling_)
return;
is_uninstalling_ = true;
context_->storage()->NotifyUninstallingRegistration(this);
context_->storage()->DeleteRegistration(
id(),
pattern().GetOrigin(),
base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
if (!active_version() || !active_version()->HasControllee())
Clear();
}
void ServiceWorkerRegistration::AbortPendingClear(
const StatusCallback& callback) {
DCHECK(context_);
if (!is_uninstalling()) {
callback.Run(SERVICE_WORKER_OK);
return;
}
is_uninstalling_ = false;
context_->storage()->NotifyDoneUninstallingRegistration(this);
scoped_refptr<ServiceWorkerVersion> most_recent_version =
waiting_version() ? waiting_version() : active_version();
DCHECK(most_recent_version.get());
context_->storage()->NotifyInstallingRegistration(this);
context_->storage()->StoreRegistration(
this,
most_recent_version.get(),
base::Bind(&ServiceWorkerRegistration::OnRestoreFinished,
this,
callback,
most_recent_version));
}
void ServiceWorkerRegistration::OnNoControllees(ServiceWorkerVersion* version) {
if (!context_)
return;
DCHECK_EQ(active_version(), version);
if (is_uninstalling_)
Clear();
else if (should_activate_when_ready_)
ActivateWaitingVersion();
is_uninstalling_ = false;
should_activate_when_ready_ = false;
}
void ServiceWorkerRegistration::ActivateWaitingVersion() {
DCHECK(context_);
DCHECK(waiting_version());
DCHECK(should_activate_when_ready_);
should_activate_when_ready_ = false;
scoped_refptr<ServiceWorkerVersion> activating_version = waiting_version();
scoped_refptr<ServiceWorkerVersion> exiting_version = active_version();
if (activating_version->is_redundant())
return; // Activation is no longer relevant.
// "5. If exitingWorker is not null,
if (exiting_version.get()) {
// TODO(michaeln): should wait for events to be complete
// "1. Wait for exitingWorker to finish handling any in-progress requests."
// "2. Terminate exitingWorker."
exiting_version->StopWorker(
base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
// "3. Run the [[UpdateState]] algorithm passing exitingWorker and
// "redundant" as the arguments."
exiting_version->SetStatus(ServiceWorkerVersion::REDUNDANT);
}
// "6. Set serviceWorkerRegistration.activeWorker to activatingWorker."
// "7. Set serviceWorkerRegistration.waitingWorker to null."
SetActiveVersion(activating_version);
// "8. Run the [[UpdateState]] algorithm passing registration.activeWorker and
// "activating" as arguments."
activating_version->SetStatus(ServiceWorkerVersion::ACTIVATING);
// "9. Fire a simple event named controllerchange..."
if (activating_version->skip_waiting())
FOR_EACH_OBSERVER(Listener, listeners_, OnSkippedWaiting(this));
// "10. Queue a task to fire an event named activate..."
activating_version->RunAfterStartWorker(
ServiceWorkerMetrics::EventType::ACTIVATE,
base::Bind(&ServiceWorkerRegistration::DispatchActivateEvent, this,
activating_version),
base::Bind(&ServiceWorkerRegistration::OnActivateEventFinished, this,
activating_version));
}
void ServiceWorkerRegistration::DeleteVersion(
const scoped_refptr<ServiceWorkerVersion>& version) {
DCHECK_EQ(id(), version->registration_id());
UnsetVersion(version.get());
for (std::unique_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
context_->GetProviderHostIterator();
!it->IsAtEnd(); it->Advance()) {
ServiceWorkerProviderHost* host = it->GetProviderHost();
if (host->controlling_version() == version)
host->NotifyControllerLost();
}
version->Doom();
if (!active_version() && !waiting_version()) {
// Delete the records from the db.
context_->storage()->DeleteRegistration(
id(), pattern().GetOrigin(),
base::Bind(&ServiceWorkerRegistration::OnDeleteFinished, this));
// But not from memory if there is a version in the pipeline.
// TODO(falken): Fix this logic. There could be a running register job for
// this registration that hasn't set installing_version() yet.
if (installing_version()) {
is_deleted_ = false;
} else {
is_uninstalled_ = true;
NotifyRegistrationFailed();
}
}
}
void ServiceWorkerRegistration::NotifyRegistrationFinished() {
std::vector<base::Closure> callbacks;
callbacks.swap(registration_finished_callbacks_);
for (const auto& callback : callbacks)
callback.Run();
}
void ServiceWorkerRegistration::RegisterRegistrationFinishedCallback(
const base::Closure& callback) {
// This should only be called if the registration is in progress.
DCHECK(!active_version() && !waiting_version() && !is_uninstalled() &&
!is_uninstalling());
registration_finished_callbacks_.push_back(callback);
}
void ServiceWorkerRegistration::DispatchActivateEvent(
const scoped_refptr<ServiceWorkerVersion>& activating_version) {
if (activating_version != active_version()) {
OnActivateEventFinished(activating_version, SERVICE_WORKER_ERROR_FAILED);
return;
}
DCHECK_EQ(ServiceWorkerVersion::ACTIVATING, activating_version->status());
DCHECK_EQ(ServiceWorkerVersion::RUNNING, activating_version->running_status())
<< "Worker stopped too soon after it was started.";
int request_id = activating_version->StartRequest(
ServiceWorkerMetrics::EventType::ACTIVATE,
base::Bind(&ServiceWorkerRegistration::OnActivateEventFinished, this,
activating_version));
activating_version
->DispatchSimpleEvent<ServiceWorkerHostMsg_ActivateEventFinished>(
request_id, ServiceWorkerMsg_ActivateEvent(request_id));
}
void ServiceWorkerRegistration::OnActivateEventFinished(
const scoped_refptr<ServiceWorkerVersion>& activating_version,
ServiceWorkerStatusCode status) {
if (!context_ || activating_version != active_version() ||
activating_version->status() != ServiceWorkerVersion::ACTIVATING)
return;
// |status| is just for UMA. Once we've attempted to dispatch the activate
// event to an installed worker, it's committed to becoming active.
ServiceWorkerMetrics::RecordActivateEventStatus(status);
// "Run the Update State algorithm passing registration's active worker and
// 'activated' as the arguments."
activating_version->SetStatus(ServiceWorkerVersion::ACTIVATED);
context_->storage()->UpdateToActiveState(
this, base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
}
void ServiceWorkerRegistration::OnDeleteFinished(
ServiceWorkerStatusCode status) {
// Intentionally empty completion callback, used to prevent
// |this| from being deleted until the storage method completes.
}
void ServiceWorkerRegistration::Clear() {
is_uninstalling_ = false;
is_uninstalled_ = true;
if (context_)
context_->storage()->NotifyDoneUninstallingRegistration(this);
std::vector<scoped_refptr<ServiceWorkerVersion>> versions_to_doom;
ChangedVersionAttributesMask mask;
if (installing_version_.get()) {
versions_to_doom.push_back(installing_version_);
installing_version_ = nullptr;
mask.add(ChangedVersionAttributesMask::INSTALLING_VERSION);
}
if (waiting_version_.get()) {
versions_to_doom.push_back(waiting_version_);
waiting_version_ = nullptr;
mask.add(ChangedVersionAttributesMask::WAITING_VERSION);
}
if (active_version_.get()) {
versions_to_doom.push_back(active_version_);
active_version_->RemoveListener(this);
active_version_ = nullptr;
mask.add(ChangedVersionAttributesMask::ACTIVE_VERSION);
}
if (mask.changed()) {
NotifyVersionAttributesChanged(mask);
// Doom only after notifying attributes changed, because the spec requires
// the attributes to be cleared by the time the statechange event is
// dispatched.
for (const auto& version : versions_to_doom)
version->Doom();
}
FOR_EACH_OBSERVER(
Listener, listeners_, OnRegistrationFinishedUninstalling(this));
}
void ServiceWorkerRegistration::OnRestoreFinished(
const StatusCallback& callback,
scoped_refptr<ServiceWorkerVersion> version,
ServiceWorkerStatusCode status) {
if (!context_) {
callback.Run(SERVICE_WORKER_ERROR_ABORT);
return;
}
context_->storage()->NotifyDoneInstallingRegistration(
this, version.get(), status);
callback.Run(status);
}
} // namespace content