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