1/* 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. 3 * Copyright (C) 2012 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 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 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#include "ScriptExecutionContext.h" 30 31#include "CachedScript.h" 32#include "DOMTimer.h" 33#include "ErrorEvent.h" 34#include "MessagePort.h" 35#include "PublicURLManager.h" 36#include "Settings.h" 37#include "WorkerGlobalScope.h" 38#include "WorkerThread.h" 39#include <inspector/ScriptCallStack.h> 40#include <wtf/MainThread.h> 41#include <wtf/Ref.h> 42 43// FIXME: This is a layering violation. 44#include "JSDOMWindow.h" 45 46#if PLATFORM(IOS) 47#include "Document.h" 48#endif 49 50#if ENABLE(SQL_DATABASE) 51#include "DatabaseContext.h" 52#endif 53 54using namespace Inspector; 55 56namespace WebCore { 57 58class ScriptExecutionContext::PendingException { 59 WTF_MAKE_NONCOPYABLE(PendingException); 60public: 61 PendingException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, PassRefPtr<ScriptCallStack> callStack) 62 : m_errorMessage(errorMessage) 63 , m_lineNumber(lineNumber) 64 , m_columnNumber(columnNumber) 65 , m_sourceURL(sourceURL) 66 , m_callStack(callStack) 67 { 68 } 69 String m_errorMessage; 70 int m_lineNumber; 71 int m_columnNumber; 72 String m_sourceURL; 73 RefPtr<ScriptCallStack> m_callStack; 74}; 75 76ScriptExecutionContext::ScriptExecutionContext() 77 : m_circularSequentialID(0) 78 , m_inDispatchErrorEvent(false) 79 , m_activeDOMObjectsAreSuspended(false) 80 , m_reasonForSuspendingActiveDOMObjects(static_cast<ActiveDOMObject::ReasonForSuspension>(-1)) 81 , m_activeDOMObjectsAreStopped(false) 82 , m_activeDOMObjectAdditionForbidden(false) 83#if !ASSERT_DISABLED 84 , m_inScriptExecutionContextDestructor(false) 85 , m_activeDOMObjectRemovalForbidden(false) 86#endif 87{ 88} 89 90#if ASSERT_DISABLED 91 92inline void ScriptExecutionContext::checkConsistency() const 93{ 94} 95 96#else 97 98void ScriptExecutionContext::checkConsistency() const 99{ 100 for (auto* messagePort : m_messagePorts) 101 ASSERT(messagePort->scriptExecutionContext() == this); 102 103 for (auto* destructionObserver : m_destructionObservers) 104 ASSERT(destructionObserver->scriptExecutionContext() == this); 105 106 for (auto* activeDOMObject : m_activeDOMObjects) { 107 ASSERT(activeDOMObject->scriptExecutionContext() == this); 108 activeDOMObject->assertSuspendIfNeededWasCalled(); 109 } 110} 111 112#endif 113 114ScriptExecutionContext::~ScriptExecutionContext() 115{ 116 checkConsistency(); 117 118#if !ASSERT_DISABLED 119 m_inScriptExecutionContextDestructor = true; 120#endif 121 122 while (auto* destructionObserver = m_destructionObservers.takeAny()) 123 destructionObserver->contextDestroyed(); 124 125 for (auto* messagePort : m_messagePorts) 126 messagePort->contextDestroyed(); 127 128#if !ASSERT_DISABLED 129 m_inScriptExecutionContextDestructor = false; 130#endif 131} 132 133void ScriptExecutionContext::processMessagePortMessagesSoon() 134{ 135 postTask([] (ScriptExecutionContext& context) { 136 context.dispatchMessagePortEvents(); 137 }); 138} 139 140void ScriptExecutionContext::dispatchMessagePortEvents() 141{ 142 checkConsistency(); 143 144 Ref<ScriptExecutionContext> protect(*this); 145 146 // Make a frozen copy of the ports so we can iterate while new ones might be added or destroyed. 147 Vector<MessagePort*> possibleMessagePorts; 148 copyToVector(m_messagePorts, possibleMessagePorts); 149 for (auto* messagePort : possibleMessagePorts) { 150 // The port may be destroyed, and another one created at the same address, 151 // but this is harmless. The worst that can happen as a result is that 152 // dispatchMessages() will be called needlessly. 153 if (m_messagePorts.contains(messagePort) && messagePort->started()) 154 messagePort->dispatchMessages(); 155 } 156} 157 158void ScriptExecutionContext::createdMessagePort(MessagePort& messagePort) 159{ 160 ASSERT((isDocument() && isMainThread()) 161 || (isWorkerGlobalScope() && currentThread() == toWorkerGlobalScope(this)->thread().threadID())); 162 163 m_messagePorts.add(&messagePort); 164} 165 166void ScriptExecutionContext::destroyedMessagePort(MessagePort& messagePort) 167{ 168 ASSERT((isDocument() && isMainThread()) 169 || (isWorkerGlobalScope() && currentThread() == toWorkerGlobalScope(this)->thread().threadID())); 170 171 m_messagePorts.remove(&messagePort); 172} 173 174bool ScriptExecutionContext::canSuspendActiveDOMObjects() 175{ 176 checkConsistency(); 177 178 bool canSuspend = true; 179 180 m_activeDOMObjectAdditionForbidden = true; 181#if !ASSERT_DISABLED 182 m_activeDOMObjectRemovalForbidden = true; 183#endif 184 185 // We assume that m_activeDOMObjects will not change during iteration: canSuspend 186 // functions should not add new active DOM objects, nor execute arbitrary JavaScript. 187 // An ASSERT or RELEASE_ASSERT will fire if this happens, but it's important to code 188 // canSuspend functions so it will not happen! 189 for (auto* activeDOMObject : m_activeDOMObjects) { 190 if (!activeDOMObject->canSuspend()) { 191 canSuspend = false; 192 break; 193 } 194 } 195 196 m_activeDOMObjectAdditionForbidden = false; 197#if !ASSERT_DISABLED 198 m_activeDOMObjectRemovalForbidden = false; 199#endif 200 201 return canSuspend; 202} 203 204void ScriptExecutionContext::suspendActiveDOMObjects(ActiveDOMObject::ReasonForSuspension why) 205{ 206 checkConsistency(); 207 208#if PLATFORM(IOS) 209 if (m_activeDOMObjectsAreSuspended) { 210 ASSERT(m_reasonForSuspendingActiveDOMObjects == ActiveDOMObject::DocumentWillBePaused); 211 return; 212 } 213#endif 214 215 m_activeDOMObjectAdditionForbidden = true; 216#if !ASSERT_DISABLED 217 m_activeDOMObjectRemovalForbidden = true; 218#endif 219 220 // We assume that m_activeDOMObjects will not change during iteration: suspend 221 // functions should not add new active DOM objects, nor execute arbitrary JavaScript. 222 // An ASSERT or RELEASE_ASSERT will fire if this happens, but it's important to code 223 // suspend functions so it will not happen! 224 for (auto* activeDOMObject : m_activeDOMObjects) 225 activeDOMObject->suspend(why); 226 227 m_activeDOMObjectAdditionForbidden = false; 228#if !ASSERT_DISABLED 229 m_activeDOMObjectRemovalForbidden = false; 230#endif 231 232 m_activeDOMObjectsAreSuspended = true; 233 m_reasonForSuspendingActiveDOMObjects = why; 234} 235 236void ScriptExecutionContext::resumeActiveDOMObjects(ActiveDOMObject::ReasonForSuspension why) 237{ 238 checkConsistency(); 239 240 if (m_reasonForSuspendingActiveDOMObjects != why) 241 return; 242 m_activeDOMObjectsAreSuspended = false; 243 244 m_activeDOMObjectAdditionForbidden = true; 245#if !ASSERT_DISABLED 246 m_activeDOMObjectRemovalForbidden = true; 247#endif 248 249 // We assume that m_activeDOMObjects will not change during iteration: resume 250 // functions should not add new active DOM objects, nor execute arbitrary JavaScript. 251 // An ASSERT or RELEASE_ASSERT will fire if this happens, but it's important to code 252 // resume functions so it will not happen! 253 for (auto* activeDOMObject : m_activeDOMObjects) 254 activeDOMObject->resume(); 255 256 m_activeDOMObjectAdditionForbidden = false; 257#if !ASSERT_DISABLED 258 m_activeDOMObjectRemovalForbidden = false; 259#endif 260} 261 262void ScriptExecutionContext::stopActiveDOMObjects() 263{ 264 checkConsistency(); 265 266 if (m_activeDOMObjectsAreStopped) 267 return; 268 m_activeDOMObjectsAreStopped = true; 269 270 // Make a frozen copy of the objects so we can iterate while new ones might be destroyed. 271 Vector<ActiveDOMObject*> possibleActiveDOMObjects; 272 copyToVector(m_activeDOMObjects, possibleActiveDOMObjects); 273 274 m_activeDOMObjectAdditionForbidden = true; 275 276 // We assume that new objects will not be added to m_activeDOMObjects during iteration: 277 // stop functions should not add new active DOM objects, nor execute arbitrary JavaScript. 278 // A RELEASE_ASSERT will fire if this happens, but it's important to code stop functions 279 // so it will not happen! 280 for (auto* activeDOMObject : possibleActiveDOMObjects) { 281 // Check if this object was deleted already. If so, just skip it. 282 // Calling contains on a possibly-already-deleted object is OK because we guarantee 283 // no new object can be added, so even if a new object ends up allocated with the 284 // same address, that will be *after* this function exits. 285 if (!m_activeDOMObjects.contains(activeDOMObject)) 286 continue; 287 activeDOMObject->stop(); 288 } 289 290 m_activeDOMObjectAdditionForbidden = false; 291 292 // FIXME: Make message ports be active DOM objects and let them implement stop instead 293 // of having this separate mechanism just for them. 294 for (auto* messagePort : m_messagePorts) 295 messagePort->close(); 296} 297 298void ScriptExecutionContext::suspendActiveDOMObjectIfNeeded(ActiveDOMObject& activeDOMObject) 299{ 300 ASSERT(m_activeDOMObjects.contains(&activeDOMObject)); 301 if (m_activeDOMObjectsAreSuspended) 302 activeDOMObject.suspend(m_reasonForSuspendingActiveDOMObjects); 303 if (m_activeDOMObjectsAreStopped) 304 activeDOMObject.stop(); 305} 306 307void ScriptExecutionContext::didCreateActiveDOMObject(ActiveDOMObject& activeDOMObject) 308{ 309 // The m_activeDOMObjectAdditionForbidden check is a RELEASE_ASSERT because of the 310 // consequences of having an ActiveDOMObject that is not correctly reflected in the set. 311 // If we do have one of those, it can possibly be a security vulnerability. So we'd 312 // rather have a crash than continue running with the set possibly compromised. 313 ASSERT(!m_inScriptExecutionContextDestructor); 314 RELEASE_ASSERT(!m_activeDOMObjectAdditionForbidden); 315 m_activeDOMObjects.add(&activeDOMObject); 316} 317 318void ScriptExecutionContext::willDestroyActiveDOMObject(ActiveDOMObject& activeDOMObject) 319{ 320 ASSERT(!m_activeDOMObjectRemovalForbidden); 321 m_activeDOMObjects.remove(&activeDOMObject); 322} 323 324void ScriptExecutionContext::didCreateDestructionObserver(ContextDestructionObserver& observer) 325{ 326 ASSERT(!m_inScriptExecutionContextDestructor); 327 m_destructionObservers.add(&observer); 328} 329 330void ScriptExecutionContext::willDestroyDestructionObserver(ContextDestructionObserver& observer) 331{ 332 m_destructionObservers.remove(&observer); 333} 334 335bool ScriptExecutionContext::sanitizeScriptError(String& errorMessage, int& lineNumber, int& columnNumber, String& sourceURL, CachedScript* cachedScript) 336{ 337 URL targetURL = completeURL(sourceURL); 338 if (securityOrigin()->canRequest(targetURL) || (cachedScript && cachedScript->passesAccessControlCheck(securityOrigin()))) 339 return false; 340 errorMessage = "Script error."; 341 sourceURL = String(); 342 lineNumber = 0; 343 columnNumber = 0; 344 return true; 345} 346 347void ScriptExecutionContext::reportException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, PassRefPtr<ScriptCallStack> callStack, CachedScript* cachedScript) 348{ 349 if (m_inDispatchErrorEvent) { 350 if (!m_pendingExceptions) 351 m_pendingExceptions = std::make_unique<Vector<std::unique_ptr<PendingException>>>(); 352 m_pendingExceptions->append(std::make_unique<PendingException>(errorMessage, lineNumber, columnNumber, sourceURL, callStack)); 353 return; 354 } 355 356 // First report the original exception and only then all the nested ones. 357 if (!dispatchErrorEvent(errorMessage, lineNumber, columnNumber, sourceURL, cachedScript)) 358 logExceptionToConsole(errorMessage, sourceURL, lineNumber, columnNumber, callStack); 359 360 if (!m_pendingExceptions) 361 return; 362 363 std::unique_ptr<Vector<std::unique_ptr<PendingException>>> pendingExceptions = WTF::move(m_pendingExceptions); 364 for (auto& exception : *pendingExceptions) 365 logExceptionToConsole(exception->m_errorMessage, exception->m_sourceURL, exception->m_lineNumber, exception->m_columnNumber, exception->m_callStack); 366} 367 368void ScriptExecutionContext::addConsoleMessage(MessageSource source, MessageLevel level, const String& message, const String& sourceURL, unsigned lineNumber, unsigned columnNumber, JSC::ExecState* state, unsigned long requestIdentifier) 369{ 370 addMessage(source, level, message, sourceURL, lineNumber, columnNumber, 0, state, requestIdentifier); 371} 372 373bool ScriptExecutionContext::dispatchErrorEvent(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, CachedScript* cachedScript) 374{ 375 EventTarget* target = errorEventTarget(); 376 if (!target) 377 return false; 378 379#if PLATFORM(IOS) 380 if (target == target->toDOMWindow() && isDocument()) { 381 Settings* settings = static_cast<Document*>(this)->settings(); 382 if (settings && !settings->shouldDispatchJavaScriptWindowOnErrorEvents()) 383 return false; 384 } 385#endif 386 387 String message = errorMessage; 388 int line = lineNumber; 389 int column = columnNumber; 390 String sourceName = sourceURL; 391 sanitizeScriptError(message, line, column, sourceName, cachedScript); 392 393 ASSERT(!m_inDispatchErrorEvent); 394 m_inDispatchErrorEvent = true; 395 RefPtr<ErrorEvent> errorEvent = ErrorEvent::create(message, sourceName, line, column); 396 target->dispatchEvent(errorEvent); 397 m_inDispatchErrorEvent = false; 398 return errorEvent->defaultPrevented(); 399} 400 401int ScriptExecutionContext::circularSequentialID() 402{ 403 ++m_circularSequentialID; 404 if (m_circularSequentialID <= 0) 405 m_circularSequentialID = 1; 406 return m_circularSequentialID; 407} 408 409PublicURLManager& ScriptExecutionContext::publicURLManager() 410{ 411 if (!m_publicURLManager) 412 m_publicURLManager = PublicURLManager::create(this); 413 return *m_publicURLManager; 414} 415 416void ScriptExecutionContext::adjustMinimumTimerInterval(double oldMinimumTimerInterval) 417{ 418 if (minimumTimerInterval() != oldMinimumTimerInterval) { 419 for (auto* timer : m_timeouts.values()) 420 timer->adjustMinimumTimerInterval(oldMinimumTimerInterval); 421 } 422} 423 424double ScriptExecutionContext::minimumTimerInterval() const 425{ 426 // The default implementation returns the DOMTimer's default 427 // minimum timer interval. FIXME: to make it work with dedicated 428 // workers, we will have to override it in the appropriate 429 // subclass, and provide a way to enumerate a Document's dedicated 430 // workers so we can update them all. 431 return Settings::defaultMinDOMTimerInterval(); 432} 433 434void ScriptExecutionContext::didChangeTimerAlignmentInterval() 435{ 436 for (auto* timer : m_timeouts.values()) 437 timer->didChangeAlignmentInterval(); 438} 439 440double ScriptExecutionContext::timerAlignmentInterval() const 441{ 442 return Settings::defaultDOMTimerAlignmentInterval(); 443} 444 445JSC::VM& ScriptExecutionContext::vm() 446{ 447 if (isDocument()) 448 return JSDOMWindow::commonVM(); 449 450 return toWorkerGlobalScope(*this).script()->vm(); 451} 452 453#if ENABLE(SQL_DATABASE) 454void ScriptExecutionContext::setDatabaseContext(DatabaseContext* databaseContext) 455{ 456 ASSERT(!m_databaseContext); 457 m_databaseContext = databaseContext; 458} 459#endif 460 461bool ScriptExecutionContext::hasPendingActivity() const 462{ 463 checkConsistency(); 464 465 for (auto* activeDOMObject : m_activeDOMObjects) { 466 if (activeDOMObject->hasPendingActivity()) 467 return true; 468 } 469 470 for (auto* messagePort : m_messagePorts) { 471 if (messagePort->hasPendingActivity()) 472 return true; 473 } 474 475 return false; 476} 477 478} // namespace WebCore 479