| /* |
| * 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 "CrossThreadTask.h" |
| #include "Document.h" |
| #include "ExceptionCode.h" |
| #include "InspectorInstrumentation.h" |
| #include "MessageEvent.h" |
| #include "MessagePort.h" |
| #include "NotImplemented.h" |
| #include "PageGroup.h" |
| #include "PlatformStrategies.h" |
| #include "ScriptCallStack.h" |
| #include "SecurityOrigin.h" |
| #include "SecurityOriginHash.h" |
| #include "SharedWorker.h" |
| #include "SharedWorkerGlobalScope.h" |
| #include "SharedWorkerRepository.h" |
| #include "SharedWorkerStrategy.h" |
| #include "SharedWorkerThread.h" |
| #include "WorkerLoaderProxy.h" |
| #include "WorkerReportingProxy.h" |
| #include "WorkerScriptLoader.h" |
| #include "WorkerScriptLoaderClient.h" |
| #include <mutex> |
| #include <wtf/HashSet.h> |
| #include <wtf/Threading.h> |
| #include <wtf/text/WTFString.h> |
| |
| namespace WebCore { |
| |
| class SharedWorkerProxy : public ThreadSafeRefCounted<SharedWorkerProxy>, public WorkerLoaderProxy, public WorkerReportingProxy { |
| public: |
| static PassRefPtr<SharedWorkerProxy> create(const String& name, const URL& url, PassRefPtr<SecurityOrigin> origin) { return adoptRef(new SharedWorkerProxy(name, url, origin)); } |
| |
| void setThread(PassRefPtr<SharedWorkerThread> thread) { m_thread = thread; } |
| SharedWorkerThread* thread() { return m_thread.get(); } |
| bool isClosing() const { return m_closing; } |
| URL url() const |
| { |
| // Don't use m_url.copy() because it isn't a threadsafe method. |
| return URL(ParsedURLString, m_url.string().isolatedCopy()); |
| } |
| |
| String name() const { return m_name.isolatedCopy(); } |
| bool matches(const String& name, PassRefPtr<SecurityOrigin> origin, const URL& urlToMatch) const; |
| |
| // WorkerLoaderProxy |
| virtual void postTaskToLoader(PassOwnPtr<ScriptExecutionContext::Task>); |
| virtual bool postTaskForModeToWorkerGlobalScope(PassOwnPtr<ScriptExecutionContext::Task>, const String&); |
| |
| // WorkerReportingProxy |
| virtual void postExceptionToWorkerObject(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL); |
| virtual void postConsoleMessageToWorkerObject(MessageSource, MessageLevel, const String& message, int lineNumber, int columnNumber, const String& sourceURL); |
| #if ENABLE(INSPECTOR) |
| virtual void postMessageToPageInspector(const String&); |
| #endif |
| virtual void workerGlobalScopeClosed(); |
| virtual void workerGlobalScopeDestroyed(); |
| |
| // Updates the list of the worker's documents, per section 4.5 of the WebWorkers spec. |
| void addToWorkerDocuments(ScriptExecutionContext*); |
| |
| bool isInWorkerDocuments(Document* document) { return m_workerDocuments.contains(document); } |
| |
| // 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. |
| void documentDetached(Document*); |
| |
| GroupSettings* groupSettings() const; // Page GroupSettings used by worker thread. |
| |
| private: |
| SharedWorkerProxy(const String& name, const URL&, PassRefPtr<SecurityOrigin>); |
| void close(); |
| |
| bool m_closing; |
| String m_name; |
| URL m_url; |
| // The thread is freed when the proxy is destroyed, so we need to make sure that the proxy stays around until the SharedWorkerGlobalScope exits. |
| RefPtr<SharedWorkerThread> m_thread; |
| RefPtr<SecurityOrigin> m_origin; |
| HashSet<Document*> m_workerDocuments; |
| // Ensures exclusive access to the worker documents. Must not grab any other locks (such as the DefaultSharedWorkerRepository lock) while holding this one. |
| Mutex m_workerDocumentsLock; |
| }; |
| |
| SharedWorkerProxy::SharedWorkerProxy(const String& name, const URL& url, PassRefPtr<SecurityOrigin> origin) |
| : m_closing(false) |
| , m_name(name.isolatedCopy()) |
| , m_url(url.copy()) |
| , m_origin(origin) |
| { |
| // We should be the sole owner of the SecurityOrigin, as we will free it on another thread. |
| ASSERT(m_origin->hasOneRef()); |
| } |
| |
| bool SharedWorkerProxy::matches(const String& name, PassRefPtr<SecurityOrigin> origin, const URL& urlToMatch) const |
| { |
| // If the origins don't match, or the names don't match, then this is not the proxy we are looking for. |
| if (!origin->equal(m_origin.get())) |
| return false; |
| |
| // If the names are both empty, compares the URLs instead per the Web Workers spec. |
| if (name.isEmpty() && m_name.isEmpty()) |
| return urlToMatch == url(); |
| |
| return name == m_name; |
| } |
| |
| void SharedWorkerProxy::postTaskToLoader(PassOwnPtr<ScriptExecutionContext::Task> task) |
| { |
| MutexLocker lock(m_workerDocumentsLock); |
| |
| if (isClosing()) |
| return; |
| |
| // If we aren't closing, then we must have at least one document. |
| ASSERT(m_workerDocuments.size()); |
| |
| // Just pick an arbitrary active document from the HashSet and pass load requests to it. |
| // 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? |
| Document* document = *(m_workerDocuments.begin()); |
| document->postTask(task); |
| } |
| |
| bool SharedWorkerProxy::postTaskForModeToWorkerGlobalScope(PassOwnPtr<ScriptExecutionContext::Task> task, const String& mode) |
| { |
| if (isClosing()) |
| return false; |
| ASSERT(m_thread); |
| m_thread->runLoop().postTaskForMode(task, mode); |
| return true; |
| } |
| |
| GroupSettings* SharedWorkerProxy::groupSettings() const |
| { |
| if (isClosing()) |
| return 0; |
| ASSERT(m_workerDocuments.size()); |
| // Just pick the first active document, and use the groupsettings of that page. |
| Document* document = *(m_workerDocuments.begin()); |
| if (document->page()) |
| return &document->page()->group().groupSettings(); |
| |
| return 0; |
| } |
| |
| static void postExceptionTask(ScriptExecutionContext* context, const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL) |
| { |
| context->reportException(errorMessage, lineNumber, columnNumber, sourceURL, 0); |
| } |
| |
| void SharedWorkerProxy::postExceptionToWorkerObject(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL) |
| { |
| MutexLocker lock(m_workerDocumentsLock); |
| for (HashSet<Document*>::iterator iter = m_workerDocuments.begin(); iter != m_workerDocuments.end(); ++iter) |
| (*iter)->postTask(createCallbackTask(&postExceptionTask, errorMessage, lineNumber, columnNumber, sourceURL)); |
| } |
| |
| static void postConsoleMessageTask(ScriptExecutionContext* document, MessageSource source, MessageLevel level, const String& message, const String& sourceURL, unsigned lineNumber, unsigned columnNumber) |
| { |
| document->addConsoleMessage(source, level, message, sourceURL, lineNumber, columnNumber); |
| } |
| |
| void SharedWorkerProxy::postConsoleMessageToWorkerObject(MessageSource source, MessageLevel level, const String& message, int lineNumber, int columnNumber, const String& sourceURL) |
| { |
| MutexLocker lock(m_workerDocumentsLock); |
| for (HashSet<Document*>::iterator iter = m_workerDocuments.begin(); iter != m_workerDocuments.end(); ++iter) |
| (*iter)->postTask(createCallbackTask(&postConsoleMessageTask, source, level, message, sourceURL, lineNumber, columnNumber)); |
| } |
| |
| #if ENABLE(INSPECTOR) |
| void SharedWorkerProxy::postMessageToPageInspector(const String&) |
| { |
| notImplemented(); |
| } |
| #endif |
| |
| void SharedWorkerProxy::workerGlobalScopeClosed() |
| { |
| if (isClosing()) |
| return; |
| close(); |
| } |
| |
| void SharedWorkerProxy::workerGlobalScopeDestroyed() |
| { |
| // The proxy may be freed by this call, so do not reference it any further. |
| DefaultSharedWorkerRepository::instance().removeProxy(this); |
| } |
| |
| void SharedWorkerProxy::addToWorkerDocuments(ScriptExecutionContext* context) |
| { |
| // Nested workers are not yet supported, so passed-in context should always be a Document. |
| ASSERT_WITH_SECURITY_IMPLICATION(context->isDocument()); |
| ASSERT(!isClosing()); |
| MutexLocker lock(m_workerDocumentsLock); |
| Document* document = static_cast<Document*>(context); |
| m_workerDocuments.add(document); |
| } |
| |
| void SharedWorkerProxy::documentDetached(Document* document) |
| { |
| if (isClosing()) |
| return; |
| // 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. |
| MutexLocker lock(m_workerDocumentsLock); |
| m_workerDocuments.remove(document); |
| if (!m_workerDocuments.size()) |
| close(); |
| } |
| |
| void SharedWorkerProxy::close() |
| { |
| ASSERT(!isClosing()); |
| m_closing = true; |
| // Stop the worker thread - the proxy will stay around until we get workerThreadExited() notification. |
| if (m_thread) |
| m_thread->stop(); |
| } |
| |
| class SharedWorkerConnectTask : public ScriptExecutionContext::Task { |
| public: |
| static PassOwnPtr<SharedWorkerConnectTask> create(PassOwnPtr<MessagePortChannel> channel) |
| { |
| return adoptPtr(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_WITH_SECURITY_IMPLICATION(scriptContext->isWorkerGlobalScope()); |
| WorkerGlobalScope* workerGlobalScope = static_cast<WorkerGlobalScope*>(scriptContext); |
| // Since close() stops the thread event loop, this should not ever get called while closing. |
| ASSERT(!workerGlobalScope->isClosing()); |
| ASSERT_WITH_SECURITY_IMPLICATION(workerGlobalScope->isSharedWorkerGlobalScope()); |
| workerGlobalScope->dispatchEvent(createConnectEvent(port)); |
| } |
| |
| OwnPtr<MessagePortChannel> m_channel; |
| }; |
| |
| // Loads the script on behalf of a worker. |
| class SharedWorkerScriptLoader : public RefCounted<SharedWorkerScriptLoader>, private WorkerScriptLoaderClient { |
| public: |
| SharedWorkerScriptLoader(PassRefPtr<SharedWorker>, PassOwnPtr<MessagePortChannel>, PassRefPtr<SharedWorkerProxy>); |
| void load(const URL&); |
| |
| private: |
| // WorkerScriptLoaderClient callbacks |
| virtual void didReceiveResponse(unsigned long identifier, const ResourceResponse&); |
| virtual void notifyFinished(); |
| |
| RefPtr<SharedWorker> m_worker; |
| OwnPtr<MessagePortChannel> m_port; |
| RefPtr<SharedWorkerProxy> m_proxy; |
| RefPtr<WorkerScriptLoader> m_scriptLoader; |
| }; |
| |
| SharedWorkerScriptLoader::SharedWorkerScriptLoader(PassRefPtr<SharedWorker> worker, PassOwnPtr<MessagePortChannel> port, PassRefPtr<SharedWorkerProxy> proxy) |
| : m_worker(worker) |
| , m_port(port) |
| , m_proxy(proxy) |
| { |
| } |
| |
| void SharedWorkerScriptLoader::load(const URL& url) |
| { |
| // Stay alive (and keep the SharedWorker and JS wrapper alive) until the load finishes. |
| this->ref(); |
| m_worker->setPendingActivity(m_worker.get()); |
| |
| // Mark this object as active for the duration of the load. |
| m_scriptLoader = WorkerScriptLoader::create(); |
| m_scriptLoader->loadAsynchronously(m_worker->scriptExecutionContext(), url, DenyCrossOriginRequests, this); |
| } |
| |
| void SharedWorkerScriptLoader::didReceiveResponse(unsigned long identifier, const ResourceResponse&) |
| { |
| InspectorInstrumentation::didReceiveScriptResponse(m_worker->scriptExecutionContext(), identifier); |
| } |
| |
| void SharedWorkerScriptLoader::notifyFinished() |
| { |
| // FIXME: This method is not guaranteed to be invoked if we are loading from WorkerGlobalScope (see comment for WorkerScriptLoaderClient::notifyFinished()). |
| // We need to address this before supporting nested workers. |
| |
| // Hand off the just-loaded code to the repository to start up the worker thread. |
| if (m_scriptLoader->failed()) |
| m_worker->dispatchEvent(Event::create(eventNames().errorEvent, false, true)); |
| else { |
| InspectorInstrumentation::scriptImported(m_worker->scriptExecutionContext(), m_scriptLoader->identifier(), m_scriptLoader->script()); |
| DefaultSharedWorkerRepository::instance().workerScriptLoaded(*m_proxy, m_worker->scriptExecutionContext()->userAgent(m_scriptLoader->url()), |
| m_scriptLoader->script(), m_port.release(), |
| m_worker->scriptExecutionContext()->contentSecurityPolicy()->deprecatedHeader(), |
| m_worker->scriptExecutionContext()->contentSecurityPolicy()->deprecatedHeaderType()); |
| } |
| m_worker->unsetPendingActivity(m_worker.get()); |
| this->deref(); // This frees this object - must be the last action in this function. |
| } |
| |
| DefaultSharedWorkerRepository& DefaultSharedWorkerRepository::instance() |
| { |
| static std::once_flag onceFlag; |
| static DefaultSharedWorkerRepository* instance; |
| std::call_once(onceFlag, []{ |
| instance = new DefaultSharedWorkerRepository; |
| }); |
| |
| return *instance; |
| } |
| |
| bool DefaultSharedWorkerRepository::isAvailable() |
| { |
| return platformStrategies()->sharedWorkerStrategy()->isAvailable(); |
| } |
| |
| void DefaultSharedWorkerRepository::workerScriptLoaded(SharedWorkerProxy& proxy, const String& userAgent, const String& workerScript, PassOwnPtr<MessagePortChannel> port, const String& contentSecurityPolicy, ContentSecurityPolicy::HeaderType contentSecurityPolicyType) |
| { |
| MutexLocker lock(m_lock); |
| if (proxy.isClosing()) |
| 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, proxy.groupSettings(), workerScript, proxy, proxy, DontPauseWorkerGlobalScopeOnStart, contentSecurityPolicy, contentSecurityPolicyType); |
| proxy.setThread(thread); |
| thread->start(); |
| } |
| proxy.thread()->runLoop().postTask(SharedWorkerConnectTask::create(port)); |
| } |
| |
| bool DefaultSharedWorkerRepository::hasSharedWorkers(Document* document) |
| { |
| MutexLocker lock(m_lock); |
| for (unsigned i = 0; i < m_proxies.size(); i++) { |
| if (m_proxies[i]->isInWorkerDocuments(document)) |
| return true; |
| } |
| return false; |
| } |
| |
| void DefaultSharedWorkerRepository::removeProxy(SharedWorkerProxy* proxy) |
| { |
| MutexLocker lock(m_lock); |
| for (unsigned i = 0; i < m_proxies.size(); i++) { |
| if (proxy == m_proxies[i].get()) { |
| m_proxies.remove(i); |
| return; |
| } |
| } |
| } |
| |
| void DefaultSharedWorkerRepository::documentDetached(Document* document) |
| { |
| MutexLocker lock(m_lock); |
| for (unsigned i = 0; i < m_proxies.size(); i++) |
| m_proxies[i]->documentDetached(document); |
| } |
| |
| void DefaultSharedWorkerRepository::connectToWorker(PassRefPtr<SharedWorker> worker, PassOwnPtr<MessagePortChannel> port, const URL& 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); |
| |
| // FIXME: Why is this done even if we are raising an exception below? |
| 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, 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 URL& url) |
| { |
| // Look for an existing worker, and create one if it doesn't exist. |
| // Items in the cache are freed on another thread, so do a threadsafe copy of the URL before creating the origin, |
| // to make sure no references to external strings linger. |
| RefPtr<SecurityOrigin> origin = SecurityOrigin::create(URL(ParsedURLString, url.string().isolatedCopy())); |
| for (unsigned i = 0; i < m_proxies.size(); i++) { |
| if (!m_proxies[i]->isClosing() && m_proxies[i]->matches(name, origin, url)) |
| return m_proxies[i]; |
| } |
| // Proxy is not in the repository currently - create a new one. |
| RefPtr<SharedWorkerProxy> proxy = SharedWorkerProxy::create(name, url, origin.release()); |
| m_proxies.append(proxy); |
| return proxy.release(); |
| } |
| |
| DefaultSharedWorkerRepository::DefaultSharedWorkerRepository() |
| { |
| } |
| |
| DefaultSharedWorkerRepository::~DefaultSharedWorkerRepository() |
| { |
| } |
| |
| } // namespace WebCore |
| |
| #endif // ENABLE(SHARED_WORKERS) |