1/*
2 * Copyright (C) 2008-2017 Apple 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
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 *
25 */
26
27#include "config.h"
28#include "WorkerThread.h"
29
30#include "ContentSecurityPolicyResponseHeaders.h"
31#include "IDBConnectionProxy.h"
32#include "ScriptSourceCode.h"
33#include "SecurityOrigin.h"
34#include "SocketProvider.h"
35#include "ThreadGlobalData.h"
36#include <wtf/URL.h>
37#include "WorkerGlobalScope.h"
38#include "WorkerInspectorController.h"
39#include <utility>
40#include <wtf/Lock.h>
41#include <wtf/NeverDestroyed.h>
42#include <wtf/Noncopyable.h>
43#include <wtf/text/WTFString.h>
44
45#if PLATFORM(IOS_FAMILY)
46#include "FloatingPointEnvironment.h"
47#include "WebCoreThread.h"
48#endif
49
50#if USE(GLIB)
51#include <wtf/glib/GRefPtr.h>
52#endif
53
54namespace WebCore {
55
56HashSet<WorkerThread*>& WorkerThread::workerThreads(const LockHolder&)
57{
58 static NeverDestroyed<HashSet<WorkerThread*>> workerThreads;
59 return workerThreads;
60}
61
62Lock& WorkerThread::workerThreadsMutex()
63{
64 static Lock mutex;
65 return mutex;
66}
67
68unsigned WorkerThread::workerThreadCount()
69{
70 LockHolder lock(workerThreadsMutex());
71 return workerThreads(lock).size();
72}
73
74struct WorkerThreadStartupData {
75 WTF_MAKE_NONCOPYABLE(WorkerThreadStartupData); WTF_MAKE_FAST_ALLOCATED;
76public:
77 WorkerThreadStartupData(const URL& scriptURL, const String& name, const String& identifier, const String& userAgent, bool isOnline, const String& sourceCode, WorkerThreadStartMode, const ContentSecurityPolicyResponseHeaders&, bool shouldBypassMainWorldContentSecurityPolicy, const SecurityOrigin& topOrigin, MonotonicTime timeOrigin, PAL::SessionID);
78
79 URL m_scriptURL;
80 Ref<SecurityOrigin> m_origin;
81 String m_name;
82 String m_identifier;
83 String m_userAgent;
84 String m_sourceCode;
85 WorkerThreadStartMode m_startMode;
86 ContentSecurityPolicyResponseHeaders m_contentSecurityPolicyResponseHeaders;
87 bool m_shouldBypassMainWorldContentSecurityPolicy;
88 bool m_isOnline;
89 Ref<SecurityOrigin> m_topOrigin;
90 MonotonicTime m_timeOrigin;
91 PAL::SessionID m_sessionID;
92};
93
94WorkerThreadStartupData::WorkerThreadStartupData(const URL& scriptURL, const String& name, const String& identifier, const String& userAgent, bool isOnline, const String& sourceCode, WorkerThreadStartMode startMode, const ContentSecurityPolicyResponseHeaders& contentSecurityPolicyResponseHeaders, bool shouldBypassMainWorldContentSecurityPolicy, const SecurityOrigin& topOrigin, MonotonicTime timeOrigin, PAL::SessionID sessionID)
95 : m_scriptURL(scriptURL.isolatedCopy())
96 , m_origin(SecurityOrigin::create(m_scriptURL)->isolatedCopy())
97 , m_name(name.isolatedCopy())
98 , m_identifier(identifier.isolatedCopy())
99 , m_userAgent(userAgent.isolatedCopy())
100 , m_sourceCode(sourceCode.isolatedCopy())
101 , m_startMode(startMode)
102 , m_contentSecurityPolicyResponseHeaders(contentSecurityPolicyResponseHeaders.isolatedCopy())
103 , m_shouldBypassMainWorldContentSecurityPolicy(shouldBypassMainWorldContentSecurityPolicy)
104 , m_isOnline(isOnline)
105 , m_topOrigin(topOrigin.isolatedCopy())
106 , m_timeOrigin(timeOrigin)
107 , m_sessionID(sessionID.isolatedCopy())
108{
109}
110
111WorkerThread::WorkerThread(const URL& scriptURL, const String& name, const String& identifier, const String& userAgent, bool isOnline, const String& sourceCode, WorkerLoaderProxy& workerLoaderProxy, WorkerDebuggerProxy& workerDebuggerProxy, WorkerReportingProxy& workerReportingProxy, WorkerThreadStartMode startMode, const ContentSecurityPolicyResponseHeaders& contentSecurityPolicyResponseHeaders, bool shouldBypassMainWorldContentSecurityPolicy, const SecurityOrigin& topOrigin, MonotonicTime timeOrigin, IDBClient::IDBConnectionProxy* connectionProxy, SocketProvider* socketProvider, JSC::RuntimeFlags runtimeFlags, PAL::SessionID sessionID)
112 : m_identifier(identifier.isolatedCopy())
113 , m_workerLoaderProxy(workerLoaderProxy)
114 , m_workerDebuggerProxy(workerDebuggerProxy)
115 , m_workerReportingProxy(workerReportingProxy)
116 , m_runtimeFlags(runtimeFlags)
117 , m_startupData(std::make_unique<WorkerThreadStartupData>(scriptURL, name, identifier, userAgent, isOnline, sourceCode, startMode, contentSecurityPolicyResponseHeaders, shouldBypassMainWorldContentSecurityPolicy, topOrigin, timeOrigin, sessionID))
118#if ENABLE(INDEXED_DATABASE)
119 , m_idbConnectionProxy(connectionProxy)
120#endif
121 , m_socketProvider(socketProvider)
122{
123#if !ENABLE(INDEXED_DATABASE)
124 UNUSED_PARAM(connectionProxy);
125#endif
126
127 LockHolder lock(workerThreadsMutex());
128 workerThreads(lock).add(this);
129}
130
131WorkerThread::~WorkerThread()
132{
133 LockHolder lock(workerThreadsMutex());
134 ASSERT(workerThreads(lock).contains(this));
135 workerThreads(lock).remove(this);
136}
137
138void WorkerThread::start(WTF::Function<void(const String&)>&& evaluateCallback)
139{
140 // Mutex protection is necessary to ensure that m_thread is initialized when the thread starts.
141 LockHolder lock(m_threadCreationAndWorkerGlobalScopeMutex);
142
143 if (m_thread)
144 return;
145
146 m_evaluateCallback = WTFMove(evaluateCallback);
147
148 m_thread = Thread::create(isServiceWorkerThread() ? "WebCore: Service Worker" : "WebCore: Worker", [this] {
149 workerThread();
150 });
151}
152
153void WorkerThread::workerThread()
154{
155 auto protectedThis = makeRef(*this);
156
157 // Propagate the mainThread's fenv to workers.
158#if PLATFORM(IOS_FAMILY)
159 FloatingPointEnvironment::singleton().propagateMainThreadEnvironment();
160#endif
161
162#if USE(GLIB)
163 GRefPtr<GMainContext> mainContext = adoptGRef(g_main_context_new());
164 g_main_context_push_thread_default(mainContext.get());
165#endif
166
167 WorkerScriptController* scriptController;
168 {
169 // Mutex protection is necessary to ensure that we don't change m_workerGlobalScope
170 // while WorkerThread::stop() is accessing it. Note that WorkerThread::stop() can
171 // be called before we've finished creating the WorkerGlobalScope.
172 LockHolder lock(m_threadCreationAndWorkerGlobalScopeMutex);
173 m_workerGlobalScope = createWorkerGlobalScope(m_startupData->m_scriptURL, WTFMove(m_startupData->m_origin), m_startupData->m_name, m_startupData->m_identifier, m_startupData->m_userAgent, m_startupData->m_isOnline, m_startupData->m_contentSecurityPolicyResponseHeaders, m_startupData->m_shouldBypassMainWorldContentSecurityPolicy, WTFMove(m_startupData->m_topOrigin), m_startupData->m_timeOrigin, m_startupData->m_sessionID);
174
175 scriptController = m_workerGlobalScope->script();
176
177 if (m_runLoop.terminated()) {
178 // The worker was terminated before the thread had a chance to run. Since the context didn't exist yet,
179 // forbidExecution() couldn't be called from stop().
180 scriptController->scheduleExecutionTermination();
181 scriptController->forbidExecution();
182 }
183 }
184
185 if (m_startupData->m_startMode == WorkerThreadStartMode::WaitForInspector) {
186 startRunningDebuggerTasks();
187
188 // If the worker was somehow terminated while processing debugger commands.
189 if (m_runLoop.terminated())
190 scriptController->forbidExecution();
191 }
192
193 String exceptionMessage;
194 scriptController->evaluate(ScriptSourceCode(m_startupData->m_sourceCode, URL(m_startupData->m_scriptURL)), &exceptionMessage);
195
196 callOnMainThread([evaluateCallback = WTFMove(m_evaluateCallback), message = exceptionMessage.isolatedCopy()] {
197 if (evaluateCallback)
198 evaluateCallback(message);
199 });
200
201 // Free the startup data to cause its member variable deref's happen on the worker's thread (since
202 // all ref/derefs of these objects are happening on the thread at this point). Note that
203 // WorkerThread::~WorkerThread happens on a different thread where it was created.
204 m_startupData = nullptr;
205
206 runEventLoop();
207
208#if USE(GLIB)
209 g_main_context_pop_thread_default(mainContext.get());
210#endif
211
212 RefPtr<Thread> protector = m_thread;
213
214 ASSERT(m_workerGlobalScope->hasOneRef());
215
216 RefPtr<WorkerGlobalScope> workerGlobalScopeToDelete;
217 {
218 // Mutex protection is necessary to ensure that we don't change m_workerGlobalScope
219 // while WorkerThread::stop is accessing it.
220 LockHolder lock(m_threadCreationAndWorkerGlobalScopeMutex);
221
222 // Delay the destruction of the WorkerGlobalScope context until after we've unlocked the
223 // m_threadCreationAndWorkerGlobalScopeMutex. This is needed because destructing the
224 // context will trigger the main thread to race against us to delete the WorkerThread
225 // object, and the WorkerThread object owns the mutex we need to unlock after this.
226 workerGlobalScopeToDelete = WTFMove(m_workerGlobalScope);
227
228 if (m_stoppedCallback)
229 callOnMainThread(WTFMove(m_stoppedCallback));
230 }
231
232 // The below assignment will destroy the context, which will in turn notify messaging proxy.
233 // We cannot let any objects survive past thread exit, because no other thread will run GC or otherwise destroy them.
234 workerGlobalScopeToDelete = nullptr;
235
236 // Clean up WebCore::ThreadGlobalData before WTF::Thread goes away!
237 threadGlobalData().destroy();
238
239 // Send the last WorkerThread Ref to be Deref'ed on the main thread.
240 callOnMainThread([protectedThis = WTFMove(protectedThis)] { });
241
242 // The thread object may be already destroyed from notification now, don't try to access "this".
243 protector->detach();
244}
245
246void WorkerThread::startRunningDebuggerTasks()
247{
248 ASSERT(!m_pausedForDebugger);
249 m_pausedForDebugger = true;
250
251 MessageQueueWaitResult result;
252 do {
253 result = m_runLoop.runInDebuggerMode(*m_workerGlobalScope);
254 } while (result != MessageQueueTerminated && m_pausedForDebugger);
255}
256
257void WorkerThread::stopRunningDebuggerTasks()
258{
259 m_pausedForDebugger = false;
260}
261
262void WorkerThread::runEventLoop()
263{
264 // Does not return until terminated.
265 m_runLoop.run(m_workerGlobalScope.get());
266}
267
268void WorkerThread::stop(WTF::Function<void()>&& stoppedCallback)
269{
270 // Mutex protection is necessary to ensure that m_workerGlobalScope isn't changed by
271 // WorkerThread::workerThread() while we're accessing it. Note also that stop() can
272 // be called before m_workerGlobalScope is fully created.
273 auto locker = Locker<Lock>::tryLock(m_threadCreationAndWorkerGlobalScopeMutex);
274 if (!locker) {
275 // The thread is still starting, spin the runloop and try again to avoid deadlocks if the worker thread
276 // needs to interact with the main thread during startup.
277 callOnMainThread([this, stoppedCallback = WTFMove(stoppedCallback)]() mutable {
278 stop(WTFMove(stoppedCallback));
279 });
280 return;
281 }
282
283 ASSERT(!m_stoppedCallback);
284 m_stoppedCallback = WTFMove(stoppedCallback);
285
286 // Ensure that tasks are being handled by thread event loop. If script execution weren't forbidden, a while(1) loop in JS could keep the thread alive forever.
287 if (m_workerGlobalScope) {
288 m_workerGlobalScope->script()->scheduleExecutionTermination();
289
290 m_runLoop.postTaskAndTerminate({ ScriptExecutionContext::Task::CleanupTask, [] (ScriptExecutionContext& context ) {
291 WorkerGlobalScope& workerGlobalScope = downcast<WorkerGlobalScope>(context);
292
293 workerGlobalScope.prepareForTermination();
294
295 // Stick a shutdown command at the end of the queue, so that we deal
296 // with all the cleanup tasks the databases post first.
297 workerGlobalScope.postTask({ ScriptExecutionContext::Task::CleanupTask, [] (ScriptExecutionContext& context) {
298 WorkerGlobalScope& workerGlobalScope = downcast<WorkerGlobalScope>(context);
299 // It's not safe to call clearScript until all the cleanup tasks posted by functions initiated by WorkerThreadShutdownStartTask have completed.
300 workerGlobalScope.clearScript();
301 } });
302
303 } });
304 return;
305 }
306 m_runLoop.terminate();
307}
308
309void WorkerThread::releaseFastMallocFreeMemoryInAllThreads()
310{
311 LockHolder lock(workerThreadsMutex());
312 for (auto* workerThread : workerThreads(lock)) {
313 workerThread->runLoop().postTask([] (ScriptExecutionContext&) {
314 WTF::releaseFastMallocFreeMemory();
315 });
316 }
317}
318
319IDBClient::IDBConnectionProxy* WorkerThread::idbConnectionProxy()
320{
321#if ENABLE(INDEXED_DATABASE)
322 return m_idbConnectionProxy.get();
323#else
324 return nullptr;
325#endif
326}
327
328SocketProvider* WorkerThread::socketProvider()
329{
330 return m_socketProvider.get();
331}
332
333} // namespace WebCore
334