/* * Copyright (C) 2009 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #if ENABLE(SHARED_WORKERS) #include "DefaultSharedWorkerRepository.h" #include "ActiveDOMObject.h" #include "MessagePort.h" #include "NotImplemented.h" #include "PlatformString.h" #include "SecurityOrigin.h" #include "SecurityOriginHash.h" #include "SharedWorker.h" #include "SharedWorkerContext.h" #include "SharedWorkerThread.h" #include "WorkerLoaderProxy.h" #include "WorkerScriptLoader.h" #include "WorkerScriptLoaderClient.h" #include <wtf/Threading.h> namespace WebCore { class SharedWorkerProxy : public ThreadSafeShared<SharedWorkerProxy>, public WorkerLoaderProxy { public: static PassRefPtr<SharedWorkerProxy> create(const String& name, const KURL& url) { return adoptRef(new SharedWorkerProxy(name, url)); } void setThread(PassRefPtr<SharedWorkerThread> thread) { m_thread = thread; } SharedWorkerThread* thread() { return m_thread.get(); } bool closing() const { return m_closing; } KURL url() const { return m_url.copy(); } String name() const { return m_name.copy(); } // WorkerLoaderProxy // FIXME: Implement WorkerLoaderProxy APIs by proxying to an active document. virtual void postTaskToLoader(PassRefPtr<ScriptExecutionContext::Task>) { notImplemented(); } virtual void postTaskForModeToWorkerContext(PassRefPtr<ScriptExecutionContext::Task>, const String&) { notImplemented(); } // Updates the list of the worker's documents, per section 4.5 of the WebWorkers spec. void addToWorkerDocuments(ScriptExecutionContext*); private: SharedWorkerProxy(const String& name, const KURL&); bool m_closing; String m_name; KURL m_url; RefPtr<SharedWorkerThread> m_thread; }; SharedWorkerProxy::SharedWorkerProxy(const String& name, const KURL& url) : m_closing(false) , m_name(name.copy()) , m_url(url.copy()) { } void SharedWorkerProxy::addToWorkerDocuments(ScriptExecutionContext* context) { // Nested workers are not yet supported, so passed-in context should always be a Document. ASSERT(context->isDocument()); // FIXME: track referring documents so we can shutdown the thread when the last one exits and remove the proxy from the cache. } class SharedWorkerConnectTask : public ScriptExecutionContext::Task { public: static PassRefPtr<SharedWorkerConnectTask> create(PassOwnPtr<MessagePortChannel> channel) { return adoptRef(new SharedWorkerConnectTask(channel)); } private: SharedWorkerConnectTask(PassOwnPtr<MessagePortChannel> channel) : m_channel(channel) { } virtual void performTask(ScriptExecutionContext* scriptContext) { RefPtr<MessagePort> port = MessagePort::create(*scriptContext); port->entangle(m_channel.release()); ASSERT(scriptContext->isWorkerContext()); WorkerContext* workerContext = static_cast<WorkerContext*>(scriptContext); ASSERT(workerContext->isSharedWorkerContext()); workerContext->toSharedWorkerContext()->dispatchConnect(port); } OwnPtr<MessagePortChannel> m_channel; }; // Loads the script on behalf of a worker. class SharedWorkerScriptLoader : public RefCounted<SharedWorkerScriptLoader>, public ActiveDOMObject, private WorkerScriptLoaderClient { public: SharedWorkerScriptLoader(PassRefPtr<SharedWorker>, PassOwnPtr<MessagePortChannel>, PassRefPtr<SharedWorkerProxy>); void load(const KURL&); private: // WorkerScriptLoaderClient callback virtual void notifyFinished(); RefPtr<SharedWorker> m_worker; OwnPtr<MessagePortChannel> m_port; RefPtr<SharedWorkerProxy> m_proxy; OwnPtr<WorkerScriptLoader> m_scriptLoader; }; SharedWorkerScriptLoader::SharedWorkerScriptLoader(PassRefPtr<SharedWorker> worker, PassOwnPtr<MessagePortChannel> port, PassRefPtr<SharedWorkerProxy> proxy) : ActiveDOMObject(worker->scriptExecutionContext(), this) , m_worker(worker) , m_port(port) , m_proxy(proxy) { } void SharedWorkerScriptLoader::load(const KURL& url) { // Mark this object as active for the duration of the load. ASSERT(!hasPendingActivity()); m_scriptLoader = new WorkerScriptLoader(); m_scriptLoader->loadAsynchronously(scriptExecutionContext(), url, DenyCrossOriginRedirect, this); // Stay alive until the load finishes. setPendingActivity(this); } void SharedWorkerScriptLoader::notifyFinished() { // Hand off the just-loaded code to the repository to start up the worker thread. if (m_scriptLoader->failed()) m_worker->dispatchLoadErrorEvent(); else DefaultSharedWorkerRepository::instance().workerScriptLoaded(*m_proxy, scriptExecutionContext()->userAgent(m_scriptLoader->url()), m_scriptLoader->script(), m_port.release()); // This frees this object - must be the last action in this function. unsetPendingActivity(this); } DefaultSharedWorkerRepository& DefaultSharedWorkerRepository::instance() { AtomicallyInitializedStatic(DefaultSharedWorkerRepository*, instance = new DefaultSharedWorkerRepository); return *instance; } void DefaultSharedWorkerRepository::workerScriptLoaded(SharedWorkerProxy& proxy, const String& userAgent, const String& workerScript, PassOwnPtr<MessagePortChannel> port) { MutexLocker lock(m_lock); if (proxy.closing()) return; // Another loader may have already started up a thread for this proxy - if so, just send a connect to the pre-existing thread. if (!proxy.thread()) { RefPtr<SharedWorkerThread> thread = SharedWorkerThread::create(proxy.name(), proxy.url(), userAgent, workerScript, proxy); proxy.setThread(thread); thread->start(); } proxy.thread()->runLoop().postTask(SharedWorkerConnectTask::create(port)); } void SharedWorkerRepository::connect(PassRefPtr<SharedWorker> worker, PassOwnPtr<MessagePortChannel> port, const KURL& url, const String& name, ExceptionCode& ec) { DefaultSharedWorkerRepository::instance().connectToWorker(worker, port, url, name, ec); } void DefaultSharedWorkerRepository::connectToWorker(PassRefPtr<SharedWorker> worker, PassOwnPtr<MessagePortChannel> port, const KURL& url, const String& name, ExceptionCode& ec) { MutexLocker lock(m_lock); ASSERT(worker->scriptExecutionContext()->securityOrigin()->canAccess(SecurityOrigin::create(url).get())); // Fetch a proxy corresponding to this SharedWorker. RefPtr<SharedWorkerProxy> proxy = getProxy(name, url); proxy->addToWorkerDocuments(worker->scriptExecutionContext()); if (proxy->url() != url) { // Proxy already existed under alternate URL - return an error. ec = URL_MISMATCH_ERR; return; } // If proxy is already running, just connect to it - otherwise, kick off a loader to load the script. if (proxy->thread()) proxy->thread()->runLoop().postTask(SharedWorkerConnectTask::create(port)); else { RefPtr<SharedWorkerScriptLoader> loader = adoptRef(new SharedWorkerScriptLoader(worker, port.release(), proxy.release())); loader->load(url); } } // Creates a new SharedWorkerProxy or returns an existing one from the repository. Must only be called while the repository mutex is held. PassRefPtr<SharedWorkerProxy> DefaultSharedWorkerRepository::getProxy(const String& name, const KURL& url) { // Look for an existing worker, and create one if it doesn't exist. // Items in the cache are freed on another thread, so copy the URL before creating the origin, to make sure no references to external strings linger. RefPtr<SecurityOrigin> origin = SecurityOrigin::create(url.copy()); SharedWorkerNameMap* nameMap = m_cache.get(origin); if (!nameMap) { nameMap = new SharedWorkerNameMap(); m_cache.set(origin, nameMap); } RefPtr<SharedWorkerProxy> proxy = nameMap->get(name); if (!proxy.get()) { proxy = SharedWorkerProxy::create(name, url); nameMap->set(proxy->name(), proxy); } return proxy; } DefaultSharedWorkerRepository::DefaultSharedWorkerRepository() { } DefaultSharedWorkerRepository::~DefaultSharedWorkerRepository() { } } // namespace WebCore #endif // ENABLE(SHARED_WORKERS)