1 /*
2 * Copyright (C) 2009 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "config.h"
32
33 #if ENABLE(SHARED_WORKERS)
34
35 #include "DefaultSharedWorkerRepository.h"
36
37 #include "ActiveDOMObject.h"
38 #include "CrossThreadTask.h"
39 #include "Document.h"
40 #include "InspectorInstrumentation.h"
41 #include "MessageEvent.h"
42 #include "MessagePort.h"
43 #include "NotImplemented.h"
44 #include "PlatformString.h"
45 #include "ScriptCallStack.h"
46 #include "SecurityOrigin.h"
47 #include "SecurityOriginHash.h"
48 #include "SharedWorker.h"
49 #include "SharedWorkerContext.h"
50 #include "SharedWorkerRepository.h"
51 #include "SharedWorkerThread.h"
52 #include "WorkerLoaderProxy.h"
53 #include "WorkerReportingProxy.h"
54 #include "WorkerScriptLoader.h"
55 #include "WorkerScriptLoaderClient.h"
56 #include <wtf/HashSet.h>
57 #include <wtf/Threading.h>
58
59 namespace WebCore {
60
61 class SharedWorkerProxy : public ThreadSafeRefCounted<SharedWorkerProxy>, public WorkerLoaderProxy, public WorkerReportingProxy {
62 public:
create(const String & name,const KURL & url,PassRefPtr<SecurityOrigin> origin)63 static PassRefPtr<SharedWorkerProxy> create(const String& name, const KURL& url, PassRefPtr<SecurityOrigin> origin) { return adoptRef(new SharedWorkerProxy(name, url, origin)); }
64
setThread(PassRefPtr<SharedWorkerThread> thread)65 void setThread(PassRefPtr<SharedWorkerThread> thread) { m_thread = thread; }
thread()66 SharedWorkerThread* thread() { return m_thread.get(); }
isClosing() const67 bool isClosing() const { return m_closing; }
url() const68 KURL url() const
69 {
70 // Don't use m_url.copy() because it isn't a threadsafe method.
71 return KURL(ParsedURLString, m_url.string().threadsafeCopy());
72 }
73
name() const74 String name() const { return m_name.threadsafeCopy(); }
75 bool matches(const String& name, PassRefPtr<SecurityOrigin> origin, const KURL& urlToMatch) const;
76
77 // WorkerLoaderProxy
78 virtual void postTaskToLoader(PassOwnPtr<ScriptExecutionContext::Task>);
79 virtual void postTaskForModeToWorkerContext(PassOwnPtr<ScriptExecutionContext::Task>, const String&);
80
81 // WorkerReportingProxy
82 virtual void postExceptionToWorkerObject(const String& errorMessage, int lineNumber, const String& sourceURL);
83 virtual void postConsoleMessageToWorkerObject(MessageSource, MessageType, MessageLevel, const String& message, int lineNumber, const String& sourceURL);
84 virtual void workerContextClosed();
85 virtual void workerContextDestroyed();
86
87 // Updates the list of the worker's documents, per section 4.5 of the WebWorkers spec.
88 void addToWorkerDocuments(ScriptExecutionContext*);
89
isInWorkerDocuments(Document * document)90 bool isInWorkerDocuments(Document* document) { return m_workerDocuments.contains(document); }
91
92 // Removes a detached document from the list of worker's documents. May set the closing flag if this is the last document in the list.
93 void documentDetached(Document*);
94
95 private:
96 SharedWorkerProxy(const String& name, const KURL&, PassRefPtr<SecurityOrigin>);
97 void close();
98
99 bool m_closing;
100 String m_name;
101 KURL m_url;
102 // The thread is freed when the proxy is destroyed, so we need to make sure that the proxy stays around until the SharedWorkerContext exits.
103 RefPtr<SharedWorkerThread> m_thread;
104 RefPtr<SecurityOrigin> m_origin;
105 HashSet<Document*> m_workerDocuments;
106 // Ensures exclusive access to the worker documents. Must not grab any other locks (such as the DefaultSharedWorkerRepository lock) while holding this one.
107 Mutex m_workerDocumentsLock;
108 };
109
SharedWorkerProxy(const String & name,const KURL & url,PassRefPtr<SecurityOrigin> origin)110 SharedWorkerProxy::SharedWorkerProxy(const String& name, const KURL& url, PassRefPtr<SecurityOrigin> origin)
111 : m_closing(false)
112 , m_name(name.crossThreadString())
113 , m_url(url.copy())
114 , m_origin(origin)
115 {
116 // We should be the sole owner of the SecurityOrigin, as we will free it on another thread.
117 ASSERT(m_origin->hasOneRef());
118 }
119
matches(const String & name,PassRefPtr<SecurityOrigin> origin,const KURL & urlToMatch) const120 bool SharedWorkerProxy::matches(const String& name, PassRefPtr<SecurityOrigin> origin, const KURL& urlToMatch) const
121 {
122 // If the origins don't match, or the names don't match, then this is not the proxy we are looking for.
123 if (!origin->equal(m_origin.get()))
124 return false;
125
126 // If the names are both empty, compares the URLs instead per the Web Workers spec.
127 if (name.isEmpty() && m_name.isEmpty())
128 return urlToMatch == url();
129
130 return name == m_name;
131 }
132
postTaskToLoader(PassOwnPtr<ScriptExecutionContext::Task> task)133 void SharedWorkerProxy::postTaskToLoader(PassOwnPtr<ScriptExecutionContext::Task> task)
134 {
135 MutexLocker lock(m_workerDocumentsLock);
136
137 if (isClosing())
138 return;
139
140 // If we aren't closing, then we must have at least one document.
141 ASSERT(m_workerDocuments.size());
142
143 // Just pick an arbitrary active document from the HashSet and pass load requests to it.
144 // FIXME: Do we need to deal with the case where the user closes the document mid-load, via a shadow document or some other solution?
145 Document* document = *(m_workerDocuments.begin());
146 document->postTask(task);
147 }
148
postTaskForModeToWorkerContext(PassOwnPtr<ScriptExecutionContext::Task> task,const String & mode)149 void SharedWorkerProxy::postTaskForModeToWorkerContext(PassOwnPtr<ScriptExecutionContext::Task> task, const String& mode)
150 {
151 if (isClosing())
152 return;
153 ASSERT(m_thread);
154 m_thread->runLoop().postTaskForMode(task, mode);
155 }
156
postExceptionTask(ScriptExecutionContext * context,const String & errorMessage,int lineNumber,const String & sourceURL)157 static void postExceptionTask(ScriptExecutionContext* context, const String& errorMessage, int lineNumber, const String& sourceURL)
158 {
159 context->reportException(errorMessage, lineNumber, sourceURL, 0);
160 }
161
postExceptionToWorkerObject(const String & errorMessage,int lineNumber,const String & sourceURL)162 void SharedWorkerProxy::postExceptionToWorkerObject(const String& errorMessage, int lineNumber, const String& sourceURL)
163 {
164 MutexLocker lock(m_workerDocumentsLock);
165 for (HashSet<Document*>::iterator iter = m_workerDocuments.begin(); iter != m_workerDocuments.end(); ++iter)
166 (*iter)->postTask(createCallbackTask(&postExceptionTask, errorMessage, lineNumber, sourceURL));
167 }
168
postConsoleMessageTask(ScriptExecutionContext * document,MessageSource source,MessageType type,MessageLevel level,const String & message,unsigned lineNumber,const String & sourceURL)169 static void postConsoleMessageTask(ScriptExecutionContext* document, MessageSource source, MessageType type, MessageLevel level, const String& message, unsigned lineNumber, const String& sourceURL)
170 {
171 document->addMessage(source, type, level, message, lineNumber, sourceURL, 0);
172 }
173
postConsoleMessageToWorkerObject(MessageSource source,MessageType type,MessageLevel level,const String & message,int lineNumber,const String & sourceURL)174 void SharedWorkerProxy::postConsoleMessageToWorkerObject(MessageSource source, MessageType type, MessageLevel level, const String& message, int lineNumber, const String& sourceURL)
175 {
176 MutexLocker lock(m_workerDocumentsLock);
177 for (HashSet<Document*>::iterator iter = m_workerDocuments.begin(); iter != m_workerDocuments.end(); ++iter)
178 (*iter)->postTask(createCallbackTask(&postConsoleMessageTask, source, type, level, message, lineNumber, sourceURL));
179 }
180
workerContextClosed()181 void SharedWorkerProxy::workerContextClosed()
182 {
183 if (isClosing())
184 return;
185 close();
186 }
187
workerContextDestroyed()188 void SharedWorkerProxy::workerContextDestroyed()
189 {
190 // The proxy may be freed by this call, so do not reference it any further.
191 DefaultSharedWorkerRepository::instance().removeProxy(this);
192 }
193
addToWorkerDocuments(ScriptExecutionContext * context)194 void SharedWorkerProxy::addToWorkerDocuments(ScriptExecutionContext* context)
195 {
196 // Nested workers are not yet supported, so passed-in context should always be a Document.
197 ASSERT(context->isDocument());
198 ASSERT(!isClosing());
199 MutexLocker lock(m_workerDocumentsLock);
200 Document* document = static_cast<Document*>(context);
201 m_workerDocuments.add(document);
202 }
203
documentDetached(Document * document)204 void SharedWorkerProxy::documentDetached(Document* document)
205 {
206 if (isClosing())
207 return;
208 // Remove the document from our set (if it's there) and if that was the last document in the set, mark the proxy as closed.
209 MutexLocker lock(m_workerDocumentsLock);
210 m_workerDocuments.remove(document);
211 if (!m_workerDocuments.size())
212 close();
213 }
214
close()215 void SharedWorkerProxy::close()
216 {
217 ASSERT(!isClosing());
218 m_closing = true;
219 // Stop the worker thread - the proxy will stay around until we get workerThreadExited() notification.
220 if (m_thread)
221 m_thread->stop();
222 }
223
224 class SharedWorkerConnectTask : public ScriptExecutionContext::Task {
225 public:
create(PassOwnPtr<MessagePortChannel> channel)226 static PassOwnPtr<SharedWorkerConnectTask> create(PassOwnPtr<MessagePortChannel> channel)
227 {
228 return adoptPtr(new SharedWorkerConnectTask(channel));
229 }
230
231 private:
SharedWorkerConnectTask(PassOwnPtr<MessagePortChannel> channel)232 SharedWorkerConnectTask(PassOwnPtr<MessagePortChannel> channel)
233 : m_channel(channel)
234 {
235 }
236
performTask(ScriptExecutionContext * scriptContext)237 virtual void performTask(ScriptExecutionContext* scriptContext)
238 {
239 RefPtr<MessagePort> port = MessagePort::create(*scriptContext);
240 port->entangle(m_channel.release());
241 ASSERT(scriptContext->isWorkerContext());
242 WorkerContext* workerContext = static_cast<WorkerContext*>(scriptContext);
243 // Since close() stops the thread event loop, this should not ever get called while closing.
244 ASSERT(!workerContext->isClosing());
245 ASSERT(workerContext->isSharedWorkerContext());
246 workerContext->toSharedWorkerContext()->dispatchEvent(createConnectEvent(port));
247 }
248
249 OwnPtr<MessagePortChannel> m_channel;
250 };
251
252 // Loads the script on behalf of a worker.
253 class SharedWorkerScriptLoader : public RefCounted<SharedWorkerScriptLoader>, private WorkerScriptLoaderClient {
254 public:
255 SharedWorkerScriptLoader(PassRefPtr<SharedWorker>, PassOwnPtr<MessagePortChannel>, PassRefPtr<SharedWorkerProxy>);
256 void load(const KURL&);
257
258 private:
259 // WorkerScriptLoaderClient callback
260 virtual void notifyFinished();
261
262 RefPtr<SharedWorker> m_worker;
263 OwnPtr<MessagePortChannel> m_port;
264 RefPtr<SharedWorkerProxy> m_proxy;
265 OwnPtr<WorkerScriptLoader> m_scriptLoader;
266 };
267
SharedWorkerScriptLoader(PassRefPtr<SharedWorker> worker,PassOwnPtr<MessagePortChannel> port,PassRefPtr<SharedWorkerProxy> proxy)268 SharedWorkerScriptLoader::SharedWorkerScriptLoader(PassRefPtr<SharedWorker> worker, PassOwnPtr<MessagePortChannel> port, PassRefPtr<SharedWorkerProxy> proxy)
269 : m_worker(worker)
270 , m_port(port)
271 , m_proxy(proxy)
272 {
273 }
274
load(const KURL & url)275 void SharedWorkerScriptLoader::load(const KURL& url)
276 {
277 // Mark this object as active for the duration of the load.
278 m_scriptLoader = adoptPtr(new WorkerScriptLoader(ResourceRequestBase::TargetIsSharedWorker));
279 m_scriptLoader->loadAsynchronously(m_worker->scriptExecutionContext(), url, DenyCrossOriginRequests, this);
280
281 // Stay alive (and keep the SharedWorker and JS wrapper alive) until the load finishes.
282 this->ref();
283 m_worker->setPendingActivity(m_worker.get());
284 }
285
notifyFinished()286 void SharedWorkerScriptLoader::notifyFinished()
287 {
288 // FIXME: This method is not guaranteed to be invoked if we are loading from WorkerContext (see comment for WorkerScriptLoaderClient::notifyFinished()).
289 // We need to address this before supporting nested workers.
290
291 // Hand off the just-loaded code to the repository to start up the worker thread.
292 if (m_scriptLoader->failed())
293 m_worker->dispatchEvent(Event::create(eventNames().errorEvent, false, true));
294 else {
295 InspectorInstrumentation::scriptImported(m_worker->scriptExecutionContext(), m_scriptLoader->identifier(), m_scriptLoader->script());
296 DefaultSharedWorkerRepository::instance().workerScriptLoaded(*m_proxy, m_worker->scriptExecutionContext()->userAgent(m_scriptLoader->url()), m_scriptLoader->script(), m_port.release());
297 }
298 m_worker->unsetPendingActivity(m_worker.get());
299 this->deref(); // This frees this object - must be the last action in this function.
300 }
301
instance()302 DefaultSharedWorkerRepository& DefaultSharedWorkerRepository::instance()
303 {
304 AtomicallyInitializedStatic(DefaultSharedWorkerRepository*, instance = new DefaultSharedWorkerRepository);
305 return *instance;
306 }
307
workerScriptLoaded(SharedWorkerProxy & proxy,const String & userAgent,const String & workerScript,PassOwnPtr<MessagePortChannel> port)308 void DefaultSharedWorkerRepository::workerScriptLoaded(SharedWorkerProxy& proxy, const String& userAgent, const String& workerScript, PassOwnPtr<MessagePortChannel> port)
309 {
310 MutexLocker lock(m_lock);
311 if (proxy.isClosing())
312 return;
313
314 // Another loader may have already started up a thread for this proxy - if so, just send a connect to the pre-existing thread.
315 if (!proxy.thread()) {
316 RefPtr<SharedWorkerThread> thread = SharedWorkerThread::create(proxy.name(), proxy.url(), userAgent, workerScript, proxy, proxy);
317 proxy.setThread(thread);
318 thread->start();
319 }
320 proxy.thread()->runLoop().postTask(SharedWorkerConnectTask::create(port));
321 }
322
isAvailable()323 bool SharedWorkerRepository::isAvailable()
324 {
325 // SharedWorkers are enabled on the default WebKit platform.
326 return true;
327 }
328
connect(PassRefPtr<SharedWorker> worker,PassOwnPtr<MessagePortChannel> port,const KURL & url,const String & name,ExceptionCode & ec)329 void SharedWorkerRepository::connect(PassRefPtr<SharedWorker> worker, PassOwnPtr<MessagePortChannel> port, const KURL& url, const String& name, ExceptionCode& ec)
330 {
331 DefaultSharedWorkerRepository::instance().connectToWorker(worker, port, url, name, ec);
332 }
333
documentDetached(Document * document)334 void SharedWorkerRepository::documentDetached(Document* document)
335 {
336 DefaultSharedWorkerRepository::instance().documentDetached(document);
337 }
338
hasSharedWorkers(Document * document)339 bool SharedWorkerRepository::hasSharedWorkers(Document* document)
340 {
341 return DefaultSharedWorkerRepository::instance().hasSharedWorkers(document);
342 }
343
hasSharedWorkers(Document * document)344 bool DefaultSharedWorkerRepository::hasSharedWorkers(Document* document)
345 {
346 MutexLocker lock(m_lock);
347 for (unsigned i = 0; i < m_proxies.size(); i++) {
348 if (m_proxies[i]->isInWorkerDocuments(document))
349 return true;
350 }
351 return false;
352 }
353
removeProxy(SharedWorkerProxy * proxy)354 void DefaultSharedWorkerRepository::removeProxy(SharedWorkerProxy* proxy)
355 {
356 MutexLocker lock(m_lock);
357 for (unsigned i = 0; i < m_proxies.size(); i++) {
358 if (proxy == m_proxies[i].get()) {
359 m_proxies.remove(i);
360 return;
361 }
362 }
363 }
364
documentDetached(Document * document)365 void DefaultSharedWorkerRepository::documentDetached(Document* document)
366 {
367 MutexLocker lock(m_lock);
368 for (unsigned i = 0; i < m_proxies.size(); i++)
369 m_proxies[i]->documentDetached(document);
370 }
371
connectToWorker(PassRefPtr<SharedWorker> worker,PassOwnPtr<MessagePortChannel> port,const KURL & url,const String & name,ExceptionCode & ec)372 void DefaultSharedWorkerRepository::connectToWorker(PassRefPtr<SharedWorker> worker, PassOwnPtr<MessagePortChannel> port, const KURL& url, const String& name, ExceptionCode& ec)
373 {
374 MutexLocker lock(m_lock);
375 ASSERT(worker->scriptExecutionContext()->securityOrigin()->canAccess(SecurityOrigin::create(url).get()));
376 // Fetch a proxy corresponding to this SharedWorker.
377 RefPtr<SharedWorkerProxy> proxy = getProxy(name, url);
378 proxy->addToWorkerDocuments(worker->scriptExecutionContext());
379 if (proxy->url() != url) {
380 // Proxy already existed under alternate URL - return an error.
381 ec = URL_MISMATCH_ERR;
382 return;
383 }
384 // If proxy is already running, just connect to it - otherwise, kick off a loader to load the script.
385 if (proxy->thread())
386 proxy->thread()->runLoop().postTask(SharedWorkerConnectTask::create(port));
387 else {
388 RefPtr<SharedWorkerScriptLoader> loader = adoptRef(new SharedWorkerScriptLoader(worker, port, proxy.release()));
389 loader->load(url);
390 }
391 }
392
393 // Creates a new SharedWorkerProxy or returns an existing one from the repository. Must only be called while the repository mutex is held.
getProxy(const String & name,const KURL & url)394 PassRefPtr<SharedWorkerProxy> DefaultSharedWorkerRepository::getProxy(const String& name, const KURL& url)
395 {
396 // Look for an existing worker, and create one if it doesn't exist.
397 // Items in the cache are freed on another thread, so do a threadsafe copy of the URL before creating the origin,
398 // to make sure no references to external strings linger.
399 RefPtr<SecurityOrigin> origin = SecurityOrigin::create(KURL(ParsedURLString, url.string().threadsafeCopy()));
400 for (unsigned i = 0; i < m_proxies.size(); i++) {
401 if (!m_proxies[i]->isClosing() && m_proxies[i]->matches(name, origin, url))
402 return m_proxies[i];
403 }
404 // Proxy is not in the repository currently - create a new one.
405 RefPtr<SharedWorkerProxy> proxy = SharedWorkerProxy::create(name, url, origin.release());
406 m_proxies.append(proxy);
407 return proxy.release();
408 }
409
DefaultSharedWorkerRepository()410 DefaultSharedWorkerRepository::DefaultSharedWorkerRepository()
411 {
412 }
413
~DefaultSharedWorkerRepository()414 DefaultSharedWorkerRepository::~DefaultSharedWorkerRepository()
415 {
416 }
417
418 } // namespace WebCore
419
420 #endif // ENABLE(SHARED_WORKERS)
421