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