| /* |
| * Copyright (C) 2008 Apple Inc. All Rights Reserved. |
| * Copyright (C) 2012 Google Inc. All Rights Reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| */ |
| |
| #include "config.h" |
| #include "ScriptExecutionContext.h" |
| |
| #include "CachedScript.h" |
| #include "DOMTimer.h" |
| #include "DatabaseContext.h" |
| #include "Document.h" |
| #include "ErrorEvent.h" |
| #include "MessagePort.h" |
| #include "NoEventDispatchAssertion.h" |
| #include "PublicURLManager.h" |
| #include "ResourceRequest.h" |
| #include "ScriptState.h" |
| #include "Settings.h" |
| #include "WorkerGlobalScope.h" |
| #include "WorkerThread.h" |
| #include <inspector/ScriptCallStack.h> |
| #include <runtime/Exception.h> |
| #include <wtf/MainThread.h> |
| #include <wtf/Ref.h> |
| |
| // FIXME: This is a layering violation. |
| #include "JSDOMWindow.h" |
| |
| #if PLATFORM(IOS) |
| #include "Document.h" |
| #endif |
| |
| using namespace Inspector; |
| |
| namespace WebCore { |
| |
| struct ScriptExecutionContext::PendingException { |
| WTF_MAKE_NONCOPYABLE(PendingException); WTF_MAKE_FAST_ALLOCATED; |
| public: |
| PendingException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, PassRefPtr<ScriptCallStack> callStack) |
| : m_errorMessage(errorMessage) |
| , m_lineNumber(lineNumber) |
| , m_columnNumber(columnNumber) |
| , m_sourceURL(sourceURL) |
| , m_callStack(callStack) |
| { |
| } |
| String m_errorMessage; |
| int m_lineNumber; |
| int m_columnNumber; |
| String m_sourceURL; |
| RefPtr<ScriptCallStack> m_callStack; |
| }; |
| |
| ScriptExecutionContext::ScriptExecutionContext() |
| : m_circularSequentialID(0) |
| , m_inDispatchErrorEvent(false) |
| , m_activeDOMObjectsAreSuspended(false) |
| , m_reasonForSuspendingActiveDOMObjects(static_cast<ActiveDOMObject::ReasonForSuspension>(-1)) |
| , m_activeDOMObjectsAreStopped(false) |
| , m_activeDOMObjectAdditionForbidden(false) |
| , m_timerNestingLevel(0) |
| #if !ASSERT_DISABLED |
| , m_inScriptExecutionContextDestructor(false) |
| #endif |
| #if !ASSERT_DISABLED || ENABLE(SECURITY_ASSERTIONS) |
| , m_activeDOMObjectRemovalForbidden(false) |
| #endif |
| { |
| } |
| |
| #if ASSERT_DISABLED |
| |
| inline void ScriptExecutionContext::checkConsistency() const |
| { |
| } |
| |
| #else |
| |
| void ScriptExecutionContext::checkConsistency() const |
| { |
| for (auto* messagePort : m_messagePorts) |
| ASSERT(messagePort->scriptExecutionContext() == this); |
| |
| for (auto* destructionObserver : m_destructionObservers) |
| ASSERT(destructionObserver->scriptExecutionContext() == this); |
| |
| for (auto* activeDOMObject : m_activeDOMObjects) { |
| ASSERT(activeDOMObject->scriptExecutionContext() == this); |
| activeDOMObject->assertSuspendIfNeededWasCalled(); |
| } |
| } |
| |
| #endif |
| |
| ScriptExecutionContext::~ScriptExecutionContext() |
| { |
| checkConsistency(); |
| |
| #if !ASSERT_DISABLED |
| m_inScriptExecutionContextDestructor = true; |
| #endif |
| |
| while (auto* destructionObserver = m_destructionObservers.takeAny()) |
| destructionObserver->contextDestroyed(); |
| |
| for (auto* messagePort : m_messagePorts) |
| messagePort->contextDestroyed(); |
| |
| #if !ASSERT_DISABLED |
| m_inScriptExecutionContextDestructor = false; |
| #endif |
| } |
| |
| void ScriptExecutionContext::processMessagePortMessagesSoon() |
| { |
| postTask([] (ScriptExecutionContext& context) { |
| context.dispatchMessagePortEvents(); |
| }); |
| } |
| |
| void ScriptExecutionContext::dispatchMessagePortEvents() |
| { |
| checkConsistency(); |
| |
| Ref<ScriptExecutionContext> protectedThis(*this); |
| |
| // Make a frozen copy of the ports so we can iterate while new ones might be added or destroyed. |
| Vector<MessagePort*> possibleMessagePorts; |
| copyToVector(m_messagePorts, possibleMessagePorts); |
| for (auto* messagePort : possibleMessagePorts) { |
| // The port may be destroyed, and another one created at the same address, |
| // but this is harmless. The worst that can happen as a result is that |
| // dispatchMessages() will be called needlessly. |
| if (m_messagePorts.contains(messagePort) && messagePort->started()) |
| messagePort->dispatchMessages(); |
| } |
| } |
| |
| void ScriptExecutionContext::createdMessagePort(MessagePort& messagePort) |
| { |
| ASSERT((is<Document>(*this) && isMainThread()) |
| || (is<WorkerGlobalScope>(*this) && currentThread() == downcast<WorkerGlobalScope>(*this).thread().threadID())); |
| |
| m_messagePorts.add(&messagePort); |
| } |
| |
| void ScriptExecutionContext::destroyedMessagePort(MessagePort& messagePort) |
| { |
| ASSERT((is<Document>(*this) && isMainThread()) |
| || (is<WorkerGlobalScope>(*this) && currentThread() == downcast<WorkerGlobalScope>(*this).thread().threadID())); |
| |
| m_messagePorts.remove(&messagePort); |
| } |
| |
| void ScriptExecutionContext::didLoadResourceSynchronously(const ResourceRequest&) |
| { |
| } |
| |
| bool ScriptExecutionContext::canSuspendActiveDOMObjectsForDocumentSuspension(Vector<ActiveDOMObject*>* unsuspendableObjects) |
| { |
| checkConsistency(); |
| |
| bool canSuspend = true; |
| |
| m_activeDOMObjectAdditionForbidden = true; |
| #if !ASSERT_DISABLED || ENABLE(SECURITY_ASSERTIONS) |
| m_activeDOMObjectRemovalForbidden = true; |
| #endif |
| |
| // We assume that m_activeDOMObjects will not change during iteration: canSuspend |
| // functions should not add new active DOM objects, nor execute arbitrary JavaScript. |
| // An ASSERT_WITH_SECURITY_IMPLICATION or RELEASE_ASSERT will fire if this happens, but it's important to code |
| // canSuspend functions so it will not happen! |
| NoEventDispatchAssertion assertNoEventDispatch; |
| for (auto* activeDOMObject : m_activeDOMObjects) { |
| if (!activeDOMObject->canSuspendForDocumentSuspension()) { |
| canSuspend = false; |
| if (unsuspendableObjects) |
| unsuspendableObjects->append(activeDOMObject); |
| else |
| break; |
| } |
| } |
| |
| m_activeDOMObjectAdditionForbidden = false; |
| #if !ASSERT_DISABLED || ENABLE(SECURITY_ASSERTIONS) |
| m_activeDOMObjectRemovalForbidden = false; |
| #endif |
| |
| return canSuspend; |
| } |
| |
| void ScriptExecutionContext::suspendActiveDOMObjects(ActiveDOMObject::ReasonForSuspension why) |
| { |
| checkConsistency(); |
| |
| if (m_activeDOMObjectsAreSuspended) { |
| // A page may subsequently suspend DOM objects, say as part of entering the page cache, after the embedding |
| // client requested the page be suspended. We ignore such requests so long as the embedding client requested |
| // the suspension first. See <rdar://problem/13754896> for more details. |
| ASSERT(m_reasonForSuspendingActiveDOMObjects == ActiveDOMObject::PageWillBeSuspended); |
| return; |
| } |
| |
| m_activeDOMObjectsAreSuspended = true; |
| |
| m_activeDOMObjectAdditionForbidden = true; |
| #if !ASSERT_DISABLED || ENABLE(SECURITY_ASSERTIONS) |
| m_activeDOMObjectRemovalForbidden = true; |
| #endif |
| |
| // We assume that m_activeDOMObjects will not change during iteration: suspend |
| // functions should not add new active DOM objects, nor execute arbitrary JavaScript. |
| // An ASSERT_WITH_SECURITY_IMPLICATION or RELEASE_ASSERT will fire if this happens, but it's important to code |
| // suspend functions so it will not happen! |
| NoEventDispatchAssertion assertNoEventDispatch; |
| for (auto* activeDOMObject : m_activeDOMObjects) |
| activeDOMObject->suspend(why); |
| |
| m_activeDOMObjectAdditionForbidden = false; |
| #if !ASSERT_DISABLED || ENABLE(SECURITY_ASSERTIONS) |
| m_activeDOMObjectRemovalForbidden = false; |
| #endif |
| |
| m_reasonForSuspendingActiveDOMObjects = why; |
| } |
| |
| void ScriptExecutionContext::resumeActiveDOMObjects(ActiveDOMObject::ReasonForSuspension why) |
| { |
| checkConsistency(); |
| |
| if (m_reasonForSuspendingActiveDOMObjects != why) |
| return; |
| m_activeDOMObjectsAreSuspended = false; |
| |
| m_activeDOMObjectAdditionForbidden = true; |
| #if !ASSERT_DISABLED || ENABLE(SECURITY_ASSERTIONS) |
| m_activeDOMObjectRemovalForbidden = true; |
| #endif |
| |
| // We assume that m_activeDOMObjects will not change during iteration: resume |
| // functions should not add new active DOM objects, nor execute arbitrary JavaScript. |
| // An ASSERT_WITH_SECURITY_IMPLICATION or RELEASE_ASSERT will fire if this happens, but it's important to code |
| // resume functions so it will not happen! |
| NoEventDispatchAssertion assertNoEventDispatch; |
| for (auto* activeDOMObject : m_activeDOMObjects) |
| activeDOMObject->resume(); |
| |
| m_activeDOMObjectAdditionForbidden = false; |
| #if !ASSERT_DISABLED || ENABLE(SECURITY_ASSERTIONS) |
| m_activeDOMObjectRemovalForbidden = false; |
| #endif |
| } |
| |
| void ScriptExecutionContext::stopActiveDOMObjects() |
| { |
| checkConsistency(); |
| |
| if (m_activeDOMObjectsAreStopped) |
| return; |
| m_activeDOMObjectsAreStopped = true; |
| |
| // Make a frozen copy of the objects so we can iterate while new ones might be destroyed. |
| Vector<ActiveDOMObject*> possibleActiveDOMObjects; |
| copyToVector(m_activeDOMObjects, possibleActiveDOMObjects); |
| |
| m_activeDOMObjectAdditionForbidden = true; |
| |
| // We assume that new objects will not be added to m_activeDOMObjects during iteration: |
| // stop functions should not add new active DOM objects, nor execute arbitrary JavaScript. |
| // An ASSERT_WITH_SECURITY_IMPLICATION or RELEASE_ASSERT will fire if this happens, but it's important to code stop functions |
| // so it will not happen! |
| NoEventDispatchAssertion assertNoEventDispatch; |
| for (auto* activeDOMObject : possibleActiveDOMObjects) { |
| // Check if this object was deleted already. If so, just skip it. |
| // Calling contains on a possibly-already-deleted object is OK because we guarantee |
| // no new object can be added, so even if a new object ends up allocated with the |
| // same address, that will be *after* this function exits. |
| if (!m_activeDOMObjects.contains(activeDOMObject)) |
| continue; |
| activeDOMObject->stop(); |
| } |
| |
| m_activeDOMObjectAdditionForbidden = false; |
| |
| // FIXME: Make message ports be active DOM objects and let them implement stop instead |
| // of having this separate mechanism just for them. |
| for (auto* messagePort : m_messagePorts) |
| messagePort->close(); |
| } |
| |
| void ScriptExecutionContext::suspendActiveDOMObjectIfNeeded(ActiveDOMObject& activeDOMObject) |
| { |
| ASSERT(m_activeDOMObjects.contains(&activeDOMObject)); |
| if (m_activeDOMObjectsAreSuspended) |
| activeDOMObject.suspend(m_reasonForSuspendingActiveDOMObjects); |
| if (m_activeDOMObjectsAreStopped) |
| activeDOMObject.stop(); |
| } |
| |
| void ScriptExecutionContext::didCreateActiveDOMObject(ActiveDOMObject& activeDOMObject) |
| { |
| // The m_activeDOMObjectAdditionForbidden check is a RELEASE_ASSERT because of the |
| // consequences of having an ActiveDOMObject that is not correctly reflected in the set. |
| // If we do have one of those, it can possibly be a security vulnerability. So we'd |
| // rather have a crash than continue running with the set possibly compromised. |
| ASSERT(!m_inScriptExecutionContextDestructor); |
| RELEASE_ASSERT(!m_activeDOMObjectAdditionForbidden); |
| m_activeDOMObjects.add(&activeDOMObject); |
| } |
| |
| void ScriptExecutionContext::willDestroyActiveDOMObject(ActiveDOMObject& activeDOMObject) |
| { |
| ASSERT_WITH_SECURITY_IMPLICATION(!m_activeDOMObjectRemovalForbidden); |
| m_activeDOMObjects.remove(&activeDOMObject); |
| } |
| |
| void ScriptExecutionContext::didCreateDestructionObserver(ContextDestructionObserver& observer) |
| { |
| ASSERT(!m_inScriptExecutionContextDestructor); |
| m_destructionObservers.add(&observer); |
| } |
| |
| void ScriptExecutionContext::willDestroyDestructionObserver(ContextDestructionObserver& observer) |
| { |
| m_destructionObservers.remove(&observer); |
| } |
| |
| bool ScriptExecutionContext::sanitizeScriptError(String& errorMessage, int& lineNumber, int& columnNumber, String& sourceURL, Deprecated::ScriptValue& error, CachedScript* cachedScript) |
| { |
| URL targetURL = completeURL(sourceURL); |
| if (securityOrigin()->canRequest(targetURL) || (cachedScript && cachedScript->passesAccessControlCheck(*securityOrigin()))) |
| return false; |
| errorMessage = "Script error."; |
| sourceURL = String(); |
| lineNumber = 0; |
| columnNumber = 0; |
| error = Deprecated::ScriptValue(); |
| return true; |
| } |
| |
| void ScriptExecutionContext::reportException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, JSC::Exception* exception, RefPtr<ScriptCallStack>&& callStack, CachedScript* cachedScript) |
| { |
| if (m_inDispatchErrorEvent) { |
| if (!m_pendingExceptions) |
| m_pendingExceptions = std::make_unique<Vector<std::unique_ptr<PendingException>>>(); |
| m_pendingExceptions->append(std::make_unique<PendingException>(errorMessage, lineNumber, columnNumber, sourceURL, callStack.copyRef())); |
| return; |
| } |
| |
| // First report the original exception and only then all the nested ones. |
| if (!dispatchErrorEvent(errorMessage, lineNumber, columnNumber, sourceURL, exception, cachedScript)) |
| logExceptionToConsole(errorMessage, sourceURL, lineNumber, columnNumber, callStack.copyRef()); |
| |
| if (!m_pendingExceptions) |
| return; |
| |
| std::unique_ptr<Vector<std::unique_ptr<PendingException>>> pendingExceptions = WTFMove(m_pendingExceptions); |
| for (auto& exception : *pendingExceptions) |
| logExceptionToConsole(exception->m_errorMessage, exception->m_sourceURL, exception->m_lineNumber, exception->m_columnNumber, exception->m_callStack.copyRef()); |
| } |
| |
| void ScriptExecutionContext::addConsoleMessage(MessageSource source, MessageLevel level, const String& message, const String& sourceURL, unsigned lineNumber, unsigned columnNumber, JSC::ExecState* state, unsigned long requestIdentifier) |
| { |
| addMessage(source, level, message, sourceURL, lineNumber, columnNumber, 0, state, requestIdentifier); |
| } |
| |
| bool ScriptExecutionContext::dispatchErrorEvent(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, JSC::Exception* exception, CachedScript* cachedScript) |
| { |
| EventTarget* target = errorEventTarget(); |
| if (!target) |
| return false; |
| |
| #if PLATFORM(IOS) |
| if (target->toDOMWindow() && is<Document>(*this)) { |
| Settings* settings = downcast<Document>(*this).settings(); |
| if (settings && !settings->shouldDispatchJavaScriptWindowOnErrorEvents()) |
| return false; |
| } |
| #endif |
| |
| String message = errorMessage; |
| int line = lineNumber; |
| int column = columnNumber; |
| String sourceName = sourceURL; |
| Deprecated::ScriptValue error = exception && exception->value() ? Deprecated::ScriptValue(vm(), exception->value()) : Deprecated::ScriptValue(); |
| sanitizeScriptError(message, line, column, sourceName, error, cachedScript); |
| |
| ASSERT(!m_inDispatchErrorEvent); |
| m_inDispatchErrorEvent = true; |
| Ref<ErrorEvent> errorEvent = ErrorEvent::create(message, sourceName, line, column, error); |
| target->dispatchEvent(errorEvent); |
| m_inDispatchErrorEvent = false; |
| return errorEvent->defaultPrevented(); |
| } |
| |
| int ScriptExecutionContext::circularSequentialID() |
| { |
| ++m_circularSequentialID; |
| if (m_circularSequentialID <= 0) |
| m_circularSequentialID = 1; |
| return m_circularSequentialID; |
| } |
| |
| PublicURLManager& ScriptExecutionContext::publicURLManager() |
| { |
| if (!m_publicURLManager) |
| m_publicURLManager = PublicURLManager::create(this); |
| return *m_publicURLManager; |
| } |
| |
| void ScriptExecutionContext::adjustMinimumTimerInterval(std::chrono::milliseconds oldMinimumTimerInterval) |
| { |
| if (minimumTimerInterval() != oldMinimumTimerInterval) { |
| for (auto& timer : m_timeouts.values()) |
| timer->updateTimerIntervalIfNecessary(); |
| } |
| } |
| |
| std::chrono::milliseconds ScriptExecutionContext::minimumTimerInterval() const |
| { |
| // The default implementation returns the DOMTimer's default |
| // minimum timer interval. FIXME: to make it work with dedicated |
| // workers, we will have to override it in the appropriate |
| // subclass, and provide a way to enumerate a Document's dedicated |
| // workers so we can update them all. |
| return DOMTimer::defaultMinimumInterval(); |
| } |
| |
| void ScriptExecutionContext::didChangeTimerAlignmentInterval() |
| { |
| for (auto& timer : m_timeouts.values()) |
| timer->didChangeAlignmentInterval(); |
| } |
| |
| std::chrono::milliseconds ScriptExecutionContext::timerAlignmentInterval(bool) const |
| { |
| return DOMTimer::defaultAlignmentInterval(); |
| } |
| |
| JSC::VM& ScriptExecutionContext::vm() |
| { |
| if (is<Document>(*this)) |
| return JSDOMWindow::commonVM(); |
| |
| return downcast<WorkerGlobalScope>(*this).script()->vm(); |
| } |
| |
| void ScriptExecutionContext::setDatabaseContext(DatabaseContext* databaseContext) |
| { |
| ASSERT(!m_databaseContext); |
| m_databaseContext = databaseContext; |
| } |
| |
| bool ScriptExecutionContext::hasPendingActivity() const |
| { |
| checkConsistency(); |
| |
| for (auto* activeDOMObject : m_activeDOMObjects) { |
| if (activeDOMObject->hasPendingActivity()) |
| return true; |
| } |
| |
| for (auto* messagePort : m_messagePorts) { |
| if (messagePort->hasPendingActivity()) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| JSC::ExecState* ScriptExecutionContext::execState() |
| { |
| if (is<Document>(*this)) { |
| Document& document = downcast<Document>(*this); |
| return execStateFromPage(mainThreadNormalWorld(), document.page()); |
| } |
| |
| WorkerGlobalScope* workerGlobalScope = static_cast<WorkerGlobalScope*>(this); |
| return execStateFromWorkerGlobalScope(workerGlobalScope); |
| } |
| |
| } // namespace WebCore |