1/* 2 * Copyright (C) 2008 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 COMPUTER, 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 COMPUTER, 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 29#if ENABLE(WORKERS) 30 31#include "WorkerThread.h" 32 33#include "DedicatedWorkerContext.h" 34#include "InspectorInstrumentation.h" 35#include "KURL.h" 36#include "ScriptSourceCode.h" 37#include "ScriptValue.h" 38#include "SecurityOrigin.h" 39#include "ThreadGlobalData.h" 40#include <utility> 41#include <wtf/Noncopyable.h> 42#include <wtf/text/WTFString.h> 43 44#if ENABLE(SQL_DATABASE) 45#include "DatabaseManager.h" 46#include "DatabaseTask.h" 47#endif 48 49namespace WebCore { 50 51static Mutex& threadSetMutex() 52{ 53 AtomicallyInitializedStatic(Mutex&, mutex = *new Mutex); 54 return mutex; 55} 56 57static HashSet<WorkerThread*>& workerThreads() 58{ 59 DEFINE_STATIC_LOCAL(HashSet<WorkerThread*>, threads, ()); 60 return threads; 61} 62 63unsigned WorkerThread::workerThreadCount() 64{ 65 MutexLocker lock(threadSetMutex()); 66 return workerThreads().size(); 67} 68 69struct WorkerThreadStartupData { 70 WTF_MAKE_NONCOPYABLE(WorkerThreadStartupData); WTF_MAKE_FAST_ALLOCATED; 71public: 72 static PassOwnPtr<WorkerThreadStartupData> create(const KURL& scriptURL, const String& userAgent, const GroupSettings* settings, const String& sourceCode, WorkerThreadStartMode startMode, const String& contentSecurityPolicy, ContentSecurityPolicy::HeaderType contentSecurityPolicyType, const SecurityOrigin* topOrigin) 73 { 74 return adoptPtr(new WorkerThreadStartupData(scriptURL, userAgent, settings, sourceCode, startMode, contentSecurityPolicy, contentSecurityPolicyType, topOrigin)); 75 } 76 77 KURL m_scriptURL; 78 String m_userAgent; 79 OwnPtr<GroupSettings> m_groupSettings; 80 String m_sourceCode; 81 WorkerThreadStartMode m_startMode; 82 String m_contentSecurityPolicy; 83 ContentSecurityPolicy::HeaderType m_contentSecurityPolicyType; 84 RefPtr<SecurityOrigin> m_topOrigin; 85private: 86 WorkerThreadStartupData(const KURL& scriptURL, const String& userAgent, const GroupSettings*, const String& sourceCode, WorkerThreadStartMode, const String& contentSecurityPolicy, ContentSecurityPolicy::HeaderType contentSecurityPolicyType, const SecurityOrigin* topOrigin); 87}; 88 89WorkerThreadStartupData::WorkerThreadStartupData(const KURL& scriptURL, const String& userAgent, const GroupSettings* settings, const String& sourceCode, WorkerThreadStartMode startMode, const String& contentSecurityPolicy, ContentSecurityPolicy::HeaderType contentSecurityPolicyType, const SecurityOrigin* topOrigin) 90 : m_scriptURL(scriptURL.copy()) 91 , m_userAgent(userAgent.isolatedCopy()) 92 , m_sourceCode(sourceCode.isolatedCopy()) 93 , m_startMode(startMode) 94 , m_contentSecurityPolicy(contentSecurityPolicy.isolatedCopy()) 95 , m_contentSecurityPolicyType(contentSecurityPolicyType) 96 , m_topOrigin(topOrigin ? topOrigin->isolatedCopy() : 0) 97{ 98 if (!settings) 99 return; 100 101 m_groupSettings = GroupSettings::create(); 102 m_groupSettings->setLocalStorageQuotaBytes(settings->localStorageQuotaBytes()); 103 m_groupSettings->setIndexedDBQuotaBytes(settings->indexedDBQuotaBytes()); 104 m_groupSettings->setIndexedDBDatabasePath(settings->indexedDBDatabasePath().isolatedCopy()); 105} 106 107WorkerThread::WorkerThread(const KURL& scriptURL, const String& userAgent, const GroupSettings* settings, const String& sourceCode, WorkerLoaderProxy& workerLoaderProxy, WorkerReportingProxy& workerReportingProxy, WorkerThreadStartMode startMode, const String& contentSecurityPolicy, ContentSecurityPolicy::HeaderType contentSecurityPolicyType, const SecurityOrigin* topOrigin) 108 : m_threadID(0) 109 , m_workerLoaderProxy(workerLoaderProxy) 110 , m_workerReportingProxy(workerReportingProxy) 111 , m_startupData(WorkerThreadStartupData::create(scriptURL, userAgent, settings, sourceCode, startMode, contentSecurityPolicy, contentSecurityPolicyType, topOrigin)) 112#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS) 113 , m_notificationClient(0) 114#endif 115{ 116 MutexLocker lock(threadSetMutex()); 117 workerThreads().add(this); 118} 119 120WorkerThread::~WorkerThread() 121{ 122 MutexLocker lock(threadSetMutex()); 123 ASSERT(workerThreads().contains(this)); 124 workerThreads().remove(this); 125} 126 127bool WorkerThread::start() 128{ 129 // Mutex protection is necessary to ensure that m_threadID is initialized when the thread starts. 130 MutexLocker lock(m_threadCreationMutex); 131 132 if (m_threadID) 133 return true; 134 135 m_threadID = createThread(WorkerThread::workerThreadStart, this, "WebCore: Worker"); 136 137 return m_threadID; 138} 139 140void WorkerThread::workerThreadStart(void* thread) 141{ 142 static_cast<WorkerThread*>(thread)->workerThread(); 143} 144 145void WorkerThread::workerThread() 146{ 147 { 148 MutexLocker lock(m_threadCreationMutex); 149 m_workerContext = createWorkerContext(m_startupData->m_scriptURL, m_startupData->m_userAgent, m_startupData->m_groupSettings.release(), m_startupData->m_contentSecurityPolicy, m_startupData->m_contentSecurityPolicyType, m_startupData->m_topOrigin.release()); 150 151 if (m_runLoop.terminated()) { 152 // The worker was terminated before the thread had a chance to run. Since the context didn't exist yet, 153 // forbidExecution() couldn't be called from stop(). 154 m_workerContext->script()->forbidExecution(); 155 } 156 } 157 158 WorkerScriptController* script = m_workerContext->script(); 159#if ENABLE(INSPECTOR) 160 InspectorInstrumentation::willEvaluateWorkerScript(workerContext(), m_startupData->m_startMode); 161#endif 162 script->evaluate(ScriptSourceCode(m_startupData->m_sourceCode, m_startupData->m_scriptURL)); 163 // Free the startup data to cause its member variable deref's happen on the worker's thread (since 164 // all ref/derefs of these objects are happening on the thread at this point). Note that 165 // WorkerThread::~WorkerThread happens on a different thread where it was created. 166 m_startupData.clear(); 167 168 runEventLoop(); 169 170 ThreadIdentifier threadID = m_threadID; 171 172 ASSERT(m_workerContext->hasOneRef()); 173 174 // The below assignment will destroy the context, which will in turn notify messaging proxy. 175 // We cannot let any objects survive past thread exit, because no other thread will run GC or otherwise destroy them. 176 m_workerContext = 0; 177 178 // Clean up WebCore::ThreadGlobalData before WTF::WTFThreadData goes away! 179 threadGlobalData().destroy(); 180 181 // The thread object may be already destroyed from notification now, don't try to access "this". 182 detachThread(threadID); 183} 184 185void WorkerThread::runEventLoop() 186{ 187 // Does not return until terminated. 188 m_runLoop.run(m_workerContext.get()); 189} 190 191class WorkerThreadShutdownFinishTask : public ScriptExecutionContext::Task { 192public: 193 static PassOwnPtr<WorkerThreadShutdownFinishTask> create() 194 { 195 return adoptPtr(new WorkerThreadShutdownFinishTask()); 196 } 197 198 virtual void performTask(ScriptExecutionContext *context) 199 { 200 ASSERT_WITH_SECURITY_IMPLICATION(context->isWorkerContext()); 201 WorkerContext* workerContext = static_cast<WorkerContext*>(context); 202#if ENABLE(INSPECTOR) 203 workerContext->clearInspector(); 204#endif 205 // It's not safe to call clearScript until all the cleanup tasks posted by functions initiated by WorkerThreadShutdownStartTask have completed. 206 workerContext->clearScript(); 207 } 208 209 virtual bool isCleanupTask() const { return true; } 210}; 211 212class WorkerThreadShutdownStartTask : public ScriptExecutionContext::Task { 213public: 214 static PassOwnPtr<WorkerThreadShutdownStartTask> create() 215 { 216 return adoptPtr(new WorkerThreadShutdownStartTask()); 217 } 218 219 virtual void performTask(ScriptExecutionContext *context) 220 { 221 ASSERT_WITH_SECURITY_IMPLICATION(context->isWorkerContext()); 222 WorkerContext* workerContext = static_cast<WorkerContext*>(context); 223 224#if ENABLE(SQL_DATABASE) 225 // FIXME: Should we stop the databases as part of stopActiveDOMObjects() below? 226 DatabaseTaskSynchronizer cleanupSync; 227 DatabaseManager::manager().stopDatabases(workerContext, &cleanupSync); 228#endif 229 230 workerContext->stopActiveDOMObjects(); 231 232 workerContext->notifyObserversOfStop(); 233 234 // Event listeners would keep DOMWrapperWorld objects alive for too long. Also, they have references to JS objects, 235 // which become dangling once Heap is destroyed. 236 workerContext->removeAllEventListeners(); 237 238#if ENABLE(SQL_DATABASE) 239 // We wait for the database thread to clean up all its stuff so that we 240 // can do more stringent leak checks as we exit. 241 cleanupSync.waitForTaskCompletion(); 242#endif 243 244 // Stick a shutdown command at the end of the queue, so that we deal 245 // with all the cleanup tasks the databases post first. 246 workerContext->postTask(WorkerThreadShutdownFinishTask::create()); 247 } 248 249 virtual bool isCleanupTask() const { return true; } 250}; 251 252void WorkerThread::stop() 253{ 254 // Mutex protection is necessary because stop() can be called before the context is fully created. 255 MutexLocker lock(m_threadCreationMutex); 256 257 // 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. 258 if (m_workerContext) { 259 m_workerContext->script()->scheduleExecutionTermination(); 260 261#if ENABLE(SQL_DATABASE) 262 DatabaseManager::manager().interruptAllDatabasesForContext(m_workerContext.get()); 263#endif 264 m_runLoop.postTaskAndTerminate(WorkerThreadShutdownStartTask::create()); 265 return; 266 } 267 m_runLoop.terminate(); 268} 269 270class ReleaseFastMallocFreeMemoryTask : public ScriptExecutionContext::Task { 271 virtual void performTask(ScriptExecutionContext*) OVERRIDE { WTF::releaseFastMallocFreeMemory(); } 272}; 273 274void WorkerThread::releaseFastMallocFreeMemoryInAllThreads() 275{ 276 MutexLocker lock(threadSetMutex()); 277 HashSet<WorkerThread*>& threads = workerThreads(); 278 HashSet<WorkerThread*>::iterator end = threads.end(); 279 for (HashSet<WorkerThread*>::iterator it = threads.begin(); it != end; ++it) 280 (*it)->runLoop().postTask(adoptPtr(new ReleaseFastMallocFreeMemoryTask)); 281} 282 283} // namespace WebCore 284 285#endif // ENABLE(WORKERS) 286