DefaultSharedWorkerRepository.cpp [plain text]
#include "config.h"
#if ENABLE(SHARED_WORKERS)
#include "DefaultSharedWorkerRepository.h"
#include "ActiveDOMObject.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 "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 <inspector/ScriptCallStack.h>
#include <mutex>
#include <wtf/HashSet.h>
#include <wtf/NeverDestroyed.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
{
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;
virtual void postTaskToLoader(ScriptExecutionContext::Task);
virtual bool postTaskForModeToWorkerGlobalScope(ScriptExecutionContext::Task, const String&);
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();
void addToWorkerDocuments(ScriptExecutionContext*);
bool isInWorkerDocuments(Document* document) { return m_workerDocuments.contains(document); }
void documentDetached(Document*);
GroupSettings* groupSettings() const;
private:
SharedWorkerProxy(const String& name, const URL&, PassRefPtr<SecurityOrigin>);
void close();
bool m_closing;
String m_name;
URL m_url;
RefPtr<SharedWorkerThread> m_thread;
RefPtr<SecurityOrigin> m_origin;
HashSet<Document*> m_workerDocuments;
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)
{
ASSERT(m_origin->hasOneRef());
}
bool SharedWorkerProxy::matches(const String& name, PassRefPtr<SecurityOrigin> origin, const URL& urlToMatch) const
{
if (!origin->equal(m_origin.get()))
return false;
if (name.isEmpty() && m_name.isEmpty())
return urlToMatch == url();
return name == m_name;
}
void SharedWorkerProxy::postTaskToLoader(ScriptExecutionContext::Task task)
{
MutexLocker lock(m_workerDocumentsLock);
if (isClosing())
return;
ASSERT(m_workerDocuments.size());
Document* document = *(m_workerDocuments.begin());
document->postTask(WTF::move(task));
}
bool SharedWorkerProxy::postTaskForModeToWorkerGlobalScope(ScriptExecutionContext::Task task, const String& mode)
{
if (isClosing())
return false;
ASSERT(m_thread);
m_thread->runLoop().postTaskForMode(WTF::move(task), mode);
return true;
}
GroupSettings* SharedWorkerProxy::groupSettings() const
{
if (isClosing())
return 0;
ASSERT(m_workerDocuments.size());
Document* document = *(m_workerDocuments.begin());
if (document->page())
return &document->page()->group().groupSettings();
return 0;
}
void SharedWorkerProxy::postExceptionToWorkerObject(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL)
{
MutexLocker lock(m_workerDocumentsLock);
String errorMessageCopy = errorMessage.isolatedCopy();
String sourceURLCopy = sourceURL.isolatedCopy();
for (auto& document : m_workerDocuments)
document->postTask([=] (ScriptExecutionContext& context) {
context.reportException(errorMessageCopy, lineNumber, columnNumber, sourceURLCopy, nullptr);
});
}
void SharedWorkerProxy::postConsoleMessageToWorkerObject(MessageSource source, MessageLevel level, const String& message, int lineNumber, int columnNumber, const String& sourceURL)
{
MutexLocker lock(m_workerDocumentsLock);
String messageCopy = message.isolatedCopy();
String sourceURLCopy = sourceURL.isolatedCopy();
for (auto& document : m_workerDocuments)
document->postTask([=] (ScriptExecutionContext& context) {
context.addConsoleMessage(source, level, messageCopy, sourceURLCopy, lineNumber, columnNumber);
});
}
#if ENABLE(INSPECTOR)
void SharedWorkerProxy::postMessageToPageInspector(const String&)
{
notImplemented();
}
#endif
void SharedWorkerProxy::workerGlobalScopeClosed()
{
if (isClosing())
return;
close();
}
void SharedWorkerProxy::workerGlobalScopeDestroyed()
{
DefaultSharedWorkerRepository::instance().removeProxy(this);
}
void SharedWorkerProxy::addToWorkerDocuments(ScriptExecutionContext* context)
{
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;
MutexLocker lock(m_workerDocumentsLock);
m_workerDocuments.remove(document);
if (!m_workerDocuments.size())
close();
}
void SharedWorkerProxy::close()
{
ASSERT(!isClosing());
m_closing = true;
if (m_thread)
m_thread->stop();
}
class SharedWorkerConnectTask : public ScriptExecutionContext::Task {
public:
SharedWorkerConnectTask(MessagePortChannel* channel)
: ScriptExecutionContext::Task([=] (ScriptExecutionContext& context) {
RefPtr<MessagePort> port = MessagePort::create(context);
port->entangle(std::unique_ptr<MessagePortChannel>(channel));
ASSERT_WITH_SECURITY_IMPLICATION(context.isWorkerGlobalScope());
WorkerGlobalScope* workerGlobalScope = toWorkerGlobalScope(&context);
ASSERT(!workerGlobalScope->isClosing());
ASSERT_WITH_SECURITY_IMPLICATION(workerGlobalScope->isSharedWorkerGlobalScope());
workerGlobalScope->dispatchEvent(createConnectEvent(port));
})
{
}
};
class SharedWorkerScriptLoader : public RefCounted<SharedWorkerScriptLoader>, private WorkerScriptLoaderClient {
public:
SharedWorkerScriptLoader(PassRefPtr<SharedWorker>, std::unique_ptr<MessagePortChannel>, PassRefPtr<SharedWorkerProxy>);
void load(const URL&);
private:
virtual void didReceiveResponse(unsigned long identifier, const ResourceResponse&);
virtual void notifyFinished();
RefPtr<SharedWorker> m_worker;
std::unique_ptr<MessagePortChannel> m_port;
RefPtr<SharedWorkerProxy> m_proxy;
RefPtr<WorkerScriptLoader> m_scriptLoader;
};
SharedWorkerScriptLoader::SharedWorkerScriptLoader(PassRefPtr<SharedWorker> worker, std::unique_ptr<MessagePortChannel> port, PassRefPtr<SharedWorkerProxy> proxy)
: m_worker(worker)
, m_port(WTF::move(port))
, m_proxy(proxy)
{
}
void SharedWorkerScriptLoader::load(const URL& url)
{
this->ref();
m_worker->setPendingActivity(m_worker.get());
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()
{
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(), WTF::move(m_port),
m_worker->scriptExecutionContext()->contentSecurityPolicy()->deprecatedHeader(),
m_worker->scriptExecutionContext()->contentSecurityPolicy()->deprecatedHeaderType());
}
m_worker->unsetPendingActivity(m_worker.get());
this->deref(); }
DefaultSharedWorkerRepository& DefaultSharedWorkerRepository::instance()
{
static std::once_flag onceFlag;
static LazyNeverDestroyed<DefaultSharedWorkerRepository> instance;
std::call_once(onceFlag, []{
instance.construct();
});
return instance;
}
bool DefaultSharedWorkerRepository::isAvailable()
{
return platformStrategies()->sharedWorkerStrategy()->isAvailable();
}
void DefaultSharedWorkerRepository::workerScriptLoaded(SharedWorkerProxy& proxy, const String& userAgent, const String& workerScript, std::unique_ptr<MessagePortChannel> port, const String& contentSecurityPolicy, ContentSecurityPolicy::HeaderType contentSecurityPolicyType)
{
MutexLocker lock(m_lock);
if (proxy.isClosing())
return;
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(port.release()));
}
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, std::unique_ptr<MessagePortChannel> port, const URL& url, const String& name, ExceptionCode& ec)
{
MutexLocker lock(m_lock);
ASSERT(worker->scriptExecutionContext()->securityOrigin()->canAccess(SecurityOrigin::create(url).get()));
RefPtr<SharedWorkerProxy> proxy = getProxy(name, url);
proxy->addToWorkerDocuments(worker->scriptExecutionContext());
if (proxy->url() != url) {
ec = URL_MISMATCH_ERR;
return;
}
if (proxy->thread())
proxy->thread()->runLoop().postTask(SharedWorkerConnectTask(port.release()));
else {
RefPtr<SharedWorkerScriptLoader> loader = adoptRef(new SharedWorkerScriptLoader(worker, WTF::move(port), proxy.release()));
loader->load(url);
}
}
PassRefPtr<SharedWorkerProxy> DefaultSharedWorkerRepository::getProxy(const String& name, const URL& url)
{
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];
}
RefPtr<SharedWorkerProxy> proxy = SharedWorkerProxy::create(name, url, origin.release());
m_proxies.append(proxy);
return proxy.release();
}
DefaultSharedWorkerRepository::DefaultSharedWorkerRepository()
{
}
DefaultSharedWorkerRepository::~DefaultSharedWorkerRepository()
{
}
}
#endif // ENABLE(SHARED_WORKERS)