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.
4
5 #include "content/child/service_worker/service_worker_dispatcher.h"
6
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"
20
21 using blink::WebServiceWorkerError;
22 using blink::WebServiceWorkerProvider;
23 using base::ThreadLocalPointer;
24
25 namespace content {
26
27 namespace {
28
29 base::LazyInstance<ThreadLocalPointer<ServiceWorkerDispatcher> >::Leaky
30 g_dispatcher_tls = LAZY_INSTANCE_INITIALIZER;
31
32 ServiceWorkerDispatcher* const kHasBeenDeleted =
33 reinterpret_cast<ServiceWorkerDispatcher*>(0x1);
34
CurrentWorkerId()35 int CurrentWorkerId() {
36 return WorkerTaskRunner::Instance()->CurrentWorkerId();
37 }
38
39 } // namespace
40
ServiceWorkerDispatcher(ThreadSafeSender * thread_safe_sender)41 ServiceWorkerDispatcher::ServiceWorkerDispatcher(
42 ThreadSafeSender* thread_safe_sender)
43 : thread_safe_sender_(thread_safe_sender) {
44 g_dispatcher_tls.Pointer()->Set(this);
45 }
46
~ServiceWorkerDispatcher()47 ServiceWorkerDispatcher::~ServiceWorkerDispatcher() {
48 g_dispatcher_tls.Pointer()->Set(kHasBeenDeleted);
49 }
50
OnMessageReceived(const IPC::Message & msg)51 void ServiceWorkerDispatcher::OnMessageReceived(const IPC::Message& msg) {
52 bool handled = true;
53 IPC_BEGIN_MESSAGE_MAP(ServiceWorkerDispatcher, msg)
54 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_ServiceWorkerRegistered, OnRegistered)
55 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_ServiceWorkerUnregistered,
56 OnUnregistered)
57 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_ServiceWorkerRegistrationError,
58 OnRegistrationError)
59 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_ServiceWorkerStateChanged,
60 OnServiceWorkerStateChanged)
61 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_SetWaitingServiceWorker,
62 OnSetWaitingServiceWorker)
63 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_SetCurrentServiceWorker,
64 OnSetCurrentServiceWorker)
65 IPC_MESSAGE_HANDLER(ServiceWorkerMsg_MessageToDocument,
66 OnPostMessage)
67 IPC_MESSAGE_UNHANDLED(handled = false)
68 IPC_END_MESSAGE_MAP()
69 DCHECK(handled) << "Unhandled message:" << msg.type();
70 }
71
Send(IPC::Message * msg)72 bool ServiceWorkerDispatcher::Send(IPC::Message* msg) {
73 return thread_safe_sender_->Send(msg);
74 }
75
RegisterServiceWorker(int provider_id,const GURL & pattern,const GURL & script_url,WebServiceWorkerProvider::WebServiceWorkerCallbacks * callbacks)76 void ServiceWorkerDispatcher::RegisterServiceWorker(
77 int provider_id,
78 const GURL& pattern,
79 const GURL& script_url,
80 WebServiceWorkerProvider::WebServiceWorkerCallbacks* callbacks) {
81 DCHECK(callbacks);
82
83 if (pattern.possibly_invalid_spec().size() > GetMaxURLChars() ||
84 script_url.possibly_invalid_spec().size() > GetMaxURLChars()) {
85 scoped_ptr<WebServiceWorkerProvider::WebServiceWorkerCallbacks>
86 owned_callbacks(callbacks);
87 scoped_ptr<WebServiceWorkerError> error(new WebServiceWorkerError(
88 WebServiceWorkerError::ErrorTypeSecurity, "URL too long"));
89 callbacks->onError(error.release());
90 return;
91 }
92
93 int request_id = pending_callbacks_.Add(callbacks);
94 thread_safe_sender_->Send(new ServiceWorkerHostMsg_RegisterServiceWorker(
95 CurrentWorkerId(), request_id, provider_id, pattern, script_url));
96 }
97
UnregisterServiceWorker(int provider_id,const GURL & pattern,WebServiceWorkerProvider::WebServiceWorkerCallbacks * callbacks)98 void ServiceWorkerDispatcher::UnregisterServiceWorker(
99 int provider_id,
100 const GURL& pattern,
101 WebServiceWorkerProvider::WebServiceWorkerCallbacks* callbacks) {
102 DCHECK(callbacks);
103
104 if (pattern.possibly_invalid_spec().size() > GetMaxURLChars()) {
105 scoped_ptr<WebServiceWorkerProvider::WebServiceWorkerCallbacks>
106 owned_callbacks(callbacks);
107 scoped_ptr<WebServiceWorkerError> error(new WebServiceWorkerError(
108 WebServiceWorkerError::ErrorTypeSecurity, "URL too long"));
109 callbacks->onError(error.release());
110 return;
111 }
112
113 int request_id = pending_callbacks_.Add(callbacks);
114 thread_safe_sender_->Send(new ServiceWorkerHostMsg_UnregisterServiceWorker(
115 CurrentWorkerId(), request_id, provider_id, pattern));
116 }
117
AddProviderContext(ServiceWorkerProviderContext * provider_context)118 void ServiceWorkerDispatcher::AddProviderContext(
119 ServiceWorkerProviderContext* provider_context) {
120 DCHECK(provider_context);
121 int provider_id = provider_context->provider_id();
122 DCHECK(!ContainsKey(provider_contexts_, provider_id));
123 provider_contexts_[provider_id] = provider_context;
124 }
125
RemoveProviderContext(ServiceWorkerProviderContext * provider_context)126 void ServiceWorkerDispatcher::RemoveProviderContext(
127 ServiceWorkerProviderContext* provider_context) {
128 DCHECK(provider_context);
129 DCHECK(ContainsKey(provider_contexts_, provider_context->provider_id()));
130 provider_contexts_.erase(provider_context->provider_id());
131 worker_to_provider_.erase(provider_context->waiting_handle_id());
132 worker_to_provider_.erase(provider_context->current_handle_id());
133 }
134
AddScriptClient(int provider_id,blink::WebServiceWorkerProviderClient * client)135 void ServiceWorkerDispatcher::AddScriptClient(
136 int provider_id,
137 blink::WebServiceWorkerProviderClient* client) {
138 DCHECK(client);
139 DCHECK(!ContainsKey(script_clients_, provider_id));
140 script_clients_[provider_id] = client;
141 }
142
RemoveScriptClient(int provider_id)143 void ServiceWorkerDispatcher::RemoveScriptClient(int provider_id) {
144 // This could be possibly called multiple times to ensure termination.
145 if (ContainsKey(script_clients_, provider_id))
146 script_clients_.erase(provider_id);
147 }
148
149 ServiceWorkerDispatcher*
GetOrCreateThreadSpecificInstance(ThreadSafeSender * thread_safe_sender)150 ServiceWorkerDispatcher::GetOrCreateThreadSpecificInstance(
151 ThreadSafeSender* thread_safe_sender) {
152 if (g_dispatcher_tls.Pointer()->Get() == kHasBeenDeleted) {
153 NOTREACHED() << "Re-instantiating TLS ServiceWorkerDispatcher.";
154 g_dispatcher_tls.Pointer()->Set(NULL);
155 }
156 if (g_dispatcher_tls.Pointer()->Get())
157 return g_dispatcher_tls.Pointer()->Get();
158
159 ServiceWorkerDispatcher* dispatcher =
160 new ServiceWorkerDispatcher(thread_safe_sender);
161 if (WorkerTaskRunner::Instance()->CurrentWorkerId())
162 WorkerTaskRunner::Instance()->AddStopObserver(dispatcher);
163 return dispatcher;
164 }
165
GetThreadSpecificInstance()166 ServiceWorkerDispatcher* ServiceWorkerDispatcher::GetThreadSpecificInstance() {
167 if (g_dispatcher_tls.Pointer()->Get() == kHasBeenDeleted)
168 return NULL;
169 return g_dispatcher_tls.Pointer()->Get();
170 }
171
OnWorkerRunLoopStopped()172 void ServiceWorkerDispatcher::OnWorkerRunLoopStopped() {
173 delete this;
174 }
175
GetServiceWorker(const ServiceWorkerObjectInfo & info,bool adopt_handle)176 WebServiceWorkerImpl* ServiceWorkerDispatcher::GetServiceWorker(
177 const ServiceWorkerObjectInfo& info,
178 bool adopt_handle) {
179 if (info.handle_id == kInvalidServiceWorkerHandleId)
180 return NULL;
181
182 WorkerObjectMap::iterator existing_worker =
183 service_workers_.find(info.handle_id);
184
185 if (existing_worker != service_workers_.end()) {
186 if (adopt_handle) {
187 // We are instructed to adopt a handle but we already have one, so
188 // adopt and destroy a handle ref.
189 ServiceWorkerHandleReference::Adopt(info, thread_safe_sender_);
190 }
191 return existing_worker->second;
192 }
193
194 scoped_ptr<ServiceWorkerHandleReference> handle_ref =
195 adopt_handle
196 ? ServiceWorkerHandleReference::Adopt(info, thread_safe_sender_)
197 : ServiceWorkerHandleReference::Create(info, thread_safe_sender_);
198 // WebServiceWorkerImpl constructor calls AddServiceWorker.
199 return new WebServiceWorkerImpl(handle_ref.Pass(), thread_safe_sender_);
200 }
201
OnRegistered(int thread_id,int request_id,const ServiceWorkerObjectInfo & info)202 void ServiceWorkerDispatcher::OnRegistered(
203 int thread_id,
204 int request_id,
205 const ServiceWorkerObjectInfo& info) {
206 WebServiceWorkerProvider::WebServiceWorkerCallbacks* callbacks =
207 pending_callbacks_.Lookup(request_id);
208 DCHECK(callbacks);
209 if (!callbacks)
210 return;
211
212 callbacks->onSuccess(GetServiceWorker(info, true));
213 pending_callbacks_.Remove(request_id);
214 }
215
OnUnregistered(int thread_id,int request_id)216 void ServiceWorkerDispatcher::OnUnregistered(
217 int thread_id,
218 int request_id) {
219 WebServiceWorkerProvider::WebServiceWorkerCallbacks* callbacks =
220 pending_callbacks_.Lookup(request_id);
221 DCHECK(callbacks);
222 if (!callbacks)
223 return;
224
225 callbacks->onSuccess(NULL);
226 pending_callbacks_.Remove(request_id);
227 }
228
OnRegistrationError(int thread_id,int request_id,WebServiceWorkerError::ErrorType error_type,const base::string16 & message)229 void ServiceWorkerDispatcher::OnRegistrationError(
230 int thread_id,
231 int request_id,
232 WebServiceWorkerError::ErrorType error_type,
233 const base::string16& message) {
234 WebServiceWorkerProvider::WebServiceWorkerCallbacks* callbacks =
235 pending_callbacks_.Lookup(request_id);
236 DCHECK(callbacks);
237 if (!callbacks)
238 return;
239
240 scoped_ptr<WebServiceWorkerError> error(
241 new WebServiceWorkerError(error_type, message));
242 callbacks->onError(error.release());
243 pending_callbacks_.Remove(request_id);
244 }
245
OnServiceWorkerStateChanged(int thread_id,int handle_id,blink::WebServiceWorkerState state)246 void ServiceWorkerDispatcher::OnServiceWorkerStateChanged(
247 int thread_id,
248 int handle_id,
249 blink::WebServiceWorkerState state) {
250 WorkerObjectMap::iterator worker = service_workers_.find(handle_id);
251 if (worker != service_workers_.end())
252 worker->second->OnStateChanged(state);
253
254 WorkerToProviderMap::iterator provider = worker_to_provider_.find(handle_id);
255 if (provider != worker_to_provider_.end())
256 provider->second->OnServiceWorkerStateChanged(handle_id, state);
257 }
258
OnSetWaitingServiceWorker(int thread_id,int provider_id,const ServiceWorkerObjectInfo & info)259 void ServiceWorkerDispatcher::OnSetWaitingServiceWorker(
260 int thread_id,
261 int provider_id,
262 const ServiceWorkerObjectInfo& info) {
263 ProviderContextMap::iterator provider = provider_contexts_.find(provider_id);
264 if (provider != provider_contexts_.end()) {
265 int existing_waiting_id = provider->second->waiting_handle_id();
266 if (existing_waiting_id != info.handle_id &&
267 existing_waiting_id != kInvalidServiceWorkerHandleId) {
268 WorkerToProviderMap::iterator associated_provider =
269 worker_to_provider_.find(existing_waiting_id);
270 DCHECK(associated_provider != worker_to_provider_.end());
271 DCHECK(associated_provider->second->provider_id() == provider_id);
272 worker_to_provider_.erase(associated_provider);
273 }
274 provider->second->OnSetWaitingServiceWorker(provider_id, info);
275 if (info.handle_id != kInvalidServiceWorkerHandleId)
276 worker_to_provider_[info.handle_id] = provider->second;
277 }
278
279 ScriptClientMap::iterator found = script_clients_.find(provider_id);
280 if (found != script_clients_.end()) {
281 // Populate the .waiting field with the new worker object.
282 found->second->setWaiting(GetServiceWorker(info, false));
283 }
284 }
285
OnSetCurrentServiceWorker(int thread_id,int provider_id,const ServiceWorkerObjectInfo & info)286 void ServiceWorkerDispatcher::OnSetCurrentServiceWorker(
287 int thread_id,
288 int provider_id,
289 const ServiceWorkerObjectInfo& info) {
290 ProviderContextMap::iterator provider = provider_contexts_.find(provider_id);
291 if (provider != provider_contexts_.end()) {
292 provider->second->OnSetCurrentServiceWorker(provider_id, info);
293 worker_to_provider_[info.handle_id] = provider->second;
294 }
295
296 ScriptClientMap::iterator found = script_clients_.find(provider_id);
297 if (found != script_clients_.end()) {
298 // Populate the .controller field with the new worker object.
299 found->second->setController(GetServiceWorker(info, false));
300 }
301 }
302
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)303 void ServiceWorkerDispatcher::OnPostMessage(
304 int thread_id,
305 int provider_id,
306 const base::string16& message,
307 const std::vector<int>& sent_message_port_ids,
308 const std::vector<int>& new_routing_ids) {
309 // Make sure we're on the main document thread. (That must be the only
310 // thread we get this message)
311 DCHECK(ChildThread::current());
312
313 ScriptClientMap::iterator found = script_clients_.find(provider_id);
314 if (found == script_clients_.end()) {
315 // For now we do no queueing for messages sent to nonexistent / unattached
316 // client.
317 return;
318 }
319
320 std::vector<WebMessagePortChannelImpl*> ports;
321 if (!sent_message_port_ids.empty()) {
322 ports.resize(sent_message_port_ids.size());
323 for (size_t i = 0; i < sent_message_port_ids.size(); ++i) {
324 ports[i] = new WebMessagePortChannelImpl(
325 new_routing_ids[i], sent_message_port_ids[i],
326 base::MessageLoopProxy::current());
327 }
328 }
329
330 found->second->dispatchMessageEvent(message, ports);
331 }
332
AddServiceWorker(int handle_id,WebServiceWorkerImpl * worker)333 void ServiceWorkerDispatcher::AddServiceWorker(
334 int handle_id, WebServiceWorkerImpl* worker) {
335 DCHECK(!ContainsKey(service_workers_, handle_id));
336 service_workers_[handle_id] = worker;
337 }
338
RemoveServiceWorker(int handle_id)339 void ServiceWorkerDispatcher::RemoveServiceWorker(int handle_id) {
340 DCHECK(ContainsKey(service_workers_, handle_id));
341 service_workers_.erase(handle_id);
342 }
343
344 } // namespace content
345