| Index: sky/engine/core/inspector/AsyncCallStackTracker.cpp
|
| diff --git a/sky/engine/core/inspector/AsyncCallStackTracker.cpp b/sky/engine/core/inspector/AsyncCallStackTracker.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..43adab66835da5f12f352cf7abb96785970320f3
|
| --- /dev/null
|
| +++ b/sky/engine/core/inspector/AsyncCallStackTracker.cpp
|
| @@ -0,0 +1,401 @@
|
| +/*
|
| + * 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/core/v8/V8Binding.h"
|
| +#include "bindings/core/v8/V8RecursionScope.h"
|
| +#include "core/dom/ExecutionContext.h"
|
| +// #include "core/dom/ExecutionContextTask.h"
|
| +#include "core/events/Event.h"
|
| +#include "core/events/EventTarget.h"
|
| +#include "wtf/text/StringBuilder.h"
|
| +#include "wtf/text/StringHash.h"
|
| +#include <v8.h>
|
| +
|
| +namespace {
|
| +
|
| +static const char setTimeoutName[] = "setTimeout";
|
| +static const char setIntervalName[] = "setInterval";
|
| +static const char requestAnimationFrameName[] = "requestAnimationFrame";
|
| +static const char enqueueMutationRecordName[] = "Mutation";
|
| +
|
| +}
|
| +
|
| +namespace blink {
|
| +
|
| +void AsyncCallStackTracker::ExecutionContextData::contextDestroyed()
|
| +{
|
| + ASSERT(executionContext());
|
| + OwnPtr<ExecutionContextData> self = m_tracker->m_executionContextDataMap.take(executionContext());
|
| + ASSERT_UNUSED(self, self == this);
|
| + ContextLifecycleObserver::contextDestroyed();
|
| +}
|
| +
|
| +int AsyncCallStackTracker::ExecutionContextData::circularSequentialID()
|
| +{
|
| + ++m_circularSequentialID;
|
| + if (m_circularSequentialID <= 0)
|
| + m_circularSequentialID = 1;
|
| + return m_circularSequentialID;
|
| +}
|
| +
|
| +AsyncCallStackTracker::AsyncCallStack::AsyncCallStack(const String& description, const ScriptValue& callFrames)
|
| + : m_description(description)
|
| + , m_callFrames(callFrames)
|
| +{
|
| +}
|
| +
|
| +AsyncCallStackTracker::AsyncCallStack::~AsyncCallStack()
|
| +{
|
| +}
|
| +
|
| +AsyncCallStackTracker::AsyncCallStackTracker()
|
| + : m_maxAsyncCallStackDepth(0)
|
| + , m_nestedAsyncCallCount(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(context, data->m_timerCallChains.get(timerId));
|
| + else
|
| + setCurrentAsyncCallChain(context, data->m_timerCallChains.take(timerId));
|
| + } else {
|
| + setCurrentAsyncCallChain(context, 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(context, data->m_animationFrameCallChains.take(callbackId));
|
| + else
|
| + setCurrentAsyncCallChain(context, nullptr);
|
| +}
|
| +
|
| +void AsyncCallStackTracker::didEnqueueEvent(EventTarget* eventTarget, Event* event, const ScriptValue& callFrames)
|
| +{
|
| + ASSERT(eventTarget->executionContext());
|
| + ASSERT(isEnabled());
|
| + if (!validateCallFrames(callFrames))
|
| + return;
|
| + ExecutionContextData* data = createContextDataIfNeeded(eventTarget->executionContext());
|
| + data->m_eventCallChains.set(event, createAsyncCallChain(event->type(), callFrames));
|
| +}
|
| +
|
| +void AsyncCallStackTracker::didRemoveEvent(EventTarget* eventTarget, Event* event)
|
| +{
|
| + ASSERT(eventTarget->executionContext());
|
| + ASSERT(isEnabled());
|
| + if (ExecutionContextData* data = m_executionContextDataMap.get(eventTarget->executionContext()))
|
| + data->m_eventCallChains.remove(event);
|
| +}
|
| +
|
| +void AsyncCallStackTracker::willHandleEvent(EventTarget* eventTarget, Event* event, EventListener* listener, bool useCapture)
|
| +{
|
| + ASSERT(eventTarget->executionContext());
|
| + ASSERT(isEnabled());
|
| + ExecutionContext* context = eventTarget->executionContext();
|
| + if (ExecutionContextData* data = m_executionContextDataMap.get(context))
|
| + setCurrentAsyncCallChain(context, data->m_eventCallChains.get(event));
|
| + else
|
| + setCurrentAsyncCallChain(context, 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(context, data->m_mutationObserverCallChains.take(observer));
|
| + else
|
| + setCurrentAsyncCallChain(context, nullptr);
|
| +}
|
| +
|
| +// void AsyncCallStackTracker::didPostExecutionContextTask(ExecutionContext* context, ExecutionContextTask* task, const ScriptValue& callFrames)
|
| +// {
|
| +// ASSERT(context);
|
| +// ASSERT(isEnabled());
|
| +// if (!validateCallFrames(callFrames))
|
| +// return;
|
| +// ExecutionContextData* data = createContextDataIfNeeded(context);
|
| +// data->m_executionContextTaskCallChains.set(task, createAsyncCallChain(task->taskNameForInstrumentation(), callFrames));
|
| +// }
|
| +
|
| +// void AsyncCallStackTracker::didKillAllExecutionContextTasks(ExecutionContext* context)
|
| +// {
|
| +// ASSERT(context);
|
| +// ASSERT(isEnabled());
|
| +// if (ExecutionContextData* data = m_executionContextDataMap.get(context))
|
| +// data->m_executionContextTaskCallChains.clear();
|
| +// }
|
| +
|
| +// void AsyncCallStackTracker::willPerformExecutionContextTask(ExecutionContext* context, ExecutionContextTask* task)
|
| +// {
|
| +// ASSERT(context);
|
| +// ASSERT(isEnabled());
|
| +// if (ExecutionContextData* data = m_executionContextDataMap.get(context))
|
| +// setCurrentAsyncCallChain(context, data->m_executionContextTaskCallChains.take(task));
|
| +// else
|
| +// setCurrentAsyncCallChain(context, nullptr);
|
| +// }
|
| +
|
| +static String makeV8AsyncTaskUniqueId(const String& eventName, int id)
|
| +{
|
| + StringBuilder builder;
|
| + builder.append(eventName);
|
| + builder.appendNumber(id);
|
| + return builder.toString();
|
| +}
|
| +
|
| +void AsyncCallStackTracker::didEnqueueV8AsyncTask(ExecutionContext* context, const String& eventName, int id, const ScriptValue& callFrames)
|
| +{
|
| + ASSERT(context);
|
| + ASSERT(isEnabled());
|
| + if (!validateCallFrames(callFrames))
|
| + return;
|
| + ExecutionContextData* data = createContextDataIfNeeded(context);
|
| + data->m_v8AsyncTaskCallChains.set(makeV8AsyncTaskUniqueId(eventName, id), createAsyncCallChain(eventName, callFrames));
|
| +}
|
| +
|
| +void AsyncCallStackTracker::willHandleV8AsyncTask(ExecutionContext* context, const String& eventName, int id)
|
| +{
|
| + ASSERT(context);
|
| + ASSERT(isEnabled());
|
| + if (ExecutionContextData* data = m_executionContextDataMap.get(context))
|
| + setCurrentAsyncCallChain(context, data->m_v8AsyncTaskCallChains.take(makeV8AsyncTaskUniqueId(eventName, id)));
|
| + else
|
| + setCurrentAsyncCallChain(context, nullptr);
|
| +}
|
| +
|
| +int AsyncCallStackTracker::traceAsyncOperationStarting(ExecutionContext* context, const String& operationName, const ScriptValue& callFrames)
|
| +{
|
| + ASSERT(context);
|
| + ASSERT(isEnabled());
|
| + if (!validateCallFrames(callFrames))
|
| + return 0;
|
| + ExecutionContextData* data = createContextDataIfNeeded(context);
|
| + int id = data->circularSequentialID();
|
| + while (data->m_asyncOperationCallChains.contains(id))
|
| + id = data->circularSequentialID();
|
| + data->m_asyncOperationCallChains.set(id, createAsyncCallChain(operationName, callFrames));
|
| + return id;
|
| +}
|
| +
|
| +void AsyncCallStackTracker::traceAsyncOperationCompleted(ExecutionContext* context, int operationId)
|
| +{
|
| + ASSERT(context);
|
| + ASSERT(isEnabled());
|
| + if (operationId <= 0)
|
| + return;
|
| + if (ExecutionContextData* data = m_executionContextDataMap.get(context))
|
| + data->m_asyncOperationCallChains.remove(operationId);
|
| +}
|
| +
|
| +void AsyncCallStackTracker::traceAsyncCallbackStarting(ExecutionContext* context, int operationId)
|
| +{
|
| + ASSERT(context);
|
| + ASSERT(isEnabled());
|
| + if (ExecutionContextData* data = m_executionContextDataMap.get(context))
|
| + setCurrentAsyncCallChain(context, operationId > 0 ? data->m_asyncOperationCallChains.get(operationId) : nullptr);
|
| + else
|
| + setCurrentAsyncCallChain(context, nullptr);
|
| +}
|
| +
|
| +void AsyncCallStackTracker::didFireAsyncCall()
|
| +{
|
| + clearCurrentAsyncCallChain();
|
| +}
|
| +
|
| +PassRefPtr<AsyncCallStackTracker::AsyncCallChain> AsyncCallStackTracker::createAsyncCallChain(const String& description, const ScriptValue& callFrames)
|
| +{
|
| + if (callFrames.isEmpty()) {
|
| + ASSERT(m_currentAsyncCallChain);
|
| + return m_currentAsyncCallChain; // Propogate async call stack chain.
|
| + }
|
| + 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(ExecutionContext* context, PassRefPtr<AsyncCallChain> chain)
|
| +{
|
| + if (chain && !V8RecursionScope::recursionLevel(toIsolate(context))) {
|
| + // Current AsyncCallChain corresponds to the bottommost JS call frame.
|
| + m_currentAsyncCallChain = chain;
|
| + m_nestedAsyncCallCount = 1;
|
| + } else {
|
| + if (m_currentAsyncCallChain)
|
| + ++m_nestedAsyncCallCount;
|
| + }
|
| +}
|
| +
|
| +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.isEmpty() || m_currentAsyncCallChain;
|
| +}
|
| +
|
| +AsyncCallStackTracker::ExecutionContextData* AsyncCallStackTracker::createContextDataIfNeeded(ExecutionContext* context)
|
| +{
|
| + ExecutionContextData* data = m_executionContextDataMap.get(context);
|
| + if (!data) {
|
| + data = m_executionContextDataMap.set(context, adoptPtr(new AsyncCallStackTracker::ExecutionContextData(this, context)))
|
| + .storedValue->value.get();
|
| + }
|
| + return data;
|
| +}
|
| +
|
| +void AsyncCallStackTracker::clear()
|
| +{
|
| + m_currentAsyncCallChain.clear();
|
| + m_nestedAsyncCallCount = 0;
|
| + m_executionContextDataMap.clear();
|
| +}
|
| +
|
| +} // namespace blink
|
|
|