// 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_version.h" #include "base/command_line.h" #include "base/stl_util.h" #include "content/browser/service_worker/embedded_worker_instance.h" #include "content/browser/service_worker/embedded_worker_registry.h" #include "content/browser/service_worker/service_worker_context_core.h" #include "content/browser/service_worker/service_worker_registration.h" #include "content/common/service_worker/service_worker_messages.h" #include "content/public/browser/browser_thread.h" #include "content/public/common/content_switches.h" namespace content { typedef ServiceWorkerVersion::StatusCallback StatusCallback; typedef ServiceWorkerVersion::MessageCallback MessageCallback; namespace { void RunSoon(const base::Closure& callback) { if (!callback.is_null()) base::MessageLoop::current()->PostTask(FROM_HERE, callback); } template void RunCallbacks(ServiceWorkerVersion* version, CallbackArray* callbacks_ptr, const Arg& arg) { CallbackArray callbacks; callbacks.swap(*callbacks_ptr); scoped_refptr protect(version); for (typename CallbackArray::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i) (*i).Run(arg); } template void RunIDMapCallbacks(IDMAP* callbacks, Method method, const Params& params) { typename IDMAP::iterator iter(callbacks); while (!iter.IsAtEnd()) { DispatchToMethod(iter.GetCurrentValue(), method, params); iter.Advance(); } callbacks->Clear(); } // A callback adapter to start a |task| after StartWorker. void RunTaskAfterStartWorker( base::WeakPtr version, const StatusCallback& error_callback, const base::Closure& task, ServiceWorkerStatusCode status) { if (status != SERVICE_WORKER_OK) { if (!error_callback.is_null()) error_callback.Run(status); return; } if (version->running_status() != ServiceWorkerVersion::RUNNING) { // We've tried to start the worker (and it has succeeded), but // it looks it's not running yet. NOTREACHED() << "The worker's not running after successful StartWorker"; if (!error_callback.is_null()) error_callback.Run(SERVICE_WORKER_ERROR_START_WORKER_FAILED); return; } task.Run(); } void RunErrorFetchCallback(const ServiceWorkerVersion::FetchCallback& callback, ServiceWorkerStatusCode status) { callback.Run(status, SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK, ServiceWorkerResponse()); } } // namespace ServiceWorkerVersion::ServiceWorkerVersion( ServiceWorkerRegistration* registration, int64 version_id, base::WeakPtr context) : version_id_(version_id), registration_id_(kInvalidServiceWorkerVersionId), status_(NEW), context_(context), weak_factory_(this) { DCHECK(context_); if (registration) { registration_id_ = registration->id(); script_url_ = registration->script_url(); scope_ = registration->pattern(); } context_->AddLiveVersion(this); embedded_worker_ = context_->embedded_worker_registry()->CreateWorker(); embedded_worker_->AddListener(this); } ServiceWorkerVersion::~ServiceWorkerVersion() { if (embedded_worker_) { embedded_worker_->RemoveListener(this); embedded_worker_.reset(); } if (context_) context_->RemoveLiveVersion(version_id_); } void ServiceWorkerVersion::SetStatus(Status status) { if (status_ == status) return; status_ = status; std::vector callbacks; callbacks.swap(status_change_callbacks_); for (std::vector::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i) { (*i).Run(); } FOR_EACH_OBSERVER(Listener, listeners_, OnVersionStateChanged(this)); } void ServiceWorkerVersion::RegisterStatusChangeCallback( const base::Closure& callback) { status_change_callbacks_.push_back(callback); } ServiceWorkerVersionInfo ServiceWorkerVersion::GetInfo() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); return ServiceWorkerVersionInfo(running_status(), status(), version_id(), embedded_worker()->process_id(), embedded_worker()->thread_id()); } void ServiceWorkerVersion::StartWorker(const StatusCallback& callback) { StartWorkerWithCandidateProcesses(std::vector(), callback); } void ServiceWorkerVersion::StartWorkerWithCandidateProcesses( const std::vector& possible_process_ids, const StatusCallback& callback) { DCHECK(embedded_worker_); switch (running_status()) { case RUNNING: RunSoon(base::Bind(callback, SERVICE_WORKER_OK)); return; case STOPPING: RunSoon(base::Bind(callback, SERVICE_WORKER_ERROR_START_WORKER_FAILED)); return; case STOPPED: case STARTING: start_callbacks_.push_back(callback); if (running_status() == STOPPED) { embedded_worker_->Start( version_id_, scope_, script_url_, possible_process_ids, base::Bind(&ServiceWorkerVersion::RunStartWorkerCallbacksOnError, weak_factory_.GetWeakPtr())); } return; } } void ServiceWorkerVersion::StopWorker(const StatusCallback& callback) { DCHECK(embedded_worker_); if (running_status() == STOPPED) { RunSoon(base::Bind(callback, SERVICE_WORKER_OK)); return; } if (stop_callbacks_.empty()) { ServiceWorkerStatusCode status = embedded_worker_->Stop(); if (status != SERVICE_WORKER_OK) { RunSoon(base::Bind(callback, status)); return; } } stop_callbacks_.push_back(callback); } void ServiceWorkerVersion::SendMessage( const IPC::Message& message, const StatusCallback& callback) { DCHECK(embedded_worker_); if (running_status() != RUNNING) { // Schedule calling this method after starting the worker. StartWorker(base::Bind(&RunTaskAfterStartWorker, weak_factory_.GetWeakPtr(), callback, base::Bind(&self::SendMessage, weak_factory_.GetWeakPtr(), message, callback))); return; } ServiceWorkerStatusCode status = embedded_worker_->SendMessage(message); RunSoon(base::Bind(callback, status)); } void ServiceWorkerVersion::DispatchInstallEvent( int active_version_id, const StatusCallback& callback) { DCHECK_EQ(NEW, status()) << status(); SetStatus(INSTALLING); if (running_status() != RUNNING) { // Schedule calling this method after starting the worker. StartWorker( base::Bind(&RunTaskAfterStartWorker, weak_factory_.GetWeakPtr(), callback, base::Bind(&self::DispatchInstallEventAfterStartWorker, weak_factory_.GetWeakPtr(), active_version_id, callback))); } else { DispatchInstallEventAfterStartWorker(active_version_id, callback); } } void ServiceWorkerVersion::DispatchActivateEvent( const StatusCallback& callback) { DCHECK_EQ(INSTALLED, status()) << status(); SetStatus(ACTIVATING); if (running_status() != RUNNING) { // Schedule calling this method after starting the worker. StartWorker( base::Bind(&RunTaskAfterStartWorker, weak_factory_.GetWeakPtr(), callback, base::Bind(&self::DispatchActivateEventAfterStartWorker, weak_factory_.GetWeakPtr(), callback))); } else { DispatchActivateEventAfterStartWorker(callback); } } void ServiceWorkerVersion::DispatchFetchEvent( const ServiceWorkerFetchRequest& request, const FetchCallback& callback) { DCHECK_EQ(ACTIVE, status()) << status(); if (running_status() != RUNNING) { // Schedule calling this method after starting the worker. StartWorker(base::Bind(&RunTaskAfterStartWorker, weak_factory_.GetWeakPtr(), base::Bind(&RunErrorFetchCallback, callback), base::Bind(&self::DispatchFetchEvent, weak_factory_.GetWeakPtr(), request, callback))); return; } int request_id = fetch_callbacks_.Add(new FetchCallback(callback)); ServiceWorkerStatusCode status = embedded_worker_->SendMessage( ServiceWorkerMsg_FetchEvent(request_id, request)); if (status != SERVICE_WORKER_OK) { fetch_callbacks_.Remove(request_id); RunSoon(base::Bind(&RunErrorFetchCallback, callback, SERVICE_WORKER_ERROR_FAILED)); } } void ServiceWorkerVersion::DispatchSyncEvent(const StatusCallback& callback) { DCHECK_EQ(ACTIVE, status()) << status(); if (!CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableServiceWorkerSync)) { callback.Run(SERVICE_WORKER_ERROR_ABORT); return; } if (running_status() != RUNNING) { // Schedule calling this method after starting the worker. StartWorker(base::Bind(&RunTaskAfterStartWorker, weak_factory_.GetWeakPtr(), callback, base::Bind(&self::DispatchSyncEvent, weak_factory_.GetWeakPtr(), callback))); return; } int request_id = sync_callbacks_.Add(new StatusCallback(callback)); ServiceWorkerStatusCode status = embedded_worker_->SendMessage( ServiceWorkerMsg_SyncEvent(request_id)); if (status != SERVICE_WORKER_OK) { sync_callbacks_.Remove(request_id); RunSoon(base::Bind(callback, status)); } } void ServiceWorkerVersion::AddProcessToWorker(int process_id) { embedded_worker_->AddProcessReference(process_id); } void ServiceWorkerVersion::RemoveProcessFromWorker(int process_id) { embedded_worker_->ReleaseProcessReference(process_id); } bool ServiceWorkerVersion::HasProcessToRun() const { return embedded_worker_->HasProcessToRun(); } void ServiceWorkerVersion::AddControllee( ServiceWorkerProviderHost* provider_host) { DCHECK(!ContainsKey(controllee_map_, provider_host)); int controllee_id = controllee_by_id_.Add(provider_host); controllee_map_[provider_host] = controllee_id; AddProcessToWorker(provider_host->process_id()); } void ServiceWorkerVersion::RemoveControllee( ServiceWorkerProviderHost* provider_host) { ControlleeMap::iterator found = controllee_map_.find(provider_host); DCHECK(found != controllee_map_.end()); controllee_by_id_.Remove(found->second); controllee_map_.erase(found); RemoveProcessFromWorker(provider_host->process_id()); // TODO(kinuko): Fire NoControllees notification when the # of controllees // reaches 0, so that a new pending version can be activated (which will // deactivate this version). // TODO(michaeln): On no controllees call storage DeleteVersionResources // if this version has been deactivated. Probably storage can listen for // NoControllees for versions that have been deleted. } void ServiceWorkerVersion::AddPendingControllee( ServiceWorkerProviderHost* provider_host) { AddProcessToWorker(provider_host->process_id()); } void ServiceWorkerVersion::RemovePendingControllee( ServiceWorkerProviderHost* provider_host) { RemoveProcessFromWorker(provider_host->process_id()); } void ServiceWorkerVersion::AddListener(Listener* listener) { listeners_.AddObserver(listener); } void ServiceWorkerVersion::RemoveListener(Listener* listener) { listeners_.RemoveObserver(listener); } void ServiceWorkerVersion::OnStarted() { DCHECK_EQ(RUNNING, running_status()); // Fire all start callbacks. RunCallbacks(this, &start_callbacks_, SERVICE_WORKER_OK); FOR_EACH_OBSERVER(Listener, listeners_, OnWorkerStarted(this)); } void ServiceWorkerVersion::OnStopped() { DCHECK_EQ(STOPPED, running_status()); scoped_refptr protect(this); // Fire all stop callbacks. RunCallbacks(this, &stop_callbacks_, SERVICE_WORKER_OK); // Let all start callbacks fail. RunCallbacks( this, &start_callbacks_, SERVICE_WORKER_ERROR_START_WORKER_FAILED); // Let all message callbacks fail (this will also fire and clear all // callbacks for events). // TODO(kinuko): Consider if we want to add queue+resend mechanism here. RunIDMapCallbacks(&activate_callbacks_, &StatusCallback::Run, MakeTuple(SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED)); RunIDMapCallbacks(&install_callbacks_, &StatusCallback::Run, MakeTuple(SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED)); RunIDMapCallbacks(&fetch_callbacks_, &FetchCallback::Run, MakeTuple(SERVICE_WORKER_ERROR_FAILED, SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK, ServiceWorkerResponse())); RunIDMapCallbacks(&sync_callbacks_, &StatusCallback::Run, MakeTuple(SERVICE_WORKER_ERROR_FAILED)); FOR_EACH_OBSERVER(Listener, listeners_, OnWorkerStopped(this)); } void ServiceWorkerVersion::OnReportException( const base::string16& error_message, int line_number, int column_number, const GURL& source_url) { FOR_EACH_OBSERVER( Listener, listeners_, OnErrorReported( this, error_message, line_number, column_number, source_url)); } void ServiceWorkerVersion::OnReportConsoleMessage(int source_identifier, int message_level, const base::string16& message, int line_number, const GURL& source_url) { FOR_EACH_OBSERVER(Listener, listeners_, OnReportConsoleMessage(this, source_identifier, message_level, message, line_number, source_url)); } bool ServiceWorkerVersion::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(ServiceWorkerVersion, message) IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_GetClientDocuments, OnGetClientDocuments) IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_ActivateEventFinished, OnActivateEventFinished) IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_InstallEventFinished, OnInstallEventFinished) IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_FetchEventFinished, OnFetchEventFinished) IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_SyncEventFinished, OnSyncEventFinished) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } void ServiceWorkerVersion::RunStartWorkerCallbacksOnError( ServiceWorkerStatusCode status) { if (status != SERVICE_WORKER_OK) RunCallbacks(this, &start_callbacks_, status); } void ServiceWorkerVersion::DispatchInstallEventAfterStartWorker( int active_version_id, const StatusCallback& callback) { DCHECK_EQ(RUNNING, running_status()) << "Worker stopped too soon after it was started."; int request_id = install_callbacks_.Add(new StatusCallback(callback)); ServiceWorkerStatusCode status = embedded_worker_->SendMessage( ServiceWorkerMsg_InstallEvent(request_id, active_version_id)); if (status != SERVICE_WORKER_OK) { install_callbacks_.Remove(request_id); RunSoon(base::Bind(callback, status)); } } void ServiceWorkerVersion::DispatchActivateEventAfterStartWorker( const StatusCallback& callback) { DCHECK_EQ(RUNNING, running_status()) << "Worker stopped too soon after it was started."; int request_id = activate_callbacks_.Add(new StatusCallback(callback)); ServiceWorkerStatusCode status = embedded_worker_->SendMessage(ServiceWorkerMsg_ActivateEvent(request_id)); if (status != SERVICE_WORKER_OK) { activate_callbacks_.Remove(request_id); RunSoon(base::Bind(callback, status)); } } void ServiceWorkerVersion::OnGetClientDocuments(int request_id) { std::vector client_ids; ControlleeByIDMap::iterator it(&controllee_by_id_); while (!it.IsAtEnd()) { client_ids.push_back(it.GetCurrentKey()); it.Advance(); } // Don't bother if it's no longer running. if (running_status() == RUNNING) { embedded_worker_->SendMessage( ServiceWorkerMsg_DidGetClientDocuments(request_id, client_ids)); } } void ServiceWorkerVersion::OnActivateEventFinished( int request_id, blink::WebServiceWorkerEventResult result) { StatusCallback* callback = activate_callbacks_.Lookup(request_id); if (!callback) { NOTREACHED() << "Got unexpected message: " << request_id; return; } ServiceWorkerStatusCode status = SERVICE_WORKER_OK; if (result == blink::WebServiceWorkerEventResultRejected) status = SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED; else SetStatus(ACTIVE); scoped_refptr protect(this); callback->Run(status); activate_callbacks_.Remove(request_id); } void ServiceWorkerVersion::OnInstallEventFinished( int request_id, blink::WebServiceWorkerEventResult result) { StatusCallback* callback = install_callbacks_.Lookup(request_id); if (!callback) { NOTREACHED() << "Got unexpected message: " << request_id; return; } ServiceWorkerStatusCode status = SERVICE_WORKER_OK; if (result == blink::WebServiceWorkerEventResultRejected) status = SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED; else SetStatus(INSTALLED); scoped_refptr protect(this); callback->Run(status); install_callbacks_.Remove(request_id); } void ServiceWorkerVersion::OnFetchEventFinished( int request_id, ServiceWorkerFetchEventResult result, const ServiceWorkerResponse& response) { FetchCallback* callback = fetch_callbacks_.Lookup(request_id); if (!callback) { NOTREACHED() << "Got unexpected message: " << request_id; return; } scoped_refptr protect(this); callback->Run(SERVICE_WORKER_OK, result, response); fetch_callbacks_.Remove(request_id); } void ServiceWorkerVersion::OnSyncEventFinished( int request_id) { StatusCallback* callback = sync_callbacks_.Lookup(request_id); if (!callback) { NOTREACHED() << "Got unexpected message: " << request_id; return; } scoped_refptr protect(this); callback->Run(SERVICE_WORKER_OK); sync_callbacks_.Remove(request_id); } void ServiceWorkerVersion::AddToScriptCache( const GURL& url, int64 resource_id) { DCHECK_EQ(kInvalidServiceWorkerResponseId, LookupInScriptCache(url)); DCHECK_EQ(NEW, status_); script_cache_map_[url] = resource_id; } int64 ServiceWorkerVersion::LookupInScriptCache(const GURL& url) { ResourceIDMap::const_iterator found = script_cache_map_.find(url); if (found == script_cache_map_.end()) return kInvalidServiceWorkerResponseId; return found->second; } } // namespace content