1// Copyright 2014 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/browser/shared_worker/shared_worker_host.h"
6
7#include "base/metrics/histogram.h"
8#include "content/browser/devtools/embedded_worker_devtools_manager.h"
9#include "content/browser/frame_host/render_frame_host_delegate.h"
10#include "content/browser/frame_host/render_frame_host_impl.h"
11#include "content/browser/message_port_service.h"
12#include "content/browser/shared_worker/shared_worker_instance.h"
13#include "content/browser/shared_worker/shared_worker_message_filter.h"
14#include "content/browser/shared_worker/shared_worker_service_impl.h"
15#include "content/browser/shared_worker/worker_document_set.h"
16#include "content/common/view_messages.h"
17#include "content/common/worker_messages.h"
18#include "content/public/browser/browser_thread.h"
19#include "content/public/browser/content_browser_client.h"
20#include "content/public/browser/render_process_host.h"
21#include "content/public/common/content_client.h"
22
23namespace content {
24namespace {
25
26// Notifies RenderViewHost that one or more worker objects crashed.
27void WorkerCrashCallback(int render_process_unique_id, int render_frame_id) {
28  RenderFrameHostImpl* host =
29      RenderFrameHostImpl::FromID(render_process_unique_id, render_frame_id);
30  if (host)
31    host->delegate()->WorkerCrashed(host);
32}
33
34void NotifyWorkerReadyForInspection(int worker_process_id,
35                                    int worker_route_id) {
36  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
37    BrowserThread::PostTask(BrowserThread::UI,
38                            FROM_HERE,
39                            base::Bind(NotifyWorkerReadyForInspection,
40                                       worker_process_id,
41                                       worker_route_id));
42    return;
43  }
44  EmbeddedWorkerDevToolsManager::GetInstance()->WorkerReadyForInspection(
45      worker_process_id, worker_route_id);
46}
47
48void NotifyWorkerContextStarted(int worker_process_id, int worker_route_id) {
49  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
50    BrowserThread::PostTask(
51        BrowserThread::UI,
52        FROM_HERE,
53        base::Bind(
54            NotifyWorkerContextStarted, worker_process_id, worker_route_id));
55    return;
56  }
57  EmbeddedWorkerDevToolsManager::GetInstance()->WorkerContextStarted(
58      worker_process_id, worker_route_id);
59}
60
61void NotifyWorkerDestroyed(int worker_process_id, int worker_route_id) {
62  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
63    BrowserThread::PostTask(
64        BrowserThread::UI,
65        FROM_HERE,
66        base::Bind(NotifyWorkerDestroyed, worker_process_id, worker_route_id));
67    return;
68  }
69  EmbeddedWorkerDevToolsManager::GetInstance()->WorkerDestroyed(
70      worker_process_id, worker_route_id);
71}
72
73}  // namespace
74
75SharedWorkerHost::SharedWorkerHost(SharedWorkerInstance* instance,
76                                   SharedWorkerMessageFilter* filter,
77                                   int worker_route_id)
78    : instance_(instance),
79      worker_document_set_(new WorkerDocumentSet()),
80      container_render_filter_(filter),
81      worker_process_id_(filter->render_process_id()),
82      worker_route_id_(worker_route_id),
83      load_failed_(false),
84      closed_(false),
85      creation_time_(base::TimeTicks::Now()),
86      weak_factory_(this) {
87  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
88}
89
90SharedWorkerHost::~SharedWorkerHost() {
91  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
92  UMA_HISTOGRAM_LONG_TIMES("SharedWorker.TimeToDeleted",
93                           base::TimeTicks::Now() - creation_time_);
94  // If we crashed, tell the RenderViewHosts.
95  if (instance_ && !load_failed_) {
96    const WorkerDocumentSet::DocumentInfoSet& parents =
97        worker_document_set_->documents();
98    for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter =
99             parents.begin();
100         parent_iter != parents.end();
101         ++parent_iter) {
102      BrowserThread::PostTask(BrowserThread::UI,
103                              FROM_HERE,
104                              base::Bind(&WorkerCrashCallback,
105                                         parent_iter->render_process_id(),
106                                         parent_iter->render_frame_id()));
107    }
108  }
109  if (!closed_)
110    NotifyWorkerDestroyed(worker_process_id_, worker_route_id_);
111  SharedWorkerServiceImpl::GetInstance()->NotifyWorkerDestroyed(
112      worker_process_id_, worker_route_id_);
113}
114
115bool SharedWorkerHost::Send(IPC::Message* message) {
116  if (!container_render_filter_) {
117    delete message;
118    return false;
119  }
120  return container_render_filter_->Send(message);
121}
122
123void SharedWorkerHost::Start(bool pause_on_start) {
124  WorkerProcessMsg_CreateWorker_Params params;
125  params.url = instance_->url();
126  params.name = instance_->name();
127  params.content_security_policy = instance_->content_security_policy();
128  params.security_policy_type = instance_->security_policy_type();
129  params.pause_on_start = pause_on_start;
130  params.route_id = worker_route_id_;
131  Send(new WorkerProcessMsg_CreateWorker(params));
132
133  for (FilterList::const_iterator i = filters_.begin(); i != filters_.end();
134       ++i) {
135    i->filter()->Send(new ViewMsg_WorkerCreated(i->route_id()));
136  }
137}
138
139bool SharedWorkerHost::FilterMessage(const IPC::Message& message,
140                                     SharedWorkerMessageFilter* filter) {
141  if (!instance_)
142    return false;
143
144  if (!closed_ && HasFilter(filter, message.routing_id())) {
145    RelayMessage(message, filter);
146    return true;
147  }
148
149  return false;
150}
151
152void SharedWorkerHost::FilterShutdown(SharedWorkerMessageFilter* filter) {
153  if (!instance_)
154    return;
155  RemoveFilters(filter);
156  worker_document_set_->RemoveAll(filter);
157  if (worker_document_set_->IsEmpty()) {
158    // This worker has no more associated documents - shut it down.
159    Send(new WorkerMsg_TerminateWorkerContext(worker_route_id_));
160  }
161}
162
163void SharedWorkerHost::DocumentDetached(SharedWorkerMessageFilter* filter,
164                                        unsigned long long document_id) {
165  if (!instance_)
166    return;
167  // Walk all instances and remove the document from their document set.
168  worker_document_set_->Remove(filter, document_id);
169  if (worker_document_set_->IsEmpty()) {
170    // This worker has no more associated documents - shut it down.
171    Send(new WorkerMsg_TerminateWorkerContext(worker_route_id_));
172  }
173}
174
175void SharedWorkerHost::WorkerContextClosed() {
176  if (!instance_)
177    return;
178  // Set the closed flag - this will stop any further messages from
179  // being sent to the worker (messages can still be sent from the worker,
180  // for exception reporting, etc).
181  closed_ = true;
182  NotifyWorkerDestroyed(worker_process_id_, worker_route_id_);
183}
184
185void SharedWorkerHost::WorkerContextDestroyed() {
186  if (!instance_)
187    return;
188  instance_.reset();
189  worker_document_set_ = NULL;
190}
191
192void SharedWorkerHost::WorkerReadyForInspection() {
193  NotifyWorkerReadyForInspection(worker_process_id_, worker_route_id_);
194}
195
196void SharedWorkerHost::WorkerScriptLoaded() {
197  UMA_HISTOGRAM_TIMES("SharedWorker.TimeToScriptLoaded",
198                      base::TimeTicks::Now() - creation_time_);
199  NotifyWorkerContextStarted(worker_process_id_, worker_route_id_);
200}
201
202void SharedWorkerHost::WorkerScriptLoadFailed() {
203  UMA_HISTOGRAM_TIMES("SharedWorker.TimeToScriptLoadFailed",
204                      base::TimeTicks::Now() - creation_time_);
205  if (!instance_)
206    return;
207  load_failed_ = true;
208  for (FilterList::const_iterator i = filters_.begin(); i != filters_.end();
209       ++i) {
210    i->filter()->Send(new ViewMsg_WorkerScriptLoadFailed(i->route_id()));
211  }
212}
213
214void SharedWorkerHost::WorkerConnected(int message_port_id) {
215  if (!instance_)
216    return;
217  for (FilterList::const_iterator i = filters_.begin(); i != filters_.end();
218       ++i) {
219    if (i->message_port_id() != message_port_id)
220      continue;
221    i->filter()->Send(new ViewMsg_WorkerConnected(i->route_id()));
222    return;
223  }
224}
225
226void SharedWorkerHost::AllowDatabase(const GURL& url,
227                                     const base::string16& name,
228                                     const base::string16& display_name,
229                                     unsigned long estimated_size,
230                                     bool* result) {
231  if (!instance_)
232    return;
233  *result = GetContentClient()->browser()->AllowWorkerDatabase(
234      url,
235      name,
236      display_name,
237      estimated_size,
238      instance_->resource_context(),
239      GetRenderFrameIDsForWorker());
240}
241
242void SharedWorkerHost::AllowFileSystem(const GURL& url,
243                                       scoped_ptr<IPC::Message> reply_msg) {
244  if (!instance_)
245    return;
246  GetContentClient()->browser()->AllowWorkerFileSystem(
247      url,
248      instance_->resource_context(),
249      GetRenderFrameIDsForWorker(),
250      base::Bind(&SharedWorkerHost::AllowFileSystemResponse,
251                 weak_factory_.GetWeakPtr(),
252                 base::Passed(&reply_msg)));
253}
254
255void SharedWorkerHost::AllowFileSystemResponse(
256    scoped_ptr<IPC::Message> reply_msg,
257    bool allowed) {
258  WorkerProcessHostMsg_RequestFileSystemAccessSync::WriteReplyParams(
259      reply_msg.get(),
260      allowed);
261  Send(reply_msg.release());
262}
263
264void SharedWorkerHost::AllowIndexedDB(const GURL& url,
265                                      const base::string16& name,
266                                      bool* result) {
267  if (!instance_)
268    return;
269  *result = GetContentClient()->browser()->AllowWorkerIndexedDB(
270      url, name, instance_->resource_context(), GetRenderFrameIDsForWorker());
271}
272
273void SharedWorkerHost::RelayMessage(
274    const IPC::Message& message,
275    SharedWorkerMessageFilter* incoming_filter) {
276  if (!instance_)
277    return;
278  if (message.type() == WorkerMsg_Connect::ID) {
279    // Crack the SharedWorker Connect message to setup routing for the port.
280    WorkerMsg_Connect::Param param;
281    if (!WorkerMsg_Connect::Read(&message, &param))
282      return;
283    int sent_message_port_id = param.a;
284    int new_routing_id = param.b;
285
286    DCHECK(container_render_filter_);
287    new_routing_id = container_render_filter_->GetNextRoutingID();
288    MessagePortService::GetInstance()->UpdateMessagePort(
289        sent_message_port_id,
290        container_render_filter_->message_port_message_filter(),
291        new_routing_id);
292    SetMessagePortID(
293        incoming_filter, message.routing_id(), sent_message_port_id);
294    // Resend the message with the new routing id.
295    Send(new WorkerMsg_Connect(
296        worker_route_id_, sent_message_port_id, new_routing_id));
297
298    // Send any queued messages for the sent port.
299    MessagePortService::GetInstance()->SendQueuedMessagesIfPossible(
300        sent_message_port_id);
301  } else {
302    IPC::Message* new_message = new IPC::Message(message);
303    new_message->set_routing_id(worker_route_id_);
304    Send(new_message);
305    return;
306  }
307}
308
309void SharedWorkerHost::TerminateWorker() {
310  Send(new WorkerMsg_TerminateWorkerContext(worker_route_id_));
311}
312
313std::vector<std::pair<int, int> >
314SharedWorkerHost::GetRenderFrameIDsForWorker() {
315  std::vector<std::pair<int, int> > result;
316  if (!instance_)
317    return result;
318  const WorkerDocumentSet::DocumentInfoSet& documents =
319      worker_document_set_->documents();
320  for (WorkerDocumentSet::DocumentInfoSet::const_iterator doc =
321           documents.begin();
322       doc != documents.end();
323       ++doc) {
324    result.push_back(
325        std::make_pair(doc->render_process_id(), doc->render_frame_id()));
326  }
327  return result;
328}
329
330void SharedWorkerHost::AddFilter(SharedWorkerMessageFilter* filter,
331                                 int route_id) {
332  CHECK(filter);
333  if (!HasFilter(filter, route_id)) {
334    FilterInfo info(filter, route_id);
335    filters_.push_back(info);
336  }
337}
338
339void SharedWorkerHost::RemoveFilters(SharedWorkerMessageFilter* filter) {
340  for (FilterList::iterator i = filters_.begin(); i != filters_.end();) {
341    if (i->filter() == filter)
342      i = filters_.erase(i);
343    else
344      ++i;
345  }
346}
347
348bool SharedWorkerHost::HasFilter(SharedWorkerMessageFilter* filter,
349                                 int route_id) const {
350  for (FilterList::const_iterator i = filters_.begin(); i != filters_.end();
351       ++i) {
352    if (i->filter() == filter && i->route_id() == route_id)
353      return true;
354  }
355  return false;
356}
357
358void SharedWorkerHost::SetMessagePortID(SharedWorkerMessageFilter* filter,
359                                        int route_id,
360                                        int message_port_id) {
361  for (FilterList::iterator i = filters_.begin(); i != filters_.end(); ++i) {
362    if (i->filter() == filter && i->route_id() == route_id) {
363      i->set_message_port_id(message_port_id);
364      return;
365    }
366  }
367}
368
369}  // namespace content
370