blob: 66bf28b6d4ea89c8a62a17b7aff5cc26c4e45bde [file] [log] [blame]
/*
* Copyright (C) 2013 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:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 THE COPYRIGHT
* OWNER 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 "core/inspector/AsyncCallStackTracker.h"
#include "bindings/v8/V8RecursionScope.h"
#include "core/dom/ContextLifecycleObserver.h"
#include "core/dom/ExecutionContext.h"
#include "core/events/EventTarget.h"
#include "core/events/RegisteredEventListener.h"
#include "core/xml/XMLHttpRequest.h"
#include "core/xml/XMLHttpRequestUpload.h"
#include "wtf/text/AtomicStringHash.h"
#include "wtf/text/StringBuilder.h"
namespace {
static const char setTimeoutName[] = "setTimeout";
static const char setIntervalName[] = "setInterval";
static const char requestAnimationFrameName[] = "requestAnimationFrame";
static const char xhrSendName[] = "XMLHttpRequest.send";
static const char enqueueMutationRecordName[] = "Mutation";
static const char promiseResolved[] = "Promise.resolve";
static const char promiseRejected[] = "Promise.reject";
}
namespace WebCore {
class AsyncCallStackTracker::ExecutionContextData FINAL : public ContextLifecycleObserver {
WTF_MAKE_FAST_ALLOCATED;
public:
typedef std::pair<RegisteredEventListener, RefPtr<AsyncCallChain> > EventListenerAsyncCallChain;
typedef Vector<EventListenerAsyncCallChain, 1> EventListenerAsyncCallChainVector;
typedef HashMap<AtomicString, EventListenerAsyncCallChainVector> EventListenerAsyncCallChainVectorHashMap;
ExecutionContextData(AsyncCallStackTracker* tracker, ExecutionContext* executionContext)
: ContextLifecycleObserver(executionContext)
, m_tracker(tracker)
{
}
virtual void contextDestroyed() OVERRIDE
{
ASSERT(executionContext());
ExecutionContextData* self = m_tracker->m_executionContextDataMap.take(executionContext());
ASSERT(self == this);
ContextLifecycleObserver::contextDestroyed();
delete self;
}
void addEventListenerData(EventTarget* eventTarget, const AtomicString& eventType, const EventListenerAsyncCallChain& item)
{
HashMap<EventTarget*, EventListenerAsyncCallChainVectorHashMap>::iterator it = m_eventTargetCallChains.find(eventTarget);
EventListenerAsyncCallChainVectorHashMap* mapPtr;
if (it == m_eventTargetCallChains.end())
mapPtr = &m_eventTargetCallChains.set(eventTarget, EventListenerAsyncCallChainVectorHashMap()).storedValue->value;
else
mapPtr = &it->value;
EventListenerAsyncCallChainVectorHashMap& map = *mapPtr;
EventListenerAsyncCallChainVectorHashMap::iterator it2 = map.find(eventType);
if (it2 == map.end())
map.set(eventType, EventListenerAsyncCallChainVector()).storedValue->value.append(item);
else
it2->value.append(item);
}
void removeEventListenerData(EventTarget* eventTarget, const AtomicString& eventType, const RegisteredEventListener& item)
{
findEventListenerData(eventTarget, eventType, item, true);
}
PassRefPtr<AsyncCallChain> findEventListenerData(EventTarget* eventTarget, const AtomicString& eventType, const RegisteredEventListener& item, bool remove = false)
{
HashMap<EventTarget*, EventListenerAsyncCallChainVectorHashMap>::iterator it = m_eventTargetCallChains.find(eventTarget);
if (it == m_eventTargetCallChains.end())
return nullptr;
EventListenerAsyncCallChainVectorHashMap& map = it->value;
EventListenerAsyncCallChainVectorHashMap::iterator it2 = map.find(eventType);
if (it2 == map.end())
return nullptr;
RefPtr<AsyncCallChain> result;
EventListenerAsyncCallChainVector& vector = it2->value;
for (size_t i = 0; i < vector.size(); ++i) {
if (vector[i].first == item) {
result = vector[i].second;
if (remove) {
vector.remove(i);
if (vector.isEmpty())
map.remove(it2);
if (map.isEmpty())
m_eventTargetCallChains.remove(it);
}
break;
}
}
return result.release();
}
public:
AsyncCallStackTracker* m_tracker;
HashSet<int> m_intervalTimerIds;
HashMap<int, RefPtr<AsyncCallChain> > m_timerCallChains;
HashMap<int, RefPtr<AsyncCallChain> > m_animationFrameCallChains;
HashMap<EventTarget*, EventListenerAsyncCallChainVectorHashMap> m_eventTargetCallChains;
HashMap<EventTarget*, RefPtr<AsyncCallChain> > m_xhrCallChains;
HashMap<MutationObserver*, RefPtr<AsyncCallChain> > m_mutationObserverCallChains;
HashMap<ExecutionContextTask*, RefPtr<AsyncCallChain> > m_promiseTaskCallChains;
};
static XMLHttpRequest* toXmlHttpRequest(EventTarget* eventTarget)
{
const AtomicString& interfaceName = eventTarget->interfaceName();
if (interfaceName == EventTargetNames::XMLHttpRequest)
return static_cast<XMLHttpRequest*>(eventTarget);
if (interfaceName == EventTargetNames::XMLHttpRequestUpload)
return static_cast<XMLHttpRequestUpload*>(eventTarget)->xmlHttpRequest();
return 0;
}
AsyncCallStackTracker::AsyncCallStack::AsyncCallStack(const String& description, const ScriptValue& callFrames)
: m_description(description)
, m_callFrames(callFrames)
{
}
AsyncCallStackTracker::AsyncCallStack::~AsyncCallStack()
{
}
AsyncCallStackTracker::AsyncCallStackTracker()
: m_maxAsyncCallStackDepth(0)
{
}
void AsyncCallStackTracker::setAsyncCallStackDepth(int depth)
{
if (depth <= 0) {
m_maxAsyncCallStackDepth = 0;
clear();
} else {
m_maxAsyncCallStackDepth = depth;
}
}
const AsyncCallStackTracker::AsyncCallChain* AsyncCallStackTracker::currentAsyncCallChain() const
{
if (m_currentAsyncCallChain)
ensureMaxAsyncCallChainDepth(m_currentAsyncCallChain.get(), m_maxAsyncCallStackDepth);
return m_currentAsyncCallChain.get();
}
void AsyncCallStackTracker::didInstallTimer(ExecutionContext* context, int timerId, bool singleShot, const ScriptValue& callFrames)
{
ASSERT(context);
ASSERT(isEnabled());
if (!validateCallFrames(callFrames))
return;
ASSERT(timerId > 0);
ExecutionContextData* data = createContextDataIfNeeded(context);
data->m_timerCallChains.set(timerId, createAsyncCallChain(singleShot ? setTimeoutName : setIntervalName, callFrames));
if (!singleShot)
data->m_intervalTimerIds.add(timerId);
}
void AsyncCallStackTracker::didRemoveTimer(ExecutionContext* context, int timerId)
{
ASSERT(context);
ASSERT(isEnabled());
if (timerId <= 0)
return;
ExecutionContextData* data = m_executionContextDataMap.get(context);
if (!data)
return;
data->m_intervalTimerIds.remove(timerId);
data->m_timerCallChains.remove(timerId);
}
void AsyncCallStackTracker::willFireTimer(ExecutionContext* context, int timerId)
{
ASSERT(context);
ASSERT(isEnabled());
ASSERT(timerId > 0);
ASSERT(!m_currentAsyncCallChain);
if (ExecutionContextData* data = m_executionContextDataMap.get(context)) {
if (data->m_intervalTimerIds.contains(timerId))
setCurrentAsyncCallChain(data->m_timerCallChains.get(timerId));
else
setCurrentAsyncCallChain(data->m_timerCallChains.take(timerId));
} else {
setCurrentAsyncCallChain(nullptr);
}
}
void AsyncCallStackTracker::didRequestAnimationFrame(ExecutionContext* context, int callbackId, const ScriptValue& callFrames)
{
ASSERT(context);
ASSERT(isEnabled());
if (!validateCallFrames(callFrames))
return;
ASSERT(callbackId > 0);
ExecutionContextData* data = createContextDataIfNeeded(context);
data->m_animationFrameCallChains.set(callbackId, createAsyncCallChain(requestAnimationFrameName, callFrames));
}
void AsyncCallStackTracker::didCancelAnimationFrame(ExecutionContext* context, int callbackId)
{
ASSERT(context);
ASSERT(isEnabled());
if (callbackId <= 0)
return;
if (ExecutionContextData* data = m_executionContextDataMap.get(context))
data->m_animationFrameCallChains.remove(callbackId);
}
void AsyncCallStackTracker::willFireAnimationFrame(ExecutionContext* context, int callbackId)
{
ASSERT(context);
ASSERT(isEnabled());
ASSERT(callbackId > 0);
ASSERT(!m_currentAsyncCallChain);
if (ExecutionContextData* data = m_executionContextDataMap.get(context))
setCurrentAsyncCallChain(data->m_animationFrameCallChains.take(callbackId));
else
setCurrentAsyncCallChain(nullptr);
}
void AsyncCallStackTracker::didAddEventListener(EventTarget* eventTarget, const AtomicString& eventType, EventListener* listener, bool useCapture, const ScriptValue& callFrames)
{
ASSERT(eventTarget->executionContext());
ASSERT(isEnabled());
if (!validateCallFrames(callFrames) || toXmlHttpRequest(eventTarget))
return;
StringBuilder description;
description.append(eventTarget->interfaceName());
if (!description.isEmpty())
description.append(".");
if (listener->isAttribute()) {
description.append("on");
description.append(eventType);
} else {
description.append("addEventListener(\"");
description.append(eventType);
description.append("\")");
}
ExecutionContextData* data = createContextDataIfNeeded(eventTarget->executionContext());
data->addEventListenerData(eventTarget, eventType, std::make_pair(RegisteredEventListener(listener, useCapture), createAsyncCallChain(description.toString(), callFrames)));
}
void AsyncCallStackTracker::didRemoveEventListener(EventTarget* eventTarget, const AtomicString& eventType, EventListener* listener, bool useCapture)
{
ASSERT(eventTarget->executionContext());
ASSERT(isEnabled());
if (ExecutionContextData* data = m_executionContextDataMap.get(eventTarget->executionContext()))
data->removeEventListenerData(eventTarget, eventType, RegisteredEventListener(listener, useCapture));
}
void AsyncCallStackTracker::didRemoveAllEventListeners(EventTarget* eventTarget)
{
ASSERT(eventTarget->executionContext());
ASSERT(isEnabled());
if (ExecutionContextData* data = m_executionContextDataMap.get(eventTarget->executionContext()))
data->m_eventTargetCallChains.remove(eventTarget);
}
void AsyncCallStackTracker::willHandleEvent(EventTarget* eventTarget, const AtomicString& eventType, EventListener* listener, bool useCapture)
{
ASSERT(eventTarget->executionContext());
ASSERT(isEnabled());
if (XMLHttpRequest* xhr = toXmlHttpRequest(eventTarget)) {
willHandleXHREvent(xhr, eventTarget, eventType);
return;
}
if (ExecutionContextData* data = m_executionContextDataMap.get(eventTarget->executionContext()))
setCurrentAsyncCallChain(data->findEventListenerData(eventTarget, eventType, RegisteredEventListener(listener, useCapture)));
else
setCurrentAsyncCallChain(nullptr);
}
void AsyncCallStackTracker::willLoadXHR(XMLHttpRequest* xhr, const ScriptValue& callFrames)
{
ASSERT(xhr->executionContext());
ASSERT(isEnabled());
if (!validateCallFrames(callFrames))
return;
ExecutionContextData* data = createContextDataIfNeeded(xhr->executionContext());
data->m_xhrCallChains.set(xhr, createAsyncCallChain(xhrSendName, callFrames));
}
void AsyncCallStackTracker::willHandleXHREvent(XMLHttpRequest* xhr, EventTarget* eventTarget, const AtomicString& eventType)
{
ASSERT(xhr->executionContext());
ASSERT(isEnabled());
if (ExecutionContextData* data = m_executionContextDataMap.get(xhr->executionContext())) {
bool isXHRDownload = (xhr == eventTarget);
if (isXHRDownload && eventType == EventTypeNames::loadend)
setCurrentAsyncCallChain(data->m_xhrCallChains.take(xhr));
else
setCurrentAsyncCallChain(data->m_xhrCallChains.get(xhr));
} else {
setCurrentAsyncCallChain(nullptr);
}
}
void AsyncCallStackTracker::didEnqueueMutationRecord(ExecutionContext* context, MutationObserver* observer, const ScriptValue& callFrames)
{
ASSERT(context);
ASSERT(isEnabled());
if (!validateCallFrames(callFrames))
return;
ExecutionContextData* data = createContextDataIfNeeded(context);
data->m_mutationObserverCallChains.set(observer, createAsyncCallChain(enqueueMutationRecordName, callFrames));
}
bool AsyncCallStackTracker::hasEnqueuedMutationRecord(ExecutionContext* context, MutationObserver* observer)
{
ASSERT(context);
ASSERT(isEnabled());
if (ExecutionContextData* data = m_executionContextDataMap.get(context))
return data->m_mutationObserverCallChains.contains(observer);
return false;
}
void AsyncCallStackTracker::didClearAllMutationRecords(ExecutionContext* context, MutationObserver* observer)
{
ASSERT(context);
ASSERT(isEnabled());
if (ExecutionContextData* data = m_executionContextDataMap.get(context))
data->m_mutationObserverCallChains.remove(observer);
}
void AsyncCallStackTracker::willDeliverMutationRecords(ExecutionContext* context, MutationObserver* observer)
{
ASSERT(context);
ASSERT(isEnabled());
if (ExecutionContextData* data = m_executionContextDataMap.get(context))
setCurrentAsyncCallChain(data->m_mutationObserverCallChains.take(observer));
else
setCurrentAsyncCallChain(nullptr);
}
void AsyncCallStackTracker::didPostPromiseTask(ExecutionContext* context, ExecutionContextTask* task, bool isResolved, const ScriptValue& callFrames)
{
ASSERT(context);
ASSERT(isEnabled());
if (validateCallFrames(callFrames)) {
ExecutionContextData* data = createContextDataIfNeeded(context);
data->m_promiseTaskCallChains.set(task, createAsyncCallChain(isResolved ? promiseResolved : promiseRejected, callFrames));
} else if (m_currentAsyncCallChain) {
// Propagate async call stack to the re-posted task to update a derived Promise.
ExecutionContextData* data = createContextDataIfNeeded(context);
data->m_promiseTaskCallChains.set(task, m_currentAsyncCallChain);
}
}
void AsyncCallStackTracker::willPerformPromiseTask(ExecutionContext* context, ExecutionContextTask* task)
{
ASSERT(context);
ASSERT(isEnabled());
if (ExecutionContextData* data = m_executionContextDataMap.get(context))
setCurrentAsyncCallChain(data->m_promiseTaskCallChains.take(task));
else
setCurrentAsyncCallChain(nullptr);
}
void AsyncCallStackTracker::didFireAsyncCall()
{
clearCurrentAsyncCallChain();
}
PassRefPtr<AsyncCallStackTracker::AsyncCallChain> AsyncCallStackTracker::createAsyncCallChain(const String& description, const ScriptValue& callFrames)
{
RefPtr<AsyncCallChain> chain = adoptRef(m_currentAsyncCallChain ? new AsyncCallStackTracker::AsyncCallChain(*m_currentAsyncCallChain) : new AsyncCallStackTracker::AsyncCallChain());
ensureMaxAsyncCallChainDepth(chain.get(), m_maxAsyncCallStackDepth - 1);
chain->m_callStacks.prepend(adoptRef(new AsyncCallStackTracker::AsyncCallStack(description, callFrames)));
return chain.release();
}
void AsyncCallStackTracker::setCurrentAsyncCallChain(PassRefPtr<AsyncCallChain> chain)
{
if (V8RecursionScope::recursionLevel()) {
if (m_currentAsyncCallChain)
++m_nestedAsyncCallCount;
} else {
// Current AsyncCallChain corresponds to the bottommost JS call frame.
m_currentAsyncCallChain = chain;
m_nestedAsyncCallCount = m_currentAsyncCallChain ? 1 : 0;
}
}
void AsyncCallStackTracker::clearCurrentAsyncCallChain()
{
if (!m_nestedAsyncCallCount)
return;
--m_nestedAsyncCallCount;
if (!m_nestedAsyncCallCount)
m_currentAsyncCallChain.clear();
}
void AsyncCallStackTracker::ensureMaxAsyncCallChainDepth(AsyncCallChain* chain, unsigned maxDepth)
{
while (chain->m_callStacks.size() > maxDepth)
chain->m_callStacks.removeLast();
}
bool AsyncCallStackTracker::validateCallFrames(const ScriptValue& callFrames)
{
return !callFrames.hasNoValue();
}
AsyncCallStackTracker::ExecutionContextData* AsyncCallStackTracker::createContextDataIfNeeded(ExecutionContext* context)
{
ExecutionContextData* data = m_executionContextDataMap.get(context);
if (!data) {
data = new AsyncCallStackTracker::ExecutionContextData(this, context);
m_executionContextDataMap.set(context, data);
}
return data;
}
void AsyncCallStackTracker::clear()
{
m_currentAsyncCallChain.clear();
m_nestedAsyncCallCount = 0;
ExecutionContextDataMap copy;
m_executionContextDataMap.swap(copy);
for (ExecutionContextDataMap::const_iterator it = copy.begin(); it != copy.end(); ++it)
delete it->value;
}
} // namespace WebCore