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 "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     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 
     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 
    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 
    120 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 
    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 
    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 
    157 static void postExceptionTask(ScriptExecutionContext* context, const String& errorMessage, int lineNumber, const String& sourceURL)
    158 {
    159     context->reportException(errorMessage, lineNumber, sourceURL, 0);
    160 }
    161 
    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 
    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 
    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 
    181 void SharedWorkerProxy::workerContextClosed()
    182 {
    183     if (isClosing())
    184         return;
    185     close();
    186 }
    187 
    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 
    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 
    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 
    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:
    226     static PassOwnPtr<SharedWorkerConnectTask> create(PassOwnPtr<MessagePortChannel> channel)
    227     {
    228         return new SharedWorkerConnectTask(channel);
    229     }
    230 
    231 private:
    232     SharedWorkerConnectTask(PassOwnPtr<MessagePortChannel> channel)
    233         : m_channel(channel)
    234     {
    235     }
    236 
    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 
    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 
    275 void SharedWorkerScriptLoader::load(const KURL& url)
    276 {
    277     // Mark this object as active for the duration of the load.
    278     m_scriptLoader = 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 
    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 
    302 DefaultSharedWorkerRepository& DefaultSharedWorkerRepository::instance()
    303 {
    304     AtomicallyInitializedStatic(DefaultSharedWorkerRepository*, instance = new DefaultSharedWorkerRepository);
    305     return *instance;
    306 }
    307 
    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 
    323 bool SharedWorkerRepository::isAvailable()
    324 {
    325     // SharedWorkers are enabled on the default WebKit platform.
    326     return true;
    327 }
    328 
    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 
    334 void SharedWorkerRepository::documentDetached(Document* document)
    335 {
    336     DefaultSharedWorkerRepository::instance().documentDetached(document);
    337 }
    338 
    339 bool SharedWorkerRepository::hasSharedWorkers(Document* document)
    340 {
    341     return DefaultSharedWorkerRepository::instance().hasSharedWorkers(document);
    342 }
    343 
    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 
    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 
    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 
    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.
    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 
    410 DefaultSharedWorkerRepository::DefaultSharedWorkerRepository()
    411 {
    412 }
    413 
    414 DefaultSharedWorkerRepository::~DefaultSharedWorkerRepository()
    415 {
    416 }
    417 
    418 } // namespace WebCore
    419 
    420 #endif // ENABLE(SHARED_WORKERS)
    421