1 /* 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. 3 * Copyright (C) 2009, 2011 Google Inc. All Rights Reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 * 26 */ 27 28 #include "config.h" 29 30 #if ENABLE(WORKERS) 31 32 #include "WorkerContext.h" 33 34 #include "AbstractDatabase.h" 35 #include "ActiveDOMObject.h" 36 #include "Database.h" 37 #include "DatabaseCallback.h" 38 #include "DatabaseSync.h" 39 #include "DatabaseTracker.h" 40 #include "DOMTimer.h" 41 #include "DOMURL.h" 42 #include "DOMWindow.h" 43 #include "ErrorEvent.h" 44 #include "Event.h" 45 #include "EventException.h" 46 #include "InspectorInstrumentation.h" 47 #include "KURL.h" 48 #include "MessagePort.h" 49 #include "NotImplemented.h" 50 #include "ScriptCallStack.h" 51 #include "ScriptSourceCode.h" 52 #include "ScriptValue.h" 53 #include "SecurityOrigin.h" 54 #include "WorkerInspectorController.h" 55 #include "WorkerLocation.h" 56 #include "WorkerNavigator.h" 57 #include "WorkerObjectProxy.h" 58 #include "WorkerScriptLoader.h" 59 #include "WorkerThread.h" 60 #include "WorkerThreadableLoader.h" 61 #include "XMLHttpRequestException.h" 62 #include <wtf/RefPtr.h> 63 #include <wtf/UnusedParam.h> 64 65 #if ENABLE(NOTIFICATIONS) 66 #include "NotificationCenter.h" 67 #endif 68 69 #if ENABLE(FILE_SYSTEM) 70 #include "AsyncFileSystem.h" 71 #include "DirectoryEntrySync.h" 72 #include "DOMFileSystem.h" 73 #include "DOMFileSystemBase.h" 74 #include "DOMFileSystemSync.h" 75 #include "ErrorCallback.h" 76 #include "FileEntrySync.h" 77 #include "FileError.h" 78 #include "FileException.h" 79 #include "FileSystemCallback.h" 80 #include "FileSystemCallbacks.h" 81 #include "LocalFileSystem.h" 82 #include "SyncCallbackHelper.h" 83 #endif 84 85 namespace WebCore { 86 87 class CloseWorkerContextTask : public ScriptExecutionContext::Task { 88 public: 89 static PassOwnPtr<CloseWorkerContextTask> create() 90 { 91 return new CloseWorkerContextTask; 92 } 93 94 virtual void performTask(ScriptExecutionContext *context) 95 { 96 ASSERT(context->isWorkerContext()); 97 WorkerContext* workerContext = static_cast<WorkerContext*>(context); 98 // Notify parent that this context is closed. Parent is responsible for calling WorkerThread::stop(). 99 workerContext->thread()->workerReportingProxy().workerContextClosed(); 100 } 101 102 virtual bool isCleanupTask() const { return true; } 103 }; 104 105 WorkerContext::WorkerContext(const KURL& url, const String& userAgent, WorkerThread* thread) 106 : m_url(url) 107 , m_userAgent(userAgent) 108 , m_script(new WorkerScriptController(this)) 109 , m_thread(thread) 110 #if ENABLE(INSPECTOR) 111 , m_workerInspectorController(new WorkerInspectorController(this)) 112 #endif 113 , m_closing(false) 114 { 115 setSecurityOrigin(SecurityOrigin::create(url)); 116 } 117 118 WorkerContext::~WorkerContext() 119 { 120 ASSERT(currentThread() == thread()->threadID()); 121 #if ENABLE(NOTIFICATIONS) 122 m_notifications.clear(); 123 #endif 124 125 // Make sure we have no observers. 126 notifyObserversOfStop(); 127 128 // Notify proxy that we are going away. This can free the WorkerThread object, so do not access it after this. 129 thread()->workerReportingProxy().workerContextDestroyed(); 130 } 131 132 ScriptExecutionContext* WorkerContext::scriptExecutionContext() const 133 { 134 return const_cast<WorkerContext*>(this); 135 } 136 137 const KURL& WorkerContext::virtualURL() const 138 { 139 return m_url; 140 } 141 142 KURL WorkerContext::virtualCompleteURL(const String& url) const 143 { 144 return completeURL(url); 145 } 146 147 KURL WorkerContext::completeURL(const String& url) const 148 { 149 // Always return a null URL when passed a null string. 150 // FIXME: Should we change the KURL constructor to have this behavior? 151 if (url.isNull()) 152 return KURL(); 153 // Always use UTF-8 in Workers. 154 return KURL(m_url, url); 155 } 156 157 String WorkerContext::userAgent(const KURL&) const 158 { 159 return m_userAgent; 160 } 161 162 WorkerLocation* WorkerContext::location() const 163 { 164 if (!m_location) 165 m_location = WorkerLocation::create(m_url); 166 return m_location.get(); 167 } 168 169 void WorkerContext::close() 170 { 171 if (m_closing) 172 return; 173 174 // Let current script run to completion but prevent future script evaluations. 175 // After m_closing is set, all the tasks in the queue continue to be fetched but only 176 // tasks with isCleanupTask()==true will be executed. 177 m_closing = true; 178 postTask(CloseWorkerContextTask::create()); 179 } 180 181 WorkerNavigator* WorkerContext::navigator() const 182 { 183 if (!m_navigator) 184 m_navigator = WorkerNavigator::create(m_userAgent); 185 return m_navigator.get(); 186 } 187 188 bool WorkerContext::hasPendingActivity() const 189 { 190 ActiveDOMObjectsMap& activeObjects = activeDOMObjects(); 191 ActiveDOMObjectsMap::const_iterator activeObjectsEnd = activeObjects.end(); 192 for (ActiveDOMObjectsMap::const_iterator iter = activeObjects.begin(); iter != activeObjectsEnd; ++iter) { 193 if (iter->first->hasPendingActivity()) 194 return true; 195 } 196 197 // Keep the worker active as long as there is a MessagePort with pending activity or that is remotely entangled. 198 HashSet<MessagePort*>::const_iterator messagePortsEnd = messagePorts().end(); 199 for (HashSet<MessagePort*>::const_iterator iter = messagePorts().begin(); iter != messagePortsEnd; ++iter) { 200 if ((*iter)->hasPendingActivity() || ((*iter)->isEntangled() && !(*iter)->locallyEntangledPort())) 201 return true; 202 } 203 204 return false; 205 } 206 207 void WorkerContext::postTask(PassOwnPtr<Task> task) 208 { 209 thread()->runLoop().postTask(task); 210 } 211 212 int WorkerContext::setTimeout(PassOwnPtr<ScheduledAction> action, int timeout) 213 { 214 return DOMTimer::install(scriptExecutionContext(), action, timeout, true); 215 } 216 217 void WorkerContext::clearTimeout(int timeoutId) 218 { 219 DOMTimer::removeById(scriptExecutionContext(), timeoutId); 220 } 221 222 int WorkerContext::setInterval(PassOwnPtr<ScheduledAction> action, int timeout) 223 { 224 return DOMTimer::install(scriptExecutionContext(), action, timeout, false); 225 } 226 227 void WorkerContext::clearInterval(int timeoutId) 228 { 229 DOMTimer::removeById(scriptExecutionContext(), timeoutId); 230 } 231 232 void WorkerContext::importScripts(const Vector<String>& urls, ExceptionCode& ec) 233 { 234 ec = 0; 235 Vector<String>::const_iterator urlsEnd = urls.end(); 236 Vector<KURL> completedURLs; 237 for (Vector<String>::const_iterator it = urls.begin(); it != urlsEnd; ++it) { 238 const KURL& url = scriptExecutionContext()->completeURL(*it); 239 if (!url.isValid()) { 240 ec = SYNTAX_ERR; 241 return; 242 } 243 completedURLs.append(url); 244 } 245 Vector<KURL>::const_iterator end = completedURLs.end(); 246 247 for (Vector<KURL>::const_iterator it = completedURLs.begin(); it != end; ++it) { 248 WorkerScriptLoader scriptLoader(ResourceRequestBase::TargetIsScript); 249 scriptLoader.loadSynchronously(scriptExecutionContext(), *it, AllowCrossOriginRequests); 250 251 // If the fetching attempt failed, throw a NETWORK_ERR exception and abort all these steps. 252 if (scriptLoader.failed()) { 253 ec = XMLHttpRequestException::NETWORK_ERR; 254 return; 255 } 256 257 InspectorInstrumentation::scriptImported(scriptExecutionContext(), scriptLoader.identifier(), scriptLoader.script()); 258 259 ScriptValue exception; 260 m_script->evaluate(ScriptSourceCode(scriptLoader.script(), scriptLoader.responseURL()), &exception); 261 if (!exception.hasNoValue()) { 262 m_script->setException(exception); 263 return; 264 } 265 } 266 } 267 268 EventTarget* WorkerContext::errorEventTarget() 269 { 270 return this; 271 } 272 273 void WorkerContext::logExceptionToConsole(const String& errorMessage, int lineNumber, const String& sourceURL, PassRefPtr<ScriptCallStack>) 274 { 275 thread()->workerReportingProxy().postExceptionToWorkerObject(errorMessage, lineNumber, sourceURL); 276 } 277 278 void WorkerContext::addMessage(MessageSource source, MessageType type, MessageLevel level, const String& message, unsigned lineNumber, const String& sourceURL, PassRefPtr<ScriptCallStack>) 279 { 280 thread()->workerReportingProxy().postConsoleMessageToWorkerObject(source, type, level, message, lineNumber, sourceURL); 281 } 282 283 #if ENABLE(NOTIFICATIONS) 284 NotificationCenter* WorkerContext::webkitNotifications() const 285 { 286 if (!m_notifications) 287 m_notifications = NotificationCenter::create(scriptExecutionContext(), m_thread->getNotificationPresenter()); 288 return m_notifications.get(); 289 } 290 #endif 291 292 #if ENABLE(DATABASE) 293 PassRefPtr<Database> WorkerContext::openDatabase(const String& name, const String& version, const String& displayName, unsigned long estimatedSize, PassRefPtr<DatabaseCallback> creationCallback, ExceptionCode& ec) 294 { 295 if (!securityOrigin()->canAccessDatabase() || !AbstractDatabase::isAvailable()) { 296 ec = SECURITY_ERR; 297 return 0; 298 } 299 300 return Database::openDatabase(this, name, version, displayName, estimatedSize, creationCallback, ec); 301 } 302 303 void WorkerContext::databaseExceededQuota(const String&) 304 { 305 #if !PLATFORM(CHROMIUM) 306 // FIXME: This needs a real implementation; this is a temporary solution for testing. 307 const unsigned long long defaultQuota = 5 * 1024 * 1024; 308 DatabaseTracker::tracker().setQuota(securityOrigin(), defaultQuota); 309 #endif 310 } 311 312 PassRefPtr<DatabaseSync> WorkerContext::openDatabaseSync(const String& name, const String& version, const String& displayName, unsigned long estimatedSize, PassRefPtr<DatabaseCallback> creationCallback, ExceptionCode& ec) 313 { 314 if (!securityOrigin()->canAccessDatabase() || !AbstractDatabase::isAvailable()) { 315 ec = SECURITY_ERR; 316 return 0; 317 } 318 319 return DatabaseSync::openDatabaseSync(this, name, version, displayName, estimatedSize, creationCallback, ec); 320 } 321 #endif 322 323 bool WorkerContext::isContextThread() const 324 { 325 return currentThread() == thread()->threadID(); 326 } 327 328 bool WorkerContext::isJSExecutionForbidden() const 329 { 330 return m_script->isExecutionForbidden(); 331 } 332 333 EventTargetData* WorkerContext::eventTargetData() 334 { 335 return &m_eventTargetData; 336 } 337 338 EventTargetData* WorkerContext::ensureEventTargetData() 339 { 340 return &m_eventTargetData; 341 } 342 343 #if ENABLE(BLOB) 344 DOMURL* WorkerContext::webkitURL() const 345 { 346 if (!m_domURL) 347 m_domURL = DOMURL::create(this->scriptExecutionContext()); 348 return m_domURL.get(); 349 } 350 #endif 351 352 #if ENABLE(FILE_SYSTEM) 353 void WorkerContext::webkitRequestFileSystem(int type, long long size, PassRefPtr<FileSystemCallback> successCallback, PassRefPtr<ErrorCallback> errorCallback) 354 { 355 if (!AsyncFileSystem::isAvailable() || !securityOrigin()->canAccessFileSystem()) { 356 DOMFileSystem::scheduleCallback(this, errorCallback, FileError::create(FileError::SECURITY_ERR)); 357 return; 358 } 359 360 AsyncFileSystem::Type fileSystemType = static_cast<AsyncFileSystem::Type>(type); 361 if (fileSystemType != AsyncFileSystem::Temporary && fileSystemType != AsyncFileSystem::Persistent && fileSystemType != AsyncFileSystem::External) { 362 DOMFileSystem::scheduleCallback(this, errorCallback, FileError::create(FileError::INVALID_MODIFICATION_ERR)); 363 return; 364 } 365 366 LocalFileSystem::localFileSystem().requestFileSystem(this, fileSystemType, size, FileSystemCallbacks::create(successCallback, errorCallback, this), false); 367 } 368 369 PassRefPtr<DOMFileSystemSync> WorkerContext::webkitRequestFileSystemSync(int type, long long size, ExceptionCode& ec) 370 { 371 ec = 0; 372 if (!AsyncFileSystem::isAvailable() || !securityOrigin()->canAccessFileSystem()) { 373 ec = FileException::SECURITY_ERR; 374 return 0; 375 } 376 377 AsyncFileSystem::Type fileSystemType = static_cast<AsyncFileSystem::Type>(type); 378 if (fileSystemType != AsyncFileSystem::Temporary && fileSystemType != AsyncFileSystem::Persistent && fileSystemType != AsyncFileSystem::External) { 379 ec = FileException::INVALID_MODIFICATION_ERR; 380 return 0; 381 } 382 383 FileSystemSyncCallbackHelper helper; 384 LocalFileSystem::localFileSystem().requestFileSystem(this, fileSystemType, size, FileSystemCallbacks::create(helper.successCallback(), helper.errorCallback(), this), true); 385 return helper.getResult(ec); 386 } 387 388 void WorkerContext::webkitResolveLocalFileSystemURL(const String& url, PassRefPtr<EntryCallback> successCallback, PassRefPtr<ErrorCallback> errorCallback) 389 { 390 KURL completedURL = completeURL(url); 391 if (!AsyncFileSystem::isAvailable() || !securityOrigin()->canAccessFileSystem() || !securityOrigin()->canRequest(completedURL)) { 392 DOMFileSystem::scheduleCallback(this, errorCallback, FileError::create(FileError::SECURITY_ERR)); 393 return; 394 } 395 396 AsyncFileSystem::Type type; 397 String filePath; 398 if (!completedURL.isValid() || !DOMFileSystemBase::crackFileSystemURL(completedURL, type, filePath)) { 399 DOMFileSystem::scheduleCallback(this, errorCallback, FileError::create(FileError::ENCODING_ERR)); 400 return; 401 } 402 403 LocalFileSystem::localFileSystem().readFileSystem(this, type, ResolveURICallbacks::create(successCallback, errorCallback, this, filePath)); 404 } 405 406 PassRefPtr<EntrySync> WorkerContext::webkitResolveLocalFileSystemSyncURL(const String& url, ExceptionCode& ec) 407 { 408 ec = 0; 409 KURL completedURL = completeURL(url); 410 if (!AsyncFileSystem::isAvailable() || !securityOrigin()->canAccessFileSystem() || !securityOrigin()->canRequest(completedURL)) { 411 ec = FileException::SECURITY_ERR; 412 return 0; 413 } 414 415 AsyncFileSystem::Type type; 416 String filePath; 417 if (!completedURL.isValid() || !DOMFileSystemBase::crackFileSystemURL(completedURL, type, filePath)) { 418 ec = FileException::ENCODING_ERR; 419 return 0; 420 } 421 422 FileSystemSyncCallbackHelper readFileSystemHelper; 423 LocalFileSystem::localFileSystem().readFileSystem(this, type, FileSystemCallbacks::create(readFileSystemHelper.successCallback(), readFileSystemHelper.errorCallback(), this), true); 424 RefPtr<DOMFileSystemSync> fileSystem = readFileSystemHelper.getResult(ec); 425 if (!fileSystem) 426 return 0; 427 428 RefPtr<EntrySync> entry = fileSystem->root()->getDirectory(filePath, 0, ec); 429 if (ec == FileException::TYPE_MISMATCH_ERR) 430 return fileSystem->root()->getFile(filePath, 0, ec); 431 432 return entry.release(); 433 } 434 435 COMPILE_ASSERT(static_cast<int>(WorkerContext::TEMPORARY) == static_cast<int>(AsyncFileSystem::Temporary), enum_mismatch); 436 COMPILE_ASSERT(static_cast<int>(WorkerContext::PERSISTENT) == static_cast<int>(AsyncFileSystem::Persistent), enum_mismatch); 437 COMPILE_ASSERT(static_cast<int>(WorkerContext::EXTERNAL) == static_cast<int>(AsyncFileSystem::External), enum_mismatch); 438 #endif 439 440 WorkerContext::Observer::Observer(WorkerContext* context) 441 : m_context(context) 442 { 443 ASSERT(m_context && m_context->isContextThread()); 444 m_context->registerObserver(this); 445 } 446 447 WorkerContext::Observer::~Observer() 448 { 449 if (!m_context) 450 return; 451 ASSERT(m_context->isContextThread()); 452 m_context->unregisterObserver(this); 453 } 454 455 void WorkerContext::Observer::stopObserving() 456 { 457 if (!m_context) 458 return; 459 ASSERT(m_context->isContextThread()); 460 m_context->unregisterObserver(this); 461 m_context = 0; 462 } 463 464 void WorkerContext::registerObserver(Observer* observer) 465 { 466 ASSERT(observer); 467 m_workerObservers.add(observer); 468 } 469 470 void WorkerContext::unregisterObserver(Observer* observer) 471 { 472 ASSERT(observer); 473 m_workerObservers.remove(observer); 474 } 475 476 void WorkerContext::notifyObserversOfStop() 477 { 478 HashSet<Observer*>::iterator iter = m_workerObservers.begin(); 479 while (iter != m_workerObservers.end()) { 480 WorkerContext::Observer* observer = *iter; 481 observer->stopObserving(); 482 observer->notifyStop(); 483 iter = m_workerObservers.begin(); 484 } 485 } 486 487 } // namespace WebCore 488 489 #endif // ENABLE(WORKERS) 490