// 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 "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_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 registration_id, base::WeakPtr 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(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)); } void ServiceWorkerRegistration::NotifyUpdateFound() { FOR_EACH_OBSERVER(Listener, listeners_, OnUpdateFound(this)); } 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& 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); FOR_EACH_OBSERVER(Listener, listeners_, OnVersionAttributesChanged(this, mask, GetInfo())); } void ServiceWorkerRegistration::SetWaitingVersion( const scoped_refptr& 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); FOR_EACH_OBSERVER(Listener, listeners_, OnVersionAttributesChanged(this, mask, GetInfo())); } void ServiceWorkerRegistration::SetInstallingVersion( const scoped_refptr& version) { if (installing_version_ == version) return; ChangedVersionAttributesMask mask; if (version) UnsetVersionInternal(version.get(), &mask); installing_version_ = version; mask.add(ChangedVersionAttributesMask::INSTALLING_VERSION); FOR_EACH_OBSERVER(Listener, listeners_, OnVersionAttributesChanged(this, mask, GetInfo())); } void ServiceWorkerRegistration::UnsetVersion(ServiceWorkerVersion* version) { if (!version) return; ChangedVersionAttributesMask mask; UnsetVersionInternal(version, &mask); if (mask.changed()) { ServiceWorkerRegistrationInfo info = GetInfo(); FOR_EACH_OBSERVER(Listener, listeners_, OnVersionAttributesChanged(this, mask, info)); } } 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 (scoped_ptr 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 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::GetUserData( const std::string& key, const GetUserDataCallback& callback) { DCHECK(context_); context_->storage()->GetUserData(registration_id_, key, callback); } void ServiceWorkerRegistration::StoreUserData( const std::string& key, const std::string& data, const StatusCallback& callback) { DCHECK(context_); context_->storage()->StoreUserData( registration_id_, pattern().GetOrigin(), key, data, callback); } void ServiceWorkerRegistration::ClearUserData( const std::string& key, const StatusCallback& callback) { DCHECK(context_); context_->storage()->ClearUserData(registration_id_, key, callback); } 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 activating_version = waiting_version(); scoped_refptr 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->DispatchActivateEvent( base::Bind(&ServiceWorkerRegistration::OnActivateEventFinished, this, activating_version)); } void ServiceWorkerRegistration::DeleteVersion( const scoped_refptr& version) { DCHECK_EQ(id(), version->registration_id()); UnsetVersion(version.get()); for (scoped_ptr 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; FOR_EACH_OBSERVER(Listener, listeners_, OnRegistrationFailed(this)); } } } void ServiceWorkerRegistration::OnActivateEventFinished( 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); ChangedVersionAttributesMask mask; if (installing_version_.get()) { installing_version_->Doom(); installing_version_ = NULL; mask.add(ChangedVersionAttributesMask::INSTALLING_VERSION); } if (waiting_version_.get()) { waiting_version_->Doom(); waiting_version_ = NULL; mask.add(ChangedVersionAttributesMask::WAITING_VERSION); } if (active_version_.get()) { active_version_->Doom(); active_version_->RemoveListener(this); active_version_ = NULL; mask.add(ChangedVersionAttributesMask::ACTIVE_VERSION); } if (mask.changed()) { ServiceWorkerRegistrationInfo info = GetInfo(); FOR_EACH_OBSERVER(Listener, listeners_, OnVersionAttributesChanged(this, mask, info)); } FOR_EACH_OBSERVER( Listener, listeners_, OnRegistrationFinishedUninstalling(this)); } void ServiceWorkerRegistration::OnRestoreFinished( const StatusCallback& callback, scoped_refptr version, ServiceWorkerStatusCode status) { if (!context_) { callback.Run(SERVICE_WORKER_ERROR_ABORT); return; } context_->storage()->NotifyDoneInstallingRegistration( this, version.get(), status); callback.Run(status); } } // namespace content