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