Home | History | Annotate | Download | only in workers
      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:
     61     static PassRefPtr<SharedWorkerProxy> create(const String& name, const KURL& url, PassRefPtr<SecurityOrigin> origin) { return adoptRef(new SharedWorkerProxy(name, url, origin)); }
     62 
     63     void setThread(PassRefPtr<SharedWorkerThread> thread) { m_thread = thread; }
     64     SharedWorkerThread* thread() { return m_thread.get(); }
     65     bool isClosing() const { return m_closing; }
     66     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 
     72     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 
     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 
    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 
    118 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 
    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 
    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 
    155 static void postExceptionTask(ScriptExecutionContext* context, const String& errorMessage, int lineNumber, const String& sourceURL)
    156 {
    157     context->reportException(errorMessage, lineNumber, sourceURL);
    158 }
    159 
    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 
    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 
    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 
    179 void SharedWorkerProxy::workerContextClosed()
    180 {
    181     if (isClosing())
    182         return;
    183     close();
    184 }
    185 
    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 
    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 
    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 
    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:
    224     static PassOwnPtr<SharedWorkerConnectTask> create(PassOwnPtr<MessagePortChannel> channel)
    225     {
    226         return new SharedWorkerConnectTask(channel);
    227     }
    228 
    229 private:
    230     SharedWorkerConnectTask(PassOwnPtr<MessagePortChannel> channel)
    231         : m_channel(channel)
    232     {
    233     }
    234 
    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 
    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 
    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 
    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 
    299 DefaultSharedWorkerRepository& DefaultSharedWorkerRepository::instance()
    300 {
    301     AtomicallyInitializedStatic(DefaultSharedWorkerRepository*, instance = new DefaultSharedWorkerRepository);
    302     return *instance;
    303 }
    304 
    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 
    320 bool SharedWorkerRepository::isAvailable()
    321 {
    322     // SharedWorkers are enabled on the default WebKit platform.
    323     return true;
    324 }
    325 
    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 
    331 void SharedWorkerRepository::documentDetached(Document* document)
    332 {
    333     DefaultSharedWorkerRepository::instance().documentDetached(document);
    334 }
    335 
    336 bool SharedWorkerRepository::hasSharedWorkers(Document* document)
    337 {
    338     return DefaultSharedWorkerRepository::instance().hasSharedWorkers(document);
    339 }
    340 
    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 
    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 
    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 
    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.
    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 
    407 DefaultSharedWorkerRepository::DefaultSharedWorkerRepository()
    408 {
    409 }
    410 
    411 DefaultSharedWorkerRepository::~DefaultSharedWorkerRepository()
    412 {
    413 }
    414 
    415 } // namespace WebCore
    416 
    417 #endif // ENABLE(SHARED_WORKERS)
    418