blob: 50f24df48b617bf8ad728e46253f59bdf73d43cf [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_version.h"
#include "base/command_line.h"
#include "base/stl_util.h"
#include "base/strings/string16.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/browser/service_worker/service_worker_utils.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 {
// Default delay to stop the worker context after all documents that
// are associated to the worker are closed.
// (Note that if all references to the version is dropped the worker
// is also stopped without delay)
const int64 kStopWorkerDelay = 30; // 30 secs.
void RunSoon(const base::Closure& callback) {
if (!callback.is_null())
base::MessageLoop::current()->PostTask(FROM_HERE, callback);
}
template <typename CallbackArray, typename Arg>
void RunCallbacks(ServiceWorkerVersion* version,
CallbackArray* callbacks_ptr,
const Arg& arg) {
CallbackArray callbacks;
callbacks.swap(*callbacks_ptr);
scoped_refptr<ServiceWorkerVersion> protect(version);
for (typename CallbackArray::const_iterator i = callbacks.begin();
i != callbacks.end(); ++i)
(*i).Run(arg);
}
template <typename IDMAP, typename Method, typename Params>
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<ServiceWorkerVersion> 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<ServiceWorkerContextCore> context)
: version_id_(version_id),
registration_id_(kInvalidServiceWorkerVersionId),
status_(NEW),
context_(context),
script_cache_map_(this, context),
weak_factory_(this) {
DCHECK(context_);
DCHECK(registration);
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() {
embedded_worker_->RemoveListener(this);
if (context_)
context_->RemoveLiveVersion(version_id_);
// EmbeddedWorker's dtor sends StopWorker if it's still running.
}
void ServiceWorkerVersion::SetStatus(Status status) {
if (status_ == status)
return;
status_ = status;
std::vector<base::Closure> callbacks;
callbacks.swap(status_change_callbacks_);
for (std::vector<base::Closure>::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(),
embedded_worker()->worker_devtools_agent_route_id());
}
void ServiceWorkerVersion::StartWorker(const StatusCallback& callback) {
StartWorkerWithCandidateProcesses(std::vector<int>(), callback);
}
void ServiceWorkerVersion::StartWorkerWithCandidateProcesses(
const std::vector<int>& possible_process_ids,
const StatusCallback& callback) {
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) {
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) {
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::DispatchPushEvent(const StatusCallback& callback,
const std::string& data) {
DCHECK_EQ(ACTIVE, status()) << status();
if (!CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExperimentalWebPlatformFeatures)) {
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::DispatchPushEvent,
weak_factory_.GetWeakPtr(),
callback, data)));
return;
}
int request_id = push_callbacks_.Add(new StatusCallback(callback));
ServiceWorkerStatusCode status = embedded_worker_->SendMessage(
ServiceWorkerMsg_PushEvent(request_id, data));
if (status != SERVICE_WORKER_OK) {
push_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());
if (stop_worker_timer_.IsRunning())
stop_worker_timer_.Stop();
}
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());
if (!HasControllee())
ScheduleStopWorker();
// 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::AddWaitingControllee(
ServiceWorkerProviderHost* provider_host) {
AddProcessToWorker(provider_host->process_id());
}
void ServiceWorkerVersion::RemoveWaitingControllee(
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<ServiceWorkerVersion> 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));
RunIDMapCallbacks(&push_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_HANDLER(ServiceWorkerHostMsg_PushEventFinished,
OnPushEventFinished)
IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PostMessageToDocument,
OnPostMessageToDocument)
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<int> 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<ServiceWorkerVersion> 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<ServiceWorkerVersion> 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<ServiceWorkerVersion> 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<ServiceWorkerVersion> protect(this);
callback->Run(SERVICE_WORKER_OK);
sync_callbacks_.Remove(request_id);
}
void ServiceWorkerVersion::OnPushEventFinished(
int request_id) {
StatusCallback* callback = push_callbacks_.Lookup(request_id);
if (!callback) {
NOTREACHED() << "Got unexpected message: " << request_id;
return;
}
scoped_refptr<ServiceWorkerVersion> protect(this);
callback->Run(SERVICE_WORKER_OK);
push_callbacks_.Remove(request_id);
}
void ServiceWorkerVersion::OnPostMessageToDocument(
int client_id,
const base::string16& message,
const std::vector<int>& sent_message_port_ids) {
ServiceWorkerProviderHost* provider_host =
controllee_by_id_.Lookup(client_id);
if (!provider_host) {
// The client may already have been closed, just ignore.
return;
}
provider_host->PostMessage(message, sent_message_port_ids);
}
void ServiceWorkerVersion::ScheduleStopWorker() {
if (running_status() != RUNNING)
return;
if (stop_worker_timer_.IsRunning()) {
stop_worker_timer_.Reset();
return;
}
stop_worker_timer_.Start(
FROM_HERE, base::TimeDelta::FromSeconds(kStopWorkerDelay),
base::Bind(&ServiceWorkerVersion::StopWorker,
weak_factory_.GetWeakPtr(),
base::Bind(&ServiceWorkerUtils::NoOpStatusCallback)));
}
} // namespace content