1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/child/service_worker/service_worker_dispatcher.h"
7 #include "base/lazy_instance.h"
8 #include "base/stl_util.h"
9 #include "base/threading/thread_local.h"
10 #include "content/child/child_thread.h"
11 #include "content/child/service_worker/service_worker_handle_reference.h"
12 #include "content/child/service_worker/service_worker_provider_context.h"
13 #include "content/child/service_worker/web_service_worker_impl.h"
14 #include "content/child/thread_safe_sender.h"
15 #include "content/child/webmessageportchannel_impl.h"
16 #include "content/common/service_worker/service_worker_messages.h"
17 #include "content/public/common/url_utils.h"
18 #include "third_party/WebKit/public/platform/WebServiceWorkerProviderClient.h"
19 #include "third_party/WebKit/public/web/WebSecurityOrigin.h"
21 using blink::WebServiceWorkerError
;
22 using blink::WebServiceWorkerProvider
;
23 using base::ThreadLocalPointer
;
29 base::LazyInstance
<ThreadLocalPointer
<ServiceWorkerDispatcher
> >::Leaky
30 g_dispatcher_tls
= LAZY_INSTANCE_INITIALIZER
;
32 ServiceWorkerDispatcher
* const kHasBeenDeleted
=
33 reinterpret_cast<ServiceWorkerDispatcher
*>(0x1);
35 int CurrentWorkerId() {
36 return WorkerTaskRunner::Instance()->CurrentWorkerId();
41 ServiceWorkerDispatcher::ServiceWorkerDispatcher(
42 ThreadSafeSender
* thread_safe_sender
)
43 : thread_safe_sender_(thread_safe_sender
) {
44 g_dispatcher_tls
.Pointer()->Set(this);
47 ServiceWorkerDispatcher::~ServiceWorkerDispatcher() {
48 g_dispatcher_tls
.Pointer()->Set(kHasBeenDeleted
);
51 void ServiceWorkerDispatcher::OnMessageReceived(const IPC::Message
& msg
) {
53 IPC_BEGIN_MESSAGE_MAP(ServiceWorkerDispatcher
, msg
)
54 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_ServiceWorkerRegistered
, OnRegistered
)
55 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_ServiceWorkerUnregistered
,
57 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_ServiceWorkerRegistrationError
,
59 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_ServiceWorkerStateChanged
,
60 OnServiceWorkerStateChanged
)
61 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_SetInstallingServiceWorker
,
62 OnSetInstallingServiceWorker
)
63 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_SetWaitingServiceWorker
,
64 OnSetWaitingServiceWorker
)
65 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_SetActiveServiceWorker
,
66 OnSetActiveServiceWorker
)
67 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_SetControllerServiceWorker
,
68 OnSetControllerServiceWorker
)
69 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_MessageToDocument
,
71 IPC_MESSAGE_UNHANDLED(handled
= false)
73 DCHECK(handled
) << "Unhandled message:" << msg
.type();
76 bool ServiceWorkerDispatcher::Send(IPC::Message
* msg
) {
77 return thread_safe_sender_
->Send(msg
);
80 void ServiceWorkerDispatcher::RegisterServiceWorker(
83 const GURL
& script_url
,
84 WebServiceWorkerProvider::WebServiceWorkerCallbacks
* callbacks
) {
87 if (pattern
.possibly_invalid_spec().size() > GetMaxURLChars() ||
88 script_url
.possibly_invalid_spec().size() > GetMaxURLChars()) {
89 scoped_ptr
<WebServiceWorkerProvider::WebServiceWorkerCallbacks
>
90 owned_callbacks(callbacks
);
91 scoped_ptr
<WebServiceWorkerError
> error(new WebServiceWorkerError(
92 WebServiceWorkerError::ErrorTypeSecurity
, "URL too long"));
93 callbacks
->onError(error
.release());
97 int request_id
= pending_callbacks_
.Add(callbacks
);
98 thread_safe_sender_
->Send(new ServiceWorkerHostMsg_RegisterServiceWorker(
99 CurrentWorkerId(), request_id
, provider_id
, pattern
, script_url
));
102 void ServiceWorkerDispatcher::UnregisterServiceWorker(
105 WebServiceWorkerProvider::WebServiceWorkerCallbacks
* callbacks
) {
108 if (pattern
.possibly_invalid_spec().size() > GetMaxURLChars()) {
109 scoped_ptr
<WebServiceWorkerProvider::WebServiceWorkerCallbacks
>
110 owned_callbacks(callbacks
);
111 scoped_ptr
<WebServiceWorkerError
> error(new WebServiceWorkerError(
112 WebServiceWorkerError::ErrorTypeSecurity
, "URL too long"));
113 callbacks
->onError(error
.release());
117 int request_id
= pending_callbacks_
.Add(callbacks
);
118 thread_safe_sender_
->Send(new ServiceWorkerHostMsg_UnregisterServiceWorker(
119 CurrentWorkerId(), request_id
, provider_id
, pattern
));
122 void ServiceWorkerDispatcher::AddProviderContext(
123 ServiceWorkerProviderContext
* provider_context
) {
124 DCHECK(provider_context
);
125 int provider_id
= provider_context
->provider_id();
126 DCHECK(!ContainsKey(provider_contexts_
, provider_id
));
127 provider_contexts_
[provider_id
] = provider_context
;
130 void ServiceWorkerDispatcher::RemoveProviderContext(
131 ServiceWorkerProviderContext
* provider_context
) {
132 DCHECK(provider_context
);
133 DCHECK(ContainsKey(provider_contexts_
, provider_context
->provider_id()));
134 provider_contexts_
.erase(provider_context
->provider_id());
135 worker_to_provider_
.erase(provider_context
->installing_handle_id());
136 worker_to_provider_
.erase(provider_context
->waiting_handle_id());
137 worker_to_provider_
.erase(provider_context
->active_handle_id());
138 worker_to_provider_
.erase(provider_context
->controller_handle_id());
141 void ServiceWorkerDispatcher::AddScriptClient(
143 blink::WebServiceWorkerProviderClient
* client
) {
145 DCHECK(!ContainsKey(script_clients_
, provider_id
));
146 script_clients_
[provider_id
] = client
;
149 void ServiceWorkerDispatcher::RemoveScriptClient(int provider_id
) {
150 // This could be possibly called multiple times to ensure termination.
151 if (ContainsKey(script_clients_
, provider_id
))
152 script_clients_
.erase(provider_id
);
155 ServiceWorkerDispatcher
*
156 ServiceWorkerDispatcher::GetOrCreateThreadSpecificInstance(
157 ThreadSafeSender
* thread_safe_sender
) {
158 if (g_dispatcher_tls
.Pointer()->Get() == kHasBeenDeleted
) {
159 NOTREACHED() << "Re-instantiating TLS ServiceWorkerDispatcher.";
160 g_dispatcher_tls
.Pointer()->Set(NULL
);
162 if (g_dispatcher_tls
.Pointer()->Get())
163 return g_dispatcher_tls
.Pointer()->Get();
165 ServiceWorkerDispatcher
* dispatcher
=
166 new ServiceWorkerDispatcher(thread_safe_sender
);
167 if (WorkerTaskRunner::Instance()->CurrentWorkerId())
168 WorkerTaskRunner::Instance()->AddStopObserver(dispatcher
);
172 ServiceWorkerDispatcher
* ServiceWorkerDispatcher::GetThreadSpecificInstance() {
173 if (g_dispatcher_tls
.Pointer()->Get() == kHasBeenDeleted
)
175 return g_dispatcher_tls
.Pointer()->Get();
178 void ServiceWorkerDispatcher::OnWorkerRunLoopStopped() {
182 WebServiceWorkerImpl
* ServiceWorkerDispatcher::GetServiceWorker(
183 const ServiceWorkerObjectInfo
& info
,
185 if (info
.handle_id
== kInvalidServiceWorkerHandleId
)
188 WorkerObjectMap::iterator existing_worker
=
189 service_workers_
.find(info
.handle_id
);
191 if (existing_worker
!= service_workers_
.end()) {
193 // We are instructed to adopt a handle but we already have one, so
194 // adopt and destroy a handle ref.
195 ServiceWorkerHandleReference::Adopt(info
, thread_safe_sender_
);
197 return existing_worker
->second
;
200 scoped_ptr
<ServiceWorkerHandleReference
> handle_ref
=
202 ? ServiceWorkerHandleReference::Adopt(info
, thread_safe_sender_
)
203 : ServiceWorkerHandleReference::Create(info
, thread_safe_sender_
);
204 // WebServiceWorkerImpl constructor calls AddServiceWorker.
205 return new WebServiceWorkerImpl(handle_ref
.Pass(), thread_safe_sender_
);
208 void ServiceWorkerDispatcher::OnRegistered(
211 const ServiceWorkerObjectInfo
& info
) {
212 WebServiceWorkerProvider::WebServiceWorkerCallbacks
* callbacks
=
213 pending_callbacks_
.Lookup(request_id
);
218 callbacks
->onSuccess(GetServiceWorker(info
, true));
219 pending_callbacks_
.Remove(request_id
);
222 void ServiceWorkerDispatcher::OnUnregistered(
225 WebServiceWorkerProvider::WebServiceWorkerCallbacks
* callbacks
=
226 pending_callbacks_
.Lookup(request_id
);
231 callbacks
->onSuccess(NULL
);
232 pending_callbacks_
.Remove(request_id
);
235 void ServiceWorkerDispatcher::OnRegistrationError(
238 WebServiceWorkerError::ErrorType error_type
,
239 const base::string16
& message
) {
240 WebServiceWorkerProvider::WebServiceWorkerCallbacks
* callbacks
=
241 pending_callbacks_
.Lookup(request_id
);
246 scoped_ptr
<WebServiceWorkerError
> error(
247 new WebServiceWorkerError(error_type
, message
));
248 callbacks
->onError(error
.release());
249 pending_callbacks_
.Remove(request_id
);
252 void ServiceWorkerDispatcher::OnServiceWorkerStateChanged(
255 blink::WebServiceWorkerState state
) {
256 WorkerObjectMap::iterator worker
= service_workers_
.find(handle_id
);
257 if (worker
!= service_workers_
.end())
258 worker
->second
->OnStateChanged(state
);
260 WorkerToProviderMap::iterator provider
= worker_to_provider_
.find(handle_id
);
261 if (provider
!= worker_to_provider_
.end())
262 provider
->second
->OnServiceWorkerStateChanged(handle_id
, state
);
265 void ServiceWorkerDispatcher::OnSetInstallingServiceWorker(
268 const ServiceWorkerObjectInfo
& info
) {
269 ProviderContextMap::iterator provider
= provider_contexts_
.find(provider_id
);
270 if (provider
!= provider_contexts_
.end()) {
271 int existing_installing_id
= provider
->second
->installing_handle_id();
272 if (existing_installing_id
!= info
.handle_id
&&
273 existing_installing_id
!= kInvalidServiceWorkerHandleId
) {
274 WorkerToProviderMap::iterator associated_provider
=
275 worker_to_provider_
.find(existing_installing_id
);
276 DCHECK(associated_provider
!= worker_to_provider_
.end());
277 DCHECK(associated_provider
->second
->provider_id() == provider_id
);
278 worker_to_provider_
.erase(associated_provider
);
280 provider
->second
->OnSetInstallingServiceWorker(provider_id
, info
);
281 if (info
.handle_id
!= kInvalidServiceWorkerHandleId
)
282 worker_to_provider_
[info
.handle_id
] = provider
->second
;
285 ScriptClientMap::iterator found
= script_clients_
.find(provider_id
);
286 if (found
!= script_clients_
.end()) {
287 // Populate the .installing field with the new worker object.
288 found
->second
->setInstalling(GetServiceWorker(info
, false));
292 void ServiceWorkerDispatcher::OnSetWaitingServiceWorker(
295 const ServiceWorkerObjectInfo
& info
) {
296 ProviderContextMap::iterator provider
= provider_contexts_
.find(provider_id
);
297 if (provider
!= provider_contexts_
.end()) {
298 int existing_waiting_id
= provider
->second
->waiting_handle_id();
299 if (existing_waiting_id
!= info
.handle_id
&&
300 existing_waiting_id
!= kInvalidServiceWorkerHandleId
) {
301 WorkerToProviderMap::iterator associated_provider
=
302 worker_to_provider_
.find(existing_waiting_id
);
303 DCHECK(associated_provider
!= worker_to_provider_
.end());
304 DCHECK(associated_provider
->second
->provider_id() == provider_id
);
305 worker_to_provider_
.erase(associated_provider
);
307 provider
->second
->OnSetWaitingServiceWorker(provider_id
, info
);
308 if (info
.handle_id
!= kInvalidServiceWorkerHandleId
)
309 worker_to_provider_
[info
.handle_id
] = provider
->second
;
312 ScriptClientMap::iterator found
= script_clients_
.find(provider_id
);
313 if (found
!= script_clients_
.end()) {
314 // Populate the .waiting field with the new worker object.
315 found
->second
->setWaiting(GetServiceWorker(info
, false));
319 void ServiceWorkerDispatcher::OnSetActiveServiceWorker(
322 const ServiceWorkerObjectInfo
& info
) {
323 ProviderContextMap::iterator provider
= provider_contexts_
.find(provider_id
);
324 if (provider
!= provider_contexts_
.end()) {
325 int existing_active_id
= provider
->second
->active_handle_id();
326 if (existing_active_id
!= info
.handle_id
&&
327 existing_active_id
!= kInvalidServiceWorkerHandleId
) {
328 WorkerToProviderMap::iterator associated_provider
=
329 worker_to_provider_
.find(existing_active_id
);
330 DCHECK(associated_provider
!= worker_to_provider_
.end());
331 DCHECK(associated_provider
->second
->provider_id() == provider_id
);
332 worker_to_provider_
.erase(associated_provider
);
334 provider
->second
->OnSetActiveServiceWorker(provider_id
, info
);
335 if (info
.handle_id
!= kInvalidServiceWorkerHandleId
)
336 worker_to_provider_
[info
.handle_id
] = provider
->second
;
339 ScriptClientMap::iterator found
= script_clients_
.find(provider_id
);
340 if (found
!= script_clients_
.end()) {
341 // Populate the .active field with the new worker object.
342 found
->second
->setActive(GetServiceWorker(info
, false));
346 void ServiceWorkerDispatcher::OnSetControllerServiceWorker(
349 const ServiceWorkerObjectInfo
& info
) {
350 ProviderContextMap::iterator provider
= provider_contexts_
.find(provider_id
);
351 if (provider
!= provider_contexts_
.end()) {
352 provider
->second
->OnSetControllerServiceWorker(provider_id
, info
);
353 worker_to_provider_
[info
.handle_id
] = provider
->second
;
356 ScriptClientMap::iterator found
= script_clients_
.find(provider_id
);
357 if (found
!= script_clients_
.end()) {
358 // Populate the .controller field with the new worker object.
359 found
->second
->setController(GetServiceWorker(info
, false));
363 void ServiceWorkerDispatcher::OnPostMessage(
366 const base::string16
& message
,
367 const std::vector
<int>& sent_message_port_ids
,
368 const std::vector
<int>& new_routing_ids
) {
369 // Make sure we're on the main document thread. (That must be the only
370 // thread we get this message)
371 DCHECK(ChildThread::current());
373 ScriptClientMap::iterator found
= script_clients_
.find(provider_id
);
374 if (found
== script_clients_
.end()) {
375 // For now we do no queueing for messages sent to nonexistent / unattached
380 std::vector
<WebMessagePortChannelImpl
*> ports
;
381 if (!sent_message_port_ids
.empty()) {
382 ports
.resize(sent_message_port_ids
.size());
383 for (size_t i
= 0; i
< sent_message_port_ids
.size(); ++i
) {
384 ports
[i
] = new WebMessagePortChannelImpl(
385 new_routing_ids
[i
], sent_message_port_ids
[i
],
386 base::MessageLoopProxy::current());
390 found
->second
->dispatchMessageEvent(message
, ports
);
393 void ServiceWorkerDispatcher::AddServiceWorker(
394 int handle_id
, WebServiceWorkerImpl
* worker
) {
395 DCHECK(!ContainsKey(service_workers_
, handle_id
));
396 service_workers_
[handle_id
] = worker
;
399 void ServiceWorkerDispatcher::RemoveServiceWorker(int handle_id
) {
400 DCHECK(ContainsKey(service_workers_
, handle_id
));
401 service_workers_
.erase(handle_id
);
404 } // namespace content