blob: c393502617e0a35f5c06bb8e86131c559e22df19 [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/renderer/service_worker/embedded_worker_context_client.h"
#include <map>
#include <string>
#include "base/lazy_instance.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/pickle.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/thread_task_runner_handle.h"
#include "base/threading/thread_local.h"
#include "base/trace_event/trace_event.h"
#include "content/child/request_extra_data.h"
#include "content/child/service_worker/service_worker_dispatcher.h"
#include "content/child/service_worker/service_worker_network_provider.h"
#include "content/child/service_worker/service_worker_provider_context.h"
#include "content/child/service_worker/service_worker_registration_handle_reference.h"
#include "content/child/service_worker/web_service_worker_impl.h"
#include "content/child/service_worker/web_service_worker_provider_impl.h"
#include "content/child/service_worker/web_service_worker_registration_impl.h"
#include "content/child/thread_safe_sender.h"
#include "content/child/worker_task_runner.h"
#include "content/common/devtools_messages.h"
#include "content/common/service_worker/embedded_worker_messages.h"
#include "content/common/service_worker/service_worker_types.h"
#include "content/public/renderer/document_state.h"
#include "content/renderer/devtools/devtools_agent.h"
#include "content/renderer/render_thread_impl.h"
#include "content/renderer/service_worker/embedded_worker_dispatcher.h"
#include "content/renderer/service_worker/service_worker_script_context.h"
#include "content/renderer/service_worker/service_worker_type_util.h"
#include "ipc/ipc_message_macros.h"
#include "third_party/WebKit/public/platform/WebServiceWorkerResponse.h"
#include "third_party/WebKit/public/platform/WebString.h"
#include "third_party/WebKit/public/web/WebDataSource.h"
#include "third_party/WebKit/public/web/WebServiceWorkerNetworkProvider.h"
namespace content {
namespace {
// For now client must be a per-thread instance.
// TODO(kinuko): This needs to be refactored when we start using thread pool
// or having multiple clients per one thread.
base::LazyInstance<base::ThreadLocalPointer<EmbeddedWorkerContextClient> >::
Leaky g_worker_client_tls = LAZY_INSTANCE_INITIALIZER;
void CallWorkerContextDestroyedOnMainThread(int embedded_worker_id) {
if (!RenderThreadImpl::current() ||
!RenderThreadImpl::current()->embedded_worker_dispatcher())
return;
RenderThreadImpl::current()->embedded_worker_dispatcher()->
WorkerContextDestroyed(embedded_worker_id);
}
// We store an instance of this class in the "extra data" of the WebDataSource
// and attach a ServiceWorkerNetworkProvider to it as base::UserData.
// (see createServiceWorkerNetworkProvider).
class DataSourceExtraData
: public blink::WebDataSource::ExtraData,
public base::SupportsUserData {
public:
DataSourceExtraData() {}
virtual ~DataSourceExtraData() {}
};
// Called on the main thread only and blink owns it.
class WebServiceWorkerNetworkProviderImpl
: public blink::WebServiceWorkerNetworkProvider {
public:
// Blink calls this method for each request starting with the main script,
// we tag them with the provider id.
virtual void willSendRequest(
blink::WebDataSource* data_source,
blink::WebURLRequest& request) {
ServiceWorkerNetworkProvider* provider =
ServiceWorkerNetworkProvider::FromDocumentState(
static_cast<DataSourceExtraData*>(data_source->extraData()));
scoped_ptr<RequestExtraData> extra_data(new RequestExtraData);
extra_data->set_service_worker_provider_id(provider->provider_id());
request.setExtraData(extra_data.release());
}
};
} // namespace
EmbeddedWorkerContextClient*
EmbeddedWorkerContextClient::ThreadSpecificInstance() {
return g_worker_client_tls.Pointer()->Get();
}
EmbeddedWorkerContextClient::EmbeddedWorkerContextClient(
int embedded_worker_id,
int64 service_worker_version_id,
const GURL& service_worker_scope,
const GURL& script_url,
int worker_devtools_agent_route_id)
: embedded_worker_id_(embedded_worker_id),
service_worker_version_id_(service_worker_version_id),
service_worker_scope_(service_worker_scope),
script_url_(script_url),
worker_devtools_agent_route_id_(worker_devtools_agent_route_id),
sender_(ChildThreadImpl::current()->thread_safe_sender()),
main_thread_task_runner_(RenderThreadImpl::current()->GetTaskRunner()),
weak_factory_(this) {
TRACE_EVENT_ASYNC_BEGIN0("ServiceWorker",
"EmbeddedWorkerContextClient::StartingWorkerContext",
this);
TRACE_EVENT_ASYNC_STEP_INTO0(
"ServiceWorker",
"EmbeddedWorkerContextClient::StartingWorkerContext",
this,
"PrepareWorker");
}
EmbeddedWorkerContextClient::~EmbeddedWorkerContextClient() {
}
bool EmbeddedWorkerContextClient::OnMessageReceived(
const IPC::Message& msg) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(EmbeddedWorkerContextClient, msg)
IPC_MESSAGE_HANDLER(EmbeddedWorkerContextMsg_MessageToWorker,
OnMessageToWorker)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void EmbeddedWorkerContextClient::Send(IPC::Message* message) {
sender_->Send(message);
}
blink::WebURL EmbeddedWorkerContextClient::scope() const {
return service_worker_scope_;
}
void EmbeddedWorkerContextClient::didPauseAfterDownload() {
Send(new EmbeddedWorkerHostMsg_DidPauseAfterDownload(embedded_worker_id_));
}
void EmbeddedWorkerContextClient::getClients(
const blink::WebServiceWorkerClientQueryOptions& options,
blink::WebServiceWorkerClientsCallbacks* callbacks) {
DCHECK(script_context_);
script_context_->GetClients(options, callbacks);
}
void EmbeddedWorkerContextClient::openWindow(
const blink::WebURL& url,
blink::WebServiceWorkerClientCallbacks* callbacks) {
DCHECK(script_context_);
script_context_->OpenWindow(url, callbacks);
}
void EmbeddedWorkerContextClient::setCachedMetadata(const blink::WebURL& url,
const char* data,
size_t size) {
DCHECK(script_context_);
script_context_->SetCachedMetadata(url, data, size);
}
void EmbeddedWorkerContextClient::clearCachedMetadata(
const blink::WebURL& url) {
DCHECK(script_context_);
script_context_->ClearCachedMetadata(url);
}
void EmbeddedWorkerContextClient::workerReadyForInspection() {
Send(new EmbeddedWorkerHostMsg_WorkerReadyForInspection(embedded_worker_id_));
}
void EmbeddedWorkerContextClient::workerContextFailedToStart() {
DCHECK(main_thread_task_runner_->RunsTasksOnCurrentThread());
DCHECK(!script_context_);
Send(new EmbeddedWorkerHostMsg_WorkerScriptLoadFailed(embedded_worker_id_));
RenderThreadImpl::current()->embedded_worker_dispatcher()->
WorkerContextDestroyed(embedded_worker_id_);
}
void EmbeddedWorkerContextClient::workerContextStarted(
blink::WebServiceWorkerContextProxy* proxy) {
DCHECK(!worker_task_runner_.get());
DCHECK_NE(0, WorkerTaskRunner::Instance()->CurrentWorkerId());
worker_task_runner_ = base::ThreadTaskRunnerHandle::Get();
// g_worker_client_tls.Pointer()->Get() could return NULL if this context
// gets deleted before workerContextStarted() is called.
DCHECK(g_worker_client_tls.Pointer()->Get() == NULL);
DCHECK(!script_context_);
g_worker_client_tls.Pointer()->Set(this);
script_context_.reset(new ServiceWorkerScriptContext(this, proxy));
SetRegistrationInServiceWorkerGlobalScope();
Send(new EmbeddedWorkerHostMsg_WorkerScriptLoaded(
embedded_worker_id_,
WorkerTaskRunner::Instance()->CurrentWorkerId(),
provider_context_->provider_id()));
TRACE_EVENT_ASYNC_STEP_INTO0(
"ServiceWorker",
"EmbeddedWorkerContextClient::StartingWorkerContext",
this,
"ExecuteScript");
}
void EmbeddedWorkerContextClient::didEvaluateWorkerScript(bool success) {
Send(new EmbeddedWorkerHostMsg_WorkerScriptEvaluated(
embedded_worker_id_, success));
// Schedule a task to send back WorkerStarted asynchronously,
// so that at the time we send it we can be sure that the
// worker run loop has been started.
worker_task_runner_->PostTask(
FROM_HERE, base::Bind(&EmbeddedWorkerContextClient::SendWorkerStarted,
weak_factory_.GetWeakPtr()));
}
void EmbeddedWorkerContextClient::willDestroyWorkerContext() {
// At this point OnWorkerRunLoopStopped is already called, so
// worker_task_runner_->RunsTasksOnCurrentThread() returns false
// (while we're still on the worker thread).
script_context_.reset();
// This also lets the message filter stop dispatching messages to
// this client.
g_worker_client_tls.Pointer()->Set(NULL);
}
void EmbeddedWorkerContextClient::workerContextDestroyed() {
DCHECK(g_worker_client_tls.Pointer()->Get() == NULL);
// Now we should be able to free the WebEmbeddedWorker container on the
// main thread.
main_thread_task_runner_->PostTask(
FROM_HERE,
base::Bind(&CallWorkerContextDestroyedOnMainThread,
embedded_worker_id_));
}
void EmbeddedWorkerContextClient::reportException(
const blink::WebString& error_message,
int line_number,
int column_number,
const blink::WebString& source_url) {
Send(new EmbeddedWorkerHostMsg_ReportException(
embedded_worker_id_, error_message, line_number,
column_number, GURL(source_url)));
}
void EmbeddedWorkerContextClient::reportConsoleMessage(
int source,
int level,
const blink::WebString& message,
int line_number,
const blink::WebString& source_url) {
EmbeddedWorkerHostMsg_ReportConsoleMessage_Params params;
params.source_identifier = source;
params.message_level = level;
params.message = message;
params.line_number = line_number;
params.source_url = GURL(source_url);
Send(new EmbeddedWorkerHostMsg_ReportConsoleMessage(
embedded_worker_id_, params));
}
void EmbeddedWorkerContextClient::sendDevToolsMessage(
int call_id,
const blink::WebString& message,
const blink::WebString& state_cookie) {
DevToolsAgent::SendChunkedProtocolMessage(
sender_.get(), worker_devtools_agent_route_id_,
call_id, message.utf8(), state_cookie.utf8());
}
void EmbeddedWorkerContextClient::didHandleActivateEvent(
int request_id,
blink::WebServiceWorkerEventResult result) {
DCHECK(script_context_);
script_context_->DidHandleActivateEvent(request_id, result);
}
void EmbeddedWorkerContextClient::didHandleInstallEvent(
int request_id,
blink::WebServiceWorkerEventResult result) {
DCHECK(script_context_);
script_context_->DidHandleInstallEvent(request_id, result);
}
void EmbeddedWorkerContextClient::didHandleFetchEvent(int request_id) {
DCHECK(script_context_);
script_context_->DidHandleFetchEvent(
request_id,
SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK,
ServiceWorkerResponse());
}
void EmbeddedWorkerContextClient::didHandleFetchEvent(
int request_id,
const blink::WebServiceWorkerResponse& web_response) {
DCHECK(script_context_);
ServiceWorkerHeaderMap headers;
GetServiceWorkerHeaderMapFromWebResponse(web_response, &headers);
ServiceWorkerResponse response(web_response.url(),
web_response.status(),
web_response.statusText().utf8(),
web_response.responseType(),
headers,
web_response.blobUUID().utf8(),
web_response.blobSize(),
web_response.streamURL());
script_context_->DidHandleFetchEvent(
request_id, SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, response);
}
void EmbeddedWorkerContextClient::didHandleNotificationClickEvent(
int request_id,
blink::WebServiceWorkerEventResult result) {
DCHECK(script_context_);
script_context_->DidHandleNotificationClickEvent(request_id, result);
}
void EmbeddedWorkerContextClient::didHandlePushEvent(
int request_id,
blink::WebServiceWorkerEventResult result) {
DCHECK(script_context_);
script_context_->DidHandlePushEvent(request_id, result);
}
void EmbeddedWorkerContextClient::didHandleSyncEvent(int request_id) {
DCHECK(script_context_);
script_context_->DidHandleSyncEvent(request_id);
}
void EmbeddedWorkerContextClient::didHandleCrossOriginConnectEvent(
int request_id,
bool accept_connection) {
DCHECK(script_context_);
script_context_->DidHandleCrossOriginConnectEvent(request_id,
accept_connection);
}
blink::WebServiceWorkerNetworkProvider*
EmbeddedWorkerContextClient::createServiceWorkerNetworkProvider(
blink::WebDataSource* data_source) {
DCHECK(main_thread_task_runner_->RunsTasksOnCurrentThread());
// Create a content::ServiceWorkerNetworkProvider for this data source so
// we can observe its requests.
scoped_ptr<ServiceWorkerNetworkProvider> provider(
new ServiceWorkerNetworkProvider(
MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_CONTROLLER));
provider_context_ = provider->context();
// Tell the network provider about which version to load.
provider->SetServiceWorkerVersionId(service_worker_version_id_);
// The provider is kept around for the lifetime of the DataSource
// and ownership is transferred to the DataSource.
DataSourceExtraData* extra_data = new DataSourceExtraData();
data_source->setExtraData(extra_data);
ServiceWorkerNetworkProvider::AttachToDocumentState(
extra_data, provider.Pass());
// Blink is responsible for deleting the returned object.
return new WebServiceWorkerNetworkProviderImpl();
}
blink::WebServiceWorkerProvider*
EmbeddedWorkerContextClient::createServiceWorkerProvider() {
DCHECK(main_thread_task_runner_->RunsTasksOnCurrentThread());
DCHECK(provider_context_);
// Blink is responsible for deleting the returned object.
return new WebServiceWorkerProviderImpl(
thread_safe_sender(), provider_context_.get());
}
void EmbeddedWorkerContextClient::postMessageToClient(
const blink::WebString& uuid,
const blink::WebString& message,
blink::WebMessagePortChannelArray* channels) {
DCHECK(script_context_);
script_context_->PostMessageToClient(
uuid, message, make_scoped_ptr(channels));
}
void EmbeddedWorkerContextClient::postMessageToCrossOriginClient(
const blink::WebCrossOriginServiceWorkerClient& client,
const blink::WebString& message,
blink::WebMessagePortChannelArray* channels) {
DCHECK(script_context_);
script_context_->PostCrossOriginMessageToClient(client, message,
make_scoped_ptr(channels));
}
void EmbeddedWorkerContextClient::focus(
const blink::WebString& uuid,
blink::WebServiceWorkerClientCallbacks* callback) {
DCHECK(script_context_);
script_context_->FocusClient(uuid, callback);
}
void EmbeddedWorkerContextClient::skipWaiting(
blink::WebServiceWorkerSkipWaitingCallbacks* callbacks) {
DCHECK(script_context_);
script_context_->SkipWaiting(callbacks);
}
void EmbeddedWorkerContextClient::claim(
blink::WebServiceWorkerClientsClaimCallbacks* callbacks) {
DCHECK(script_context_);
script_context_->ClaimClients(callbacks);
}
void EmbeddedWorkerContextClient::OnMessageToWorker(
int thread_id,
int embedded_worker_id,
const IPC::Message& message) {
if (!script_context_)
return;
DCHECK_EQ(embedded_worker_id_, embedded_worker_id);
script_context_->OnMessageReceived(message);
}
void EmbeddedWorkerContextClient::SendWorkerStarted() {
DCHECK(worker_task_runner_->RunsTasksOnCurrentThread());
TRACE_EVENT_ASYNC_END0("ServiceWorker",
"EmbeddedWorkerContextClient::StartingWorkerContext",
this);
Send(new EmbeddedWorkerHostMsg_WorkerStarted(embedded_worker_id_));
}
void EmbeddedWorkerContextClient::SetRegistrationInServiceWorkerGlobalScope() {
DCHECK(worker_task_runner_->RunsTasksOnCurrentThread());
DCHECK(provider_context_);
DCHECK(script_context_);
ServiceWorkerRegistrationObjectInfo info;
ServiceWorkerVersionAttributes attrs;
bool found =
provider_context_->GetRegistrationInfoAndVersionAttributes(&info, &attrs);
if (!found)
return; // Cannot be associated with a registration in some tests.
ServiceWorkerDispatcher* dispatcher =
ServiceWorkerDispatcher::GetOrCreateThreadSpecificInstance(
thread_safe_sender());
// Register a registration and its version attributes with the dispatcher
// living on the worker thread.
scoped_ptr<WebServiceWorkerRegistrationImpl> registration(
dispatcher->CreateServiceWorkerRegistration(info, false));
registration->SetInstalling(
dispatcher->GetServiceWorker(attrs.installing, false));
registration->SetWaiting(
dispatcher->GetServiceWorker(attrs.waiting, false));
registration->SetActive(
dispatcher->GetServiceWorker(attrs.active, false));
script_context_->SetRegistrationInServiceWorkerGlobalScope(
registration.Pass());
}
} // namespace content