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