blob: 936f5c84dc51d16975a3fcbfee72f53c845f321e [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/child/service_worker/service_worker_dispatcher.h"
#include "base/lazy_instance.h"
#include "base/stl_util.h"
#include "base/threading/thread_local.h"
#include "content/child/child_thread.h"
#include "content/child/service_worker/service_worker_handle_reference.h"
#include "content/child/service_worker/service_worker_provider_context.h"
#include "content/child/service_worker/web_service_worker_impl.h"
#include "content/child/thread_safe_sender.h"
#include "content/child/webmessageportchannel_impl.h"
#include "content/common/service_worker/service_worker_messages.h"
#include "content/public/common/url_utils.h"
#include "third_party/WebKit/public/platform/WebServiceWorkerProviderClient.h"
#include "third_party/WebKit/public/web/WebSecurityOrigin.h"
using blink::WebServiceWorkerError;
using blink::WebServiceWorkerProvider;
using base::ThreadLocalPointer;
namespace content {
namespace {
base::LazyInstance<ThreadLocalPointer<ServiceWorkerDispatcher> >::Leaky
g_dispatcher_tls = LAZY_INSTANCE_INITIALIZER;
ServiceWorkerDispatcher* const kHasBeenDeleted =
reinterpret_cast<ServiceWorkerDispatcher*>(0x1);
int CurrentWorkerId() {
return WorkerTaskRunner::Instance()->CurrentWorkerId();
}
} // namespace
ServiceWorkerDispatcher::ServiceWorkerDispatcher(
ThreadSafeSender* thread_safe_sender)
: thread_safe_sender_(thread_safe_sender) {
g_dispatcher_tls.Pointer()->Set(this);
}
ServiceWorkerDispatcher::~ServiceWorkerDispatcher() {
g_dispatcher_tls.Pointer()->Set(kHasBeenDeleted);
}
void ServiceWorkerDispatcher::OnMessageReceived(const IPC::Message& msg) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(ServiceWorkerDispatcher, msg)
IPC_MESSAGE_HANDLER(ServiceWorkerMsg_ServiceWorkerRegistered, OnRegistered)
IPC_MESSAGE_HANDLER(ServiceWorkerMsg_ServiceWorkerUnregistered,
OnUnregistered)
IPC_MESSAGE_HANDLER(ServiceWorkerMsg_ServiceWorkerRegistrationError,
OnRegistrationError)
IPC_MESSAGE_HANDLER(ServiceWorkerMsg_ServiceWorkerStateChanged,
OnServiceWorkerStateChanged)
IPC_MESSAGE_HANDLER(ServiceWorkerMsg_SetWaitingServiceWorker,
OnSetWaitingServiceWorker)
IPC_MESSAGE_HANDLER(ServiceWorkerMsg_SetCurrentServiceWorker,
OnSetCurrentServiceWorker)
IPC_MESSAGE_HANDLER(ServiceWorkerMsg_MessageToDocument,
OnPostMessage)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
DCHECK(handled) << "Unhandled message:" << msg.type();
}
bool ServiceWorkerDispatcher::Send(IPC::Message* msg) {
return thread_safe_sender_->Send(msg);
}
void ServiceWorkerDispatcher::RegisterServiceWorker(
int provider_id,
const GURL& pattern,
const GURL& script_url,
WebServiceWorkerProvider::WebServiceWorkerCallbacks* callbacks) {
DCHECK(callbacks);
if (pattern.possibly_invalid_spec().size() > GetMaxURLChars() ||
script_url.possibly_invalid_spec().size() > GetMaxURLChars()) {
scoped_ptr<WebServiceWorkerProvider::WebServiceWorkerCallbacks>
owned_callbacks(callbacks);
scoped_ptr<WebServiceWorkerError> error(new WebServiceWorkerError(
WebServiceWorkerError::ErrorTypeSecurity, "URL too long"));
callbacks->onError(error.release());
return;
}
int request_id = pending_callbacks_.Add(callbacks);
thread_safe_sender_->Send(new ServiceWorkerHostMsg_RegisterServiceWorker(
CurrentWorkerId(), request_id, provider_id, pattern, script_url));
}
void ServiceWorkerDispatcher::UnregisterServiceWorker(
int provider_id,
const GURL& pattern,
WebServiceWorkerProvider::WebServiceWorkerCallbacks* callbacks) {
DCHECK(callbacks);
if (pattern.possibly_invalid_spec().size() > GetMaxURLChars()) {
scoped_ptr<WebServiceWorkerProvider::WebServiceWorkerCallbacks>
owned_callbacks(callbacks);
scoped_ptr<WebServiceWorkerError> error(new WebServiceWorkerError(
WebServiceWorkerError::ErrorTypeSecurity, "URL too long"));
callbacks->onError(error.release());
return;
}
int request_id = pending_callbacks_.Add(callbacks);
thread_safe_sender_->Send(new ServiceWorkerHostMsg_UnregisterServiceWorker(
CurrentWorkerId(), request_id, provider_id, pattern));
}
void ServiceWorkerDispatcher::AddProviderContext(
ServiceWorkerProviderContext* provider_context) {
DCHECK(provider_context);
int provider_id = provider_context->provider_id();
DCHECK(!ContainsKey(provider_contexts_, provider_id));
provider_contexts_[provider_id] = provider_context;
}
void ServiceWorkerDispatcher::RemoveProviderContext(
ServiceWorkerProviderContext* provider_context) {
DCHECK(provider_context);
DCHECK(ContainsKey(provider_contexts_, provider_context->provider_id()));
provider_contexts_.erase(provider_context->provider_id());
worker_to_provider_.erase(provider_context->waiting_handle_id());
worker_to_provider_.erase(provider_context->current_handle_id());
}
void ServiceWorkerDispatcher::AddScriptClient(
int provider_id,
blink::WebServiceWorkerProviderClient* client) {
DCHECK(client);
DCHECK(!ContainsKey(script_clients_, provider_id));
script_clients_[provider_id] = client;
}
void ServiceWorkerDispatcher::RemoveScriptClient(int provider_id) {
// This could be possibly called multiple times to ensure termination.
if (ContainsKey(script_clients_, provider_id))
script_clients_.erase(provider_id);
}
ServiceWorkerDispatcher*
ServiceWorkerDispatcher::GetOrCreateThreadSpecificInstance(
ThreadSafeSender* thread_safe_sender) {
if (g_dispatcher_tls.Pointer()->Get() == kHasBeenDeleted) {
NOTREACHED() << "Re-instantiating TLS ServiceWorkerDispatcher.";
g_dispatcher_tls.Pointer()->Set(NULL);
}
if (g_dispatcher_tls.Pointer()->Get())
return g_dispatcher_tls.Pointer()->Get();
ServiceWorkerDispatcher* dispatcher =
new ServiceWorkerDispatcher(thread_safe_sender);
if (WorkerTaskRunner::Instance()->CurrentWorkerId())
WorkerTaskRunner::Instance()->AddStopObserver(dispatcher);
return dispatcher;
}
ServiceWorkerDispatcher* ServiceWorkerDispatcher::GetThreadSpecificInstance() {
if (g_dispatcher_tls.Pointer()->Get() == kHasBeenDeleted)
return NULL;
return g_dispatcher_tls.Pointer()->Get();
}
void ServiceWorkerDispatcher::OnWorkerRunLoopStopped() {
delete this;
}
WebServiceWorkerImpl* ServiceWorkerDispatcher::GetServiceWorker(
const ServiceWorkerObjectInfo& info,
bool adopt_handle) {
if (info.handle_id == kInvalidServiceWorkerHandleId)
return NULL;
WorkerObjectMap::iterator existing_worker =
service_workers_.find(info.handle_id);
if (existing_worker != service_workers_.end()) {
if (adopt_handle) {
// We are instructed to adopt a handle but we already have one, so
// adopt and destroy a handle ref.
ServiceWorkerHandleReference::Adopt(info, thread_safe_sender_);
}
return existing_worker->second;
}
scoped_ptr<ServiceWorkerHandleReference> handle_ref =
adopt_handle
? ServiceWorkerHandleReference::Adopt(info, thread_safe_sender_)
: ServiceWorkerHandleReference::Create(info, thread_safe_sender_);
// WebServiceWorkerImpl constructor calls AddServiceWorker.
return new WebServiceWorkerImpl(handle_ref.Pass(), thread_safe_sender_);
}
void ServiceWorkerDispatcher::OnRegistered(
int thread_id,
int request_id,
const ServiceWorkerObjectInfo& info) {
WebServiceWorkerProvider::WebServiceWorkerCallbacks* callbacks =
pending_callbacks_.Lookup(request_id);
DCHECK(callbacks);
if (!callbacks)
return;
callbacks->onSuccess(GetServiceWorker(info, true));
pending_callbacks_.Remove(request_id);
}
void ServiceWorkerDispatcher::OnUnregistered(
int thread_id,
int request_id) {
WebServiceWorkerProvider::WebServiceWorkerCallbacks* callbacks =
pending_callbacks_.Lookup(request_id);
DCHECK(callbacks);
if (!callbacks)
return;
callbacks->onSuccess(NULL);
pending_callbacks_.Remove(request_id);
}
void ServiceWorkerDispatcher::OnRegistrationError(
int thread_id,
int request_id,
WebServiceWorkerError::ErrorType error_type,
const base::string16& message) {
WebServiceWorkerProvider::WebServiceWorkerCallbacks* callbacks =
pending_callbacks_.Lookup(request_id);
DCHECK(callbacks);
if (!callbacks)
return;
scoped_ptr<WebServiceWorkerError> error(
new WebServiceWorkerError(error_type, message));
callbacks->onError(error.release());
pending_callbacks_.Remove(request_id);
}
void ServiceWorkerDispatcher::OnServiceWorkerStateChanged(
int thread_id,
int handle_id,
blink::WebServiceWorkerState state) {
WorkerObjectMap::iterator worker = service_workers_.find(handle_id);
if (worker != service_workers_.end())
worker->second->OnStateChanged(state);
WorkerToProviderMap::iterator provider = worker_to_provider_.find(handle_id);
if (provider != worker_to_provider_.end())
provider->second->OnServiceWorkerStateChanged(handle_id, state);
}
void ServiceWorkerDispatcher::OnSetWaitingServiceWorker(
int thread_id,
int provider_id,
const ServiceWorkerObjectInfo& info) {
ProviderContextMap::iterator provider = provider_contexts_.find(provider_id);
if (provider != provider_contexts_.end()) {
int existing_waiting_id = provider->second->waiting_handle_id();
if (existing_waiting_id != info.handle_id &&
existing_waiting_id != kInvalidServiceWorkerHandleId) {
WorkerToProviderMap::iterator associated_provider =
worker_to_provider_.find(existing_waiting_id);
DCHECK(associated_provider != worker_to_provider_.end());
DCHECK(associated_provider->second->provider_id() == provider_id);
worker_to_provider_.erase(associated_provider);
}
provider->second->OnSetWaitingServiceWorker(provider_id, info);
if (info.handle_id != kInvalidServiceWorkerHandleId)
worker_to_provider_[info.handle_id] = provider->second;
}
ScriptClientMap::iterator found = script_clients_.find(provider_id);
if (found != script_clients_.end()) {
// Populate the .waiting field with the new worker object.
found->second->setWaiting(GetServiceWorker(info, false));
}
}
void ServiceWorkerDispatcher::OnSetCurrentServiceWorker(
int thread_id,
int provider_id,
const ServiceWorkerObjectInfo& info) {
ProviderContextMap::iterator provider = provider_contexts_.find(provider_id);
if (provider != provider_contexts_.end()) {
provider->second->OnSetCurrentServiceWorker(provider_id, info);
worker_to_provider_[info.handle_id] = provider->second;
}
ScriptClientMap::iterator found = script_clients_.find(provider_id);
if (found != script_clients_.end()) {
// Populate the .controller field with the new worker object.
found->second->setController(GetServiceWorker(info, false));
}
}
void ServiceWorkerDispatcher::OnPostMessage(
int thread_id,
int provider_id,
const base::string16& message,
const std::vector<int>& sent_message_port_ids,
const std::vector<int>& new_routing_ids) {
// Make sure we're on the main document thread. (That must be the only
// thread we get this message)
DCHECK(ChildThread::current());
ScriptClientMap::iterator found = script_clients_.find(provider_id);
if (found == script_clients_.end()) {
// For now we do no queueing for messages sent to nonexistent / unattached
// client.
return;
}
std::vector<WebMessagePortChannelImpl*> ports;
if (!sent_message_port_ids.empty()) {
ports.resize(sent_message_port_ids.size());
for (size_t i = 0; i < sent_message_port_ids.size(); ++i) {
ports[i] = new WebMessagePortChannelImpl(
new_routing_ids[i], sent_message_port_ids[i],
base::MessageLoopProxy::current());
}
}
found->second->dispatchMessageEvent(message, ports);
}
void ServiceWorkerDispatcher::AddServiceWorker(
int handle_id, WebServiceWorkerImpl* worker) {
DCHECK(!ContainsKey(service_workers_, handle_id));
service_workers_[handle_id] = worker;
}
void ServiceWorkerDispatcher::RemoveServiceWorker(int handle_id) {
DCHECK(ContainsKey(service_workers_, handle_id));
service_workers_.erase(handle_id);
}
} // namespace content