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 #include "ScriptExecutionContext.h" 29 30 #include "ActiveDOMObject.h" 31 #include "Blob.h" 32 #include "BlobURL.h" 33 #include "DOMTimer.h" 34 #include "DOMURL.h" 35 #include "Database.h" 36 #include "DatabaseTask.h" 37 #include "DatabaseThread.h" 38 #include "ErrorEvent.h" 39 #include "EventListener.h" 40 #include "EventTarget.h" 41 #include "FileThread.h" 42 #include "MessagePort.h" 43 #include "ScriptCallStack.h" 44 #include "SecurityOrigin.h" 45 #include "Settings.h" 46 #include "ThreadableBlobRegistry.h" 47 #include "WorkerContext.h" 48 #include "WorkerThread.h" 49 #include <wtf/MainThread.h> 50 #include <wtf/PassRefPtr.h> 51 #include <wtf/Vector.h> 52 53 #if USE(JSC) 54 #include "JSDOMWindow.h" 55 #endif 56 57 namespace WebCore { 58 59 class ProcessMessagesSoonTask : public ScriptExecutionContext::Task { 60 public: 61 static PassOwnPtr<ProcessMessagesSoonTask> create() 62 { 63 return new ProcessMessagesSoonTask; 64 } 65 66 virtual void performTask(ScriptExecutionContext* context) 67 { 68 context->dispatchMessagePortEvents(); 69 } 70 }; 71 72 class ScriptExecutionContext::PendingException { 73 WTF_MAKE_NONCOPYABLE(PendingException); 74 public: 75 PendingException(const String& errorMessage, int lineNumber, const String& sourceURL, PassRefPtr<ScriptCallStack> callStack) 76 : m_errorMessage(errorMessage) 77 , m_lineNumber(lineNumber) 78 , m_sourceURL(sourceURL) 79 , m_callStack(callStack) 80 { 81 } 82 String m_errorMessage; 83 int m_lineNumber; 84 String m_sourceURL; 85 RefPtr<ScriptCallStack> m_callStack; 86 }; 87 88 ScriptExecutionContext::ScriptExecutionContext() 89 : m_iteratingActiveDOMObjects(false) 90 , m_inDestructor(false) 91 , m_inDispatchErrorEvent(false) 92 #if ENABLE(DATABASE) 93 , m_hasOpenDatabases(false) 94 #endif 95 { 96 } 97 98 ScriptExecutionContext::~ScriptExecutionContext() 99 { 100 m_inDestructor = true; 101 for (HashMap<ActiveDOMObject*, void*>::iterator iter = m_activeDOMObjects.begin(); iter != m_activeDOMObjects.end(); iter = m_activeDOMObjects.begin()) { 102 ActiveDOMObject* object = iter->first; 103 m_activeDOMObjects.remove(iter); 104 ASSERT(object->scriptExecutionContext() == this); 105 object->contextDestroyed(); 106 } 107 108 HashSet<MessagePort*>::iterator messagePortsEnd = m_messagePorts.end(); 109 for (HashSet<MessagePort*>::iterator iter = m_messagePorts.begin(); iter != messagePortsEnd; ++iter) { 110 ASSERT((*iter)->scriptExecutionContext() == this); 111 (*iter)->contextDestroyed(); 112 } 113 #if ENABLE(DATABASE) 114 if (m_databaseThread) { 115 ASSERT(m_databaseThread->terminationRequested()); 116 m_databaseThread = 0; 117 } 118 #endif 119 #if ENABLE(BLOB) || ENABLE(FILE_SYSTEM) 120 if (m_fileThread) { 121 m_fileThread->stop(); 122 m_fileThread = 0; 123 } 124 #endif 125 126 #if ENABLE(BLOB) 127 HashSet<String>::iterator publicBlobURLsEnd = m_publicBlobURLs.end(); 128 for (HashSet<String>::iterator iter = m_publicBlobURLs.begin(); iter != publicBlobURLsEnd; ++iter) 129 ThreadableBlobRegistry::unregisterBlobURL(KURL(ParsedURLString, *iter)); 130 131 HashSet<DOMURL*>::iterator domUrlsEnd = m_domUrls.end(); 132 for (HashSet<DOMURL*>::iterator iter = m_domUrls.begin(); iter != domUrlsEnd; ++iter) { 133 ASSERT((*iter)->scriptExecutionContext() == this); 134 (*iter)->contextDestroyed(); 135 } 136 #endif 137 } 138 139 #if ENABLE(DATABASE) 140 141 DatabaseThread* ScriptExecutionContext::databaseThread() 142 { 143 if (!m_databaseThread && !m_hasOpenDatabases) { 144 // Create the database thread on first request - but not if at least one database was already opened, 145 // because in that case we already had a database thread and terminated it and should not create another. 146 m_databaseThread = DatabaseThread::create(); 147 if (!m_databaseThread->start()) 148 m_databaseThread = 0; 149 } 150 151 return m_databaseThread.get(); 152 } 153 154 void ScriptExecutionContext::stopDatabases(DatabaseTaskSynchronizer* cleanupSync) 155 { 156 ASSERT(isContextThread()); 157 if (m_databaseThread) 158 m_databaseThread->requestTermination(cleanupSync); 159 else if (cleanupSync) 160 cleanupSync->taskCompleted(); 161 } 162 163 #endif 164 165 void ScriptExecutionContext::processMessagePortMessagesSoon() 166 { 167 postTask(ProcessMessagesSoonTask::create()); 168 } 169 170 void ScriptExecutionContext::dispatchMessagePortEvents() 171 { 172 RefPtr<ScriptExecutionContext> protect(this); 173 174 // Make a frozen copy. 175 Vector<MessagePort*> ports; 176 copyToVector(m_messagePorts, ports); 177 178 unsigned portCount = ports.size(); 179 for (unsigned i = 0; i < portCount; ++i) { 180 MessagePort* port = ports[i]; 181 // The port may be destroyed, and another one created at the same address, but this is safe, as the worst that can happen 182 // as a result is that dispatchMessages() will be called needlessly. 183 if (m_messagePorts.contains(port) && port->started()) 184 port->dispatchMessages(); 185 } 186 } 187 188 void ScriptExecutionContext::createdMessagePort(MessagePort* port) 189 { 190 ASSERT(port); 191 #if ENABLE(WORKERS) 192 ASSERT((isDocument() && isMainThread()) 193 || (isWorkerContext() && currentThread() == static_cast<WorkerContext*>(this)->thread()->threadID())); 194 #endif 195 196 m_messagePorts.add(port); 197 } 198 199 void ScriptExecutionContext::destroyedMessagePort(MessagePort* port) 200 { 201 ASSERT(port); 202 #if ENABLE(WORKERS) 203 ASSERT((isDocument() && isMainThread()) 204 || (isWorkerContext() && currentThread() == static_cast<WorkerContext*>(this)->thread()->threadID())); 205 #endif 206 207 m_messagePorts.remove(port); 208 } 209 210 #if ENABLE(BLOB) 211 void ScriptExecutionContext::createdDomUrl(DOMURL* url) 212 { 213 ASSERT(url); 214 m_domUrls.add(url); 215 } 216 217 void ScriptExecutionContext::destroyedDomUrl(DOMURL* url) 218 { 219 ASSERT(url); 220 m_domUrls.remove(url); 221 } 222 #endif 223 224 bool ScriptExecutionContext::canSuspendActiveDOMObjects() 225 { 226 // No protection against m_activeDOMObjects changing during iteration: canSuspend() shouldn't execute arbitrary JS. 227 m_iteratingActiveDOMObjects = true; 228 HashMap<ActiveDOMObject*, void*>::iterator activeObjectsEnd = m_activeDOMObjects.end(); 229 for (HashMap<ActiveDOMObject*, void*>::iterator iter = m_activeDOMObjects.begin(); iter != activeObjectsEnd; ++iter) { 230 ASSERT(iter->first->scriptExecutionContext() == this); 231 if (!iter->first->canSuspend()) { 232 m_iteratingActiveDOMObjects = false; 233 return false; 234 } 235 } 236 m_iteratingActiveDOMObjects = false; 237 return true; 238 } 239 240 void ScriptExecutionContext::suspendActiveDOMObjects(ActiveDOMObject::ReasonForSuspension why) 241 { 242 // No protection against m_activeDOMObjects changing during iteration: suspend() shouldn't execute arbitrary JS. 243 m_iteratingActiveDOMObjects = true; 244 HashMap<ActiveDOMObject*, void*>::iterator activeObjectsEnd = m_activeDOMObjects.end(); 245 for (HashMap<ActiveDOMObject*, void*>::iterator iter = m_activeDOMObjects.begin(); iter != activeObjectsEnd; ++iter) { 246 ASSERT(iter->first->scriptExecutionContext() == this); 247 iter->first->suspend(why); 248 } 249 m_iteratingActiveDOMObjects = false; 250 } 251 252 void ScriptExecutionContext::resumeActiveDOMObjects() 253 { 254 // No protection against m_activeDOMObjects changing during iteration: resume() shouldn't execute arbitrary JS. 255 m_iteratingActiveDOMObjects = true; 256 HashMap<ActiveDOMObject*, void*>::iterator activeObjectsEnd = m_activeDOMObjects.end(); 257 for (HashMap<ActiveDOMObject*, void*>::iterator iter = m_activeDOMObjects.begin(); iter != activeObjectsEnd; ++iter) { 258 ASSERT(iter->first->scriptExecutionContext() == this); 259 iter->first->resume(); 260 } 261 m_iteratingActiveDOMObjects = false; 262 } 263 264 void ScriptExecutionContext::stopActiveDOMObjects() 265 { 266 // No protection against m_activeDOMObjects changing during iteration: stop() shouldn't execute arbitrary JS. 267 m_iteratingActiveDOMObjects = true; 268 HashMap<ActiveDOMObject*, void*>::iterator activeObjectsEnd = m_activeDOMObjects.end(); 269 for (HashMap<ActiveDOMObject*, void*>::iterator iter = m_activeDOMObjects.begin(); iter != activeObjectsEnd; ++iter) { 270 ASSERT(iter->first->scriptExecutionContext() == this); 271 iter->first->stop(); 272 } 273 m_iteratingActiveDOMObjects = false; 274 275 // Also close MessagePorts. If they were ActiveDOMObjects (they could be) then they could be stopped instead. 276 closeMessagePorts(); 277 } 278 279 void ScriptExecutionContext::createdActiveDOMObject(ActiveDOMObject* object, void* upcastPointer) 280 { 281 ASSERT(object); 282 ASSERT(upcastPointer); 283 ASSERT(!m_inDestructor); 284 if (m_iteratingActiveDOMObjects) 285 CRASH(); 286 m_activeDOMObjects.add(object, upcastPointer); 287 } 288 289 void ScriptExecutionContext::destroyedActiveDOMObject(ActiveDOMObject* object) 290 { 291 ASSERT(object); 292 if (m_iteratingActiveDOMObjects) 293 CRASH(); 294 m_activeDOMObjects.remove(object); 295 } 296 297 void ScriptExecutionContext::closeMessagePorts() { 298 HashSet<MessagePort*>::iterator messagePortsEnd = m_messagePorts.end(); 299 for (HashSet<MessagePort*>::iterator iter = m_messagePorts.begin(); iter != messagePortsEnd; ++iter) { 300 ASSERT((*iter)->scriptExecutionContext() == this); 301 (*iter)->close(); 302 } 303 } 304 305 void ScriptExecutionContext::setSecurityOrigin(PassRefPtr<SecurityOrigin> securityOrigin) 306 { 307 m_securityOrigin = securityOrigin; 308 } 309 310 bool ScriptExecutionContext::sanitizeScriptError(String& errorMessage, int& lineNumber, String& sourceURL) 311 { 312 KURL targetURL = completeURL(sourceURL); 313 if (securityOrigin()->canRequest(targetURL)) 314 return false; 315 errorMessage = "Script error."; 316 sourceURL = String(); 317 lineNumber = 0; 318 return true; 319 } 320 321 void ScriptExecutionContext::reportException(const String& errorMessage, int lineNumber, const String& sourceURL, PassRefPtr<ScriptCallStack> callStack) 322 { 323 if (m_inDispatchErrorEvent) { 324 if (!m_pendingExceptions) 325 m_pendingExceptions = adoptPtr(new Vector<OwnPtr<PendingException> >()); 326 m_pendingExceptions->append(adoptPtr(new PendingException(errorMessage, lineNumber, sourceURL, callStack))); 327 return; 328 } 329 330 // First report the original exception and only then all the nested ones. 331 if (!dispatchErrorEvent(errorMessage, lineNumber, sourceURL)) 332 logExceptionToConsole(errorMessage, lineNumber, sourceURL, callStack); 333 334 if (!m_pendingExceptions) 335 return; 336 337 for (size_t i = 0; i < m_pendingExceptions->size(); i++) { 338 PendingException* e = m_pendingExceptions->at(i).get(); 339 logExceptionToConsole(e->m_errorMessage, e->m_lineNumber, e->m_sourceURL, e->m_callStack); 340 } 341 m_pendingExceptions.clear(); 342 } 343 344 bool ScriptExecutionContext::dispatchErrorEvent(const String& errorMessage, int lineNumber, const String& sourceURL) 345 { 346 EventTarget* target = errorEventTarget(); 347 if (!target) 348 return false; 349 350 String message = errorMessage; 351 int line = lineNumber; 352 String sourceName = sourceURL; 353 sanitizeScriptError(message, line, sourceName); 354 355 ASSERT(!m_inDispatchErrorEvent); 356 m_inDispatchErrorEvent = true; 357 RefPtr<ErrorEvent> errorEvent = ErrorEvent::create(message, sourceName, line); 358 target->dispatchEvent(errorEvent); 359 m_inDispatchErrorEvent = false; 360 return errorEvent->defaultPrevented(); 361 } 362 363 void ScriptExecutionContext::addTimeout(int timeoutId, DOMTimer* timer) 364 { 365 ASSERT(!m_timeouts.contains(timeoutId)); 366 m_timeouts.set(timeoutId, timer); 367 } 368 369 void ScriptExecutionContext::removeTimeout(int timeoutId) 370 { 371 m_timeouts.remove(timeoutId); 372 } 373 374 DOMTimer* ScriptExecutionContext::findTimeout(int timeoutId) 375 { 376 return m_timeouts.get(timeoutId); 377 } 378 379 #if ENABLE(BLOB) 380 KURL ScriptExecutionContext::createPublicBlobURL(Blob* blob) 381 { 382 if (!blob) 383 return KURL(); 384 KURL publicURL = BlobURL::createPublicURL(securityOrigin()); 385 if (publicURL.isEmpty()) 386 return KURL(); 387 ThreadableBlobRegistry::registerBlobURL(publicURL, blob->url()); 388 m_publicBlobURLs.add(publicURL.string()); 389 return publicURL; 390 } 391 392 void ScriptExecutionContext::revokePublicBlobURL(const KURL& url) 393 { 394 if (m_publicBlobURLs.contains(url.string())) { 395 ThreadableBlobRegistry::unregisterBlobURL(url); 396 m_publicBlobURLs.remove(url.string()); 397 } 398 } 399 #endif 400 401 #if ENABLE(BLOB) || ENABLE(FILE_SYSTEM) 402 FileThread* ScriptExecutionContext::fileThread() 403 { 404 if (!m_fileThread) { 405 m_fileThread = FileThread::create(); 406 if (!m_fileThread->start()) 407 m_fileThread = 0; 408 } 409 return m_fileThread.get(); 410 } 411 #endif 412 413 void ScriptExecutionContext::adjustMinimumTimerInterval(double oldMinimumTimerInterval) 414 { 415 if (minimumTimerInterval() != oldMinimumTimerInterval) { 416 for (TimeoutMap::iterator iter = m_timeouts.begin(); iter != m_timeouts.end(); ++iter) { 417 DOMTimer* timer = iter->second; 418 timer->adjustMinimumTimerInterval(oldMinimumTimerInterval); 419 } 420 } 421 } 422 423 double ScriptExecutionContext::minimumTimerInterval() const 424 { 425 // The default implementation returns the DOMTimer's default 426 // minimum timer interval. FIXME: to make it work with dedicated 427 // workers, we will have to override it in the appropriate 428 // subclass, and provide a way to enumerate a Document's dedicated 429 // workers so we can update them all. 430 return Settings::defaultMinDOMTimerInterval(); 431 } 432 433 ScriptExecutionContext::Task::~Task() 434 { 435 } 436 437 #if USE(JSC) 438 JSC::JSGlobalData* ScriptExecutionContext::globalData() 439 { 440 if (isDocument()) 441 return JSDOMWindow::commonJSGlobalData(); 442 443 #if ENABLE(WORKERS) 444 if (isWorkerContext()) 445 return static_cast<WorkerContext*>(this)->script()->globalData(); 446 #endif 447 448 ASSERT_NOT_REACHED(); 449 return 0; 450 } 451 #endif 452 453 } // namespace WebCore 454