initial import
[vuplus_webkit] / Source / WebCore / workers / DefaultSharedWorkerRepository.cpp
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:
63     static PassRefPtr<SharedWorkerProxy> create(const String& name, const KURL& url, PassRefPtr<SecurityOrigin> origin) { return adoptRef(new SharedWorkerProxy(name, url, origin)); }
64
65     void setThread(PassRefPtr<SharedWorkerThread> thread) { m_thread = thread; }
66     SharedWorkerThread* thread() { return m_thread.get(); }
67     bool isClosing() const { return m_closing; }
68     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
74     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 #if ENABLE(INSPECTOR)
85     virtual void postMessageToPageInspector(const String&);
86 #endif
87     virtual void workerContextClosed();
88     virtual void workerContextDestroyed();
89
90     // Updates the list of the worker's documents, per section 4.5 of the WebWorkers spec.
91     void addToWorkerDocuments(ScriptExecutionContext*);
92
93     bool isInWorkerDocuments(Document* document) { return m_workerDocuments.contains(document); }
94
95     // 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.
96     void documentDetached(Document*);
97
98 private:
99     SharedWorkerProxy(const String& name, const KURL&, PassRefPtr<SecurityOrigin>);
100     void close();
101
102     bool m_closing;
103     String m_name;
104     KURL m_url;
105     // The thread is freed when the proxy is destroyed, so we need to make sure that the proxy stays around until the SharedWorkerContext exits.
106     RefPtr<SharedWorkerThread> m_thread;
107     RefPtr<SecurityOrigin> m_origin;
108     HashSet<Document*> m_workerDocuments;
109     // Ensures exclusive access to the worker documents. Must not grab any other locks (such as the DefaultSharedWorkerRepository lock) while holding this one.
110     Mutex m_workerDocumentsLock;
111 };
112
113 SharedWorkerProxy::SharedWorkerProxy(const String& name, const KURL& url, PassRefPtr<SecurityOrigin> origin)
114     : m_closing(false)
115     , m_name(name.crossThreadString())
116     , m_url(url.copy())
117     , m_origin(origin)
118 {
119     // We should be the sole owner of the SecurityOrigin, as we will free it on another thread.
120     ASSERT(m_origin->hasOneRef());
121 }
122
123 bool SharedWorkerProxy::matches(const String& name, PassRefPtr<SecurityOrigin> origin, const KURL& urlToMatch) const
124 {
125     // If the origins don't match, or the names don't match, then this is not the proxy we are looking for.
126     if (!origin->equal(m_origin.get()))
127         return false;
128
129     // If the names are both empty, compares the URLs instead per the Web Workers spec.
130     if (name.isEmpty() && m_name.isEmpty())
131         return urlToMatch == url();
132
133     return name == m_name;
134 }
135
136 void SharedWorkerProxy::postTaskToLoader(PassOwnPtr<ScriptExecutionContext::Task> task)
137 {
138     MutexLocker lock(m_workerDocumentsLock);
139
140     if (isClosing())
141         return;
142
143     // If we aren't closing, then we must have at least one document.
144     ASSERT(m_workerDocuments.size());
145
146     // Just pick an arbitrary active document from the HashSet and pass load requests to it.
147     // 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?
148     Document* document = *(m_workerDocuments.begin());
149     document->postTask(task);
150 }
151
152 void SharedWorkerProxy::postTaskForModeToWorkerContext(PassOwnPtr<ScriptExecutionContext::Task> task, const String& mode)
153 {
154     if (isClosing())
155         return;
156     ASSERT(m_thread);
157     m_thread->runLoop().postTaskForMode(task, mode);
158 }
159
160 static void postExceptionTask(ScriptExecutionContext* context, const String& errorMessage, int lineNumber, const String& sourceURL)
161 {
162     context->reportException(errorMessage, lineNumber, sourceURL, 0);
163 }
164
165 void SharedWorkerProxy::postExceptionToWorkerObject(const String& errorMessage, int lineNumber, const String& sourceURL)
166 {
167     MutexLocker lock(m_workerDocumentsLock);
168     for (HashSet<Document*>::iterator iter = m_workerDocuments.begin(); iter != m_workerDocuments.end(); ++iter)
169         (*iter)->postTask(createCallbackTask(&postExceptionTask, errorMessage, lineNumber, sourceURL));
170 }
171
172 static void postConsoleMessageTask(ScriptExecutionContext* document, MessageSource source, MessageType type, MessageLevel level, const String& message, unsigned lineNumber, const String& sourceURL)
173 {
174     document->addMessage(source, type, level, message, lineNumber, sourceURL, 0);
175 }
176
177 void SharedWorkerProxy::postConsoleMessageToWorkerObject(MessageSource source, MessageType type, MessageLevel level, const String& message, int lineNumber, const String& sourceURL)
178 {
179     MutexLocker lock(m_workerDocumentsLock);
180     for (HashSet<Document*>::iterator iter = m_workerDocuments.begin(); iter != m_workerDocuments.end(); ++iter)
181         (*iter)->postTask(createCallbackTask(&postConsoleMessageTask, source, type, level, message, lineNumber, sourceURL));
182 }
183
184 #if ENABLE(INSPECTOR)
185 void SharedWorkerProxy::postMessageToPageInspector(const String&)
186 {
187 }
188 #endif
189
190 void SharedWorkerProxy::workerContextClosed()
191 {
192     if (isClosing())
193         return;
194     close();
195 }
196
197 void SharedWorkerProxy::workerContextDestroyed()
198 {
199     // The proxy may be freed by this call, so do not reference it any further.
200     DefaultSharedWorkerRepository::instance().removeProxy(this);
201 }
202
203 void SharedWorkerProxy::addToWorkerDocuments(ScriptExecutionContext* context)
204 {
205     // Nested workers are not yet supported, so passed-in context should always be a Document.
206     ASSERT(context->isDocument());
207     ASSERT(!isClosing());
208     MutexLocker lock(m_workerDocumentsLock);
209     Document* document = static_cast<Document*>(context);
210     m_workerDocuments.add(document);
211 }
212
213 void SharedWorkerProxy::documentDetached(Document* document)
214 {
215     if (isClosing())
216         return;
217     // 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.
218     MutexLocker lock(m_workerDocumentsLock);
219     m_workerDocuments.remove(document);
220     if (!m_workerDocuments.size())
221         close();
222 }
223
224 void SharedWorkerProxy::close()
225 {
226     ASSERT(!isClosing());
227     m_closing = true;
228     // Stop the worker thread - the proxy will stay around until we get workerThreadExited() notification.
229     if (m_thread)
230         m_thread->stop();
231 }
232
233 class SharedWorkerConnectTask : public ScriptExecutionContext::Task {
234 public:
235     static PassOwnPtr<SharedWorkerConnectTask> create(PassOwnPtr<MessagePortChannel> channel)
236     {
237         return adoptPtr(new SharedWorkerConnectTask(channel));
238     }
239
240 private:
241     SharedWorkerConnectTask(PassOwnPtr<MessagePortChannel> channel)
242         : m_channel(channel)
243     {
244     }
245
246     virtual void performTask(ScriptExecutionContext* scriptContext)
247     {
248         RefPtr<MessagePort> port = MessagePort::create(*scriptContext);
249         port->entangle(m_channel.release());
250         ASSERT(scriptContext->isWorkerContext());
251         WorkerContext* workerContext = static_cast<WorkerContext*>(scriptContext);
252         // Since close() stops the thread event loop, this should not ever get called while closing.
253         ASSERT(!workerContext->isClosing());
254         ASSERT(workerContext->isSharedWorkerContext());
255         workerContext->toSharedWorkerContext()->dispatchEvent(createConnectEvent(port));
256     }
257
258     OwnPtr<MessagePortChannel> m_channel;
259 };
260
261 // Loads the script on behalf of a worker.
262 class SharedWorkerScriptLoader : public RefCounted<SharedWorkerScriptLoader>, private WorkerScriptLoaderClient {
263 public:
264     SharedWorkerScriptLoader(PassRefPtr<SharedWorker>, PassOwnPtr<MessagePortChannel>, PassRefPtr<SharedWorkerProxy>);
265     void load(const KURL&);
266
267 private:
268     // WorkerScriptLoaderClient callbacks
269     virtual void didReceiveResponse(unsigned long identifier, const ResourceResponse&);
270     virtual void notifyFinished();
271
272     RefPtr<SharedWorker> m_worker;
273     OwnPtr<MessagePortChannel> m_port;
274     RefPtr<SharedWorkerProxy> m_proxy;
275     RefPtr<WorkerScriptLoader> m_scriptLoader;
276 };
277
278 SharedWorkerScriptLoader::SharedWorkerScriptLoader(PassRefPtr<SharedWorker> worker, PassOwnPtr<MessagePortChannel> port, PassRefPtr<SharedWorkerProxy> proxy)
279     : m_worker(worker)
280     , m_port(port)
281     , m_proxy(proxy)
282 {
283 }
284
285 void SharedWorkerScriptLoader::load(const KURL& url)
286 {
287     // Stay alive (and keep the SharedWorker and JS wrapper alive) until the load finishes.
288     this->ref();
289     m_worker->setPendingActivity(m_worker.get());
290
291     // Mark this object as active for the duration of the load.
292     m_scriptLoader = WorkerScriptLoader::create();
293 #if PLATFORM(CHROMIUM)
294     m_scriptLoader->setTargetType(ResourceRequest::TargetIsSharedWorker);
295 #endif
296     m_scriptLoader->loadAsynchronously(m_worker->scriptExecutionContext(), url, DenyCrossOriginRequests, this);
297 }
298
299 void SharedWorkerScriptLoader::didReceiveResponse(unsigned long identifier, const ResourceResponse&)
300 {
301     InspectorInstrumentation::didReceiveScriptResponse(m_worker->scriptExecutionContext(), identifier);
302 }
303
304 void SharedWorkerScriptLoader::notifyFinished()
305 {
306     // FIXME: This method is not guaranteed to be invoked if we are loading from WorkerContext (see comment for WorkerScriptLoaderClient::notifyFinished()).
307     // We need to address this before supporting nested workers.
308
309     // Hand off the just-loaded code to the repository to start up the worker thread.
310     if (m_scriptLoader->failed())
311         m_worker->dispatchEvent(Event::create(eventNames().errorEvent, false, true));
312     else {
313         InspectorInstrumentation::scriptImported(m_worker->scriptExecutionContext(), m_scriptLoader->identifier(), m_scriptLoader->script());
314         DefaultSharedWorkerRepository::instance().workerScriptLoaded(*m_proxy, m_worker->scriptExecutionContext()->userAgent(m_scriptLoader->url()), m_scriptLoader->script(), m_port.release());
315     }
316     m_worker->unsetPendingActivity(m_worker.get());
317     this->deref(); // This frees this object - must be the last action in this function.
318 }
319
320 DefaultSharedWorkerRepository& DefaultSharedWorkerRepository::instance()
321 {
322     AtomicallyInitializedStatic(DefaultSharedWorkerRepository*, instance = new DefaultSharedWorkerRepository);
323     return *instance;
324 }
325
326 void DefaultSharedWorkerRepository::workerScriptLoaded(SharedWorkerProxy& proxy, const String& userAgent, const String& workerScript, PassOwnPtr<MessagePortChannel> port)
327 {
328     MutexLocker lock(m_lock);
329     if (proxy.isClosing())
330         return;
331
332     // Another loader may have already started up a thread for this proxy - if so, just send a connect to the pre-existing thread.
333     if (!proxy.thread()) {
334         RefPtr<SharedWorkerThread> thread = SharedWorkerThread::create(proxy.name(), proxy.url(), userAgent, workerScript, proxy, proxy);
335         proxy.setThread(thread);
336         thread->start();
337     }
338     proxy.thread()->runLoop().postTask(SharedWorkerConnectTask::create(port));
339 }
340
341 bool SharedWorkerRepository::isAvailable()
342 {
343     // SharedWorkers are enabled on the default WebKit platform.
344     return true;
345 }
346
347 void SharedWorkerRepository::connect(PassRefPtr<SharedWorker> worker, PassOwnPtr<MessagePortChannel> port, const KURL& url, const String& name, ExceptionCode& ec)
348 {
349     DefaultSharedWorkerRepository::instance().connectToWorker(worker, port, url, name, ec);
350 }
351
352 void SharedWorkerRepository::documentDetached(Document* document)
353 {
354     DefaultSharedWorkerRepository::instance().documentDetached(document);
355 }
356
357 bool SharedWorkerRepository::hasSharedWorkers(Document* document)
358 {
359     return DefaultSharedWorkerRepository::instance().hasSharedWorkers(document);
360 }
361
362 bool DefaultSharedWorkerRepository::hasSharedWorkers(Document* document)
363 {
364     MutexLocker lock(m_lock);
365     for (unsigned i = 0; i < m_proxies.size(); i++) {
366         if (m_proxies[i]->isInWorkerDocuments(document))
367             return true;
368     }
369     return false;
370 }
371
372 void DefaultSharedWorkerRepository::removeProxy(SharedWorkerProxy* proxy)
373 {
374     MutexLocker lock(m_lock);
375     for (unsigned i = 0; i < m_proxies.size(); i++) {
376         if (proxy == m_proxies[i].get()) {
377             m_proxies.remove(i);
378             return;
379         }
380     }
381 }
382
383 void DefaultSharedWorkerRepository::documentDetached(Document* document)
384 {
385     MutexLocker lock(m_lock);
386     for (unsigned i = 0; i < m_proxies.size(); i++)
387         m_proxies[i]->documentDetached(document);
388 }
389
390 void DefaultSharedWorkerRepository::connectToWorker(PassRefPtr<SharedWorker> worker, PassOwnPtr<MessagePortChannel> port, const KURL& url, const String& name, ExceptionCode& ec)
391 {
392     MutexLocker lock(m_lock);
393     ASSERT(worker->scriptExecutionContext()->securityOrigin()->canAccess(SecurityOrigin::create(url).get()));
394     // Fetch a proxy corresponding to this SharedWorker.
395     RefPtr<SharedWorkerProxy> proxy = getProxy(name, url);
396     proxy->addToWorkerDocuments(worker->scriptExecutionContext());
397     if (proxy->url() != url) {
398         // Proxy already existed under alternate URL - return an error.
399         ec = URL_MISMATCH_ERR;
400         return;
401     }
402     // If proxy is already running, just connect to it - otherwise, kick off a loader to load the script.
403     if (proxy->thread())
404         proxy->thread()->runLoop().postTask(SharedWorkerConnectTask::create(port));
405     else {
406         RefPtr<SharedWorkerScriptLoader> loader = adoptRef(new SharedWorkerScriptLoader(worker, port, proxy.release()));
407         loader->load(url);
408     }
409 }
410
411 // Creates a new SharedWorkerProxy or returns an existing one from the repository. Must only be called while the repository mutex is held.
412 PassRefPtr<SharedWorkerProxy> DefaultSharedWorkerRepository::getProxy(const String& name, const KURL& url)
413 {
414     // Look for an existing worker, and create one if it doesn't exist.
415     // Items in the cache are freed on another thread, so do a threadsafe copy of the URL before creating the origin,
416     // to make sure no references to external strings linger.
417     RefPtr<SecurityOrigin> origin = SecurityOrigin::create(KURL(ParsedURLString, url.string().threadsafeCopy()));
418     for (unsigned i = 0; i < m_proxies.size(); i++) {
419         if (!m_proxies[i]->isClosing() && m_proxies[i]->matches(name, origin, url))
420             return m_proxies[i];
421     }
422     // Proxy is not in the repository currently - create a new one.
423     RefPtr<SharedWorkerProxy> proxy = SharedWorkerProxy::create(name, url, origin.release());
424     m_proxies.append(proxy);
425     return proxy.release();
426 }
427
428 DefaultSharedWorkerRepository::DefaultSharedWorkerRepository()
429 {
430 }
431
432 DefaultSharedWorkerRepository::~DefaultSharedWorkerRepository()
433 {
434 }
435
436 } // namespace WebCore
437
438 #endif // ENABLE(SHARED_WORKERS)