1/*
2 * Copyright (C) 2013 Google 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 are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32#include "core/inspector/AsyncCallStackTracker.h"
33
34#include "bindings/core/v8/V8Binding.h"
35#include "bindings/core/v8/V8RecursionScope.h"
36#include "core/dom/ExecutionContext.h"
37#include "core/dom/ExecutionContextTask.h"
38#include "core/dom/Microtask.h"
39#include "core/events/Event.h"
40#include "core/events/EventTarget.h"
41#include "core/xml/XMLHttpRequest.h"
42#include "core/xml/XMLHttpRequestUpload.h"
43#include "wtf/text/StringBuilder.h"
44#include "wtf/text/StringHash.h"
45#include <v8.h>
46
47namespace {
48
49static const char setTimeoutName[] = "setTimeout";
50static const char setIntervalName[] = "setInterval";
51static const char requestAnimationFrameName[] = "requestAnimationFrame";
52static const char xhrSendName[] = "XMLHttpRequest.send";
53static const char enqueueMutationRecordName[] = "Mutation";
54
55}
56
57namespace blink {
58
59void AsyncCallStackTracker::ExecutionContextData::contextDestroyed()
60{
61    ASSERT(executionContext());
62    OwnPtrWillBeRawPtr<ExecutionContextData> self = m_tracker->m_executionContextDataMap.take(executionContext());
63    ASSERT_UNUSED(self, self == this);
64    ContextLifecycleObserver::contextDestroyed();
65}
66
67int AsyncCallStackTracker::ExecutionContextData::circularSequentialID()
68{
69    ++m_circularSequentialID;
70    if (m_circularSequentialID <= 0)
71        m_circularSequentialID = 1;
72    return m_circularSequentialID;
73}
74
75void AsyncCallStackTracker::ExecutionContextData::trace(Visitor* visitor)
76{
77    visitor->trace(m_tracker);
78#if ENABLE(OILPAN)
79    visitor->trace(m_timerCallChains);
80    visitor->trace(m_animationFrameCallChains);
81    visitor->trace(m_eventCallChains);
82    visitor->trace(m_xhrCallChains);
83    visitor->trace(m_mutationObserverCallChains);
84    visitor->trace(m_executionContextTaskCallChains);
85    visitor->trace(m_v8AsyncTaskCallChains);
86    visitor->trace(m_asyncOperationCallChains);
87#endif
88}
89
90static XMLHttpRequest* toXmlHttpRequest(EventTarget* eventTarget)
91{
92    const AtomicString& interfaceName = eventTarget->interfaceName();
93    if (interfaceName == EventTargetNames::XMLHttpRequest)
94        return static_cast<XMLHttpRequest*>(eventTarget);
95    if (interfaceName == EventTargetNames::XMLHttpRequestUpload)
96        return static_cast<XMLHttpRequestUpload*>(eventTarget)->xmlHttpRequest();
97    return 0;
98}
99
100void AsyncCallStackTracker::AsyncCallChain::trace(Visitor* visitor)
101{
102    visitor->trace(m_callStacks);
103}
104
105AsyncCallStackTracker::AsyncCallStack::AsyncCallStack(const String& description, const ScriptValue& callFrames)
106    : m_description(description)
107    , m_callFrames(callFrames)
108{
109}
110
111AsyncCallStackTracker::AsyncCallStack::~AsyncCallStack()
112{
113}
114
115AsyncCallStackTracker::AsyncCallStackTracker()
116    : m_maxAsyncCallStackDepth(0)
117    , m_nestedAsyncCallCount(0)
118{
119}
120
121void AsyncCallStackTracker::setAsyncCallStackDepth(int depth)
122{
123    if (depth <= 0) {
124        m_maxAsyncCallStackDepth = 0;
125        clear();
126    } else {
127        m_maxAsyncCallStackDepth = depth;
128    }
129}
130
131const AsyncCallStackTracker::AsyncCallChain* AsyncCallStackTracker::currentAsyncCallChain() const
132{
133    if (m_currentAsyncCallChain)
134        ensureMaxAsyncCallChainDepth(m_currentAsyncCallChain.get(), m_maxAsyncCallStackDepth);
135    return m_currentAsyncCallChain.get();
136}
137
138void AsyncCallStackTracker::didInstallTimer(ExecutionContext* context, int timerId, bool singleShot, const ScriptValue& callFrames)
139{
140    ASSERT(context);
141    ASSERT(isEnabled());
142    if (!validateCallFrames(callFrames))
143        return;
144    ASSERT(timerId > 0);
145    ExecutionContextData* data = createContextDataIfNeeded(context);
146    data->m_timerCallChains.set(timerId, createAsyncCallChain(singleShot ? setTimeoutName : setIntervalName, callFrames));
147    if (!singleShot)
148        data->m_intervalTimerIds.add(timerId);
149}
150
151void AsyncCallStackTracker::didRemoveTimer(ExecutionContext* context, int timerId)
152{
153    ASSERT(context);
154    ASSERT(isEnabled());
155    if (timerId <= 0)
156        return;
157    ExecutionContextData* data = m_executionContextDataMap.get(context);
158    if (!data)
159        return;
160    data->m_intervalTimerIds.remove(timerId);
161    data->m_timerCallChains.remove(timerId);
162}
163
164void AsyncCallStackTracker::willFireTimer(ExecutionContext* context, int timerId)
165{
166    ASSERT(context);
167    ASSERT(isEnabled());
168    ASSERT(timerId > 0);
169    ASSERT(!m_currentAsyncCallChain);
170    if (ExecutionContextData* data = m_executionContextDataMap.get(context)) {
171        if (data->m_intervalTimerIds.contains(timerId))
172            setCurrentAsyncCallChain(context, data->m_timerCallChains.get(timerId));
173        else
174            setCurrentAsyncCallChain(context, data->m_timerCallChains.take(timerId));
175    } else {
176        setCurrentAsyncCallChain(context, nullptr);
177    }
178}
179
180void AsyncCallStackTracker::didRequestAnimationFrame(ExecutionContext* context, int callbackId, const ScriptValue& callFrames)
181{
182    ASSERT(context);
183    ASSERT(isEnabled());
184    if (!validateCallFrames(callFrames))
185        return;
186    ASSERT(callbackId > 0);
187    ExecutionContextData* data = createContextDataIfNeeded(context);
188    data->m_animationFrameCallChains.set(callbackId, createAsyncCallChain(requestAnimationFrameName, callFrames));
189}
190
191void AsyncCallStackTracker::didCancelAnimationFrame(ExecutionContext* context, int callbackId)
192{
193    ASSERT(context);
194    ASSERT(isEnabled());
195    if (callbackId <= 0)
196        return;
197    if (ExecutionContextData* data = m_executionContextDataMap.get(context))
198        data->m_animationFrameCallChains.remove(callbackId);
199}
200
201void AsyncCallStackTracker::willFireAnimationFrame(ExecutionContext* context, int callbackId)
202{
203    ASSERT(context);
204    ASSERT(isEnabled());
205    ASSERT(callbackId > 0);
206    ASSERT(!m_currentAsyncCallChain);
207    if (ExecutionContextData* data = m_executionContextDataMap.get(context))
208        setCurrentAsyncCallChain(context, data->m_animationFrameCallChains.take(callbackId));
209    else
210        setCurrentAsyncCallChain(context, nullptr);
211}
212
213void AsyncCallStackTracker::didEnqueueEvent(EventTarget* eventTarget, Event* event, const ScriptValue& callFrames)
214{
215    ASSERT(eventTarget->executionContext());
216    ASSERT(isEnabled());
217    if (!validateCallFrames(callFrames))
218        return;
219    ExecutionContextData* data = createContextDataIfNeeded(eventTarget->executionContext());
220    data->m_eventCallChains.set(event, createAsyncCallChain(event->type(), callFrames));
221}
222
223void AsyncCallStackTracker::didRemoveEvent(EventTarget* eventTarget, Event* event)
224{
225    ASSERT(eventTarget->executionContext());
226    ASSERT(isEnabled());
227    if (ExecutionContextData* data = m_executionContextDataMap.get(eventTarget->executionContext()))
228        data->m_eventCallChains.remove(event);
229}
230
231void AsyncCallStackTracker::willHandleEvent(EventTarget* eventTarget, Event* event, EventListener* listener, bool useCapture)
232{
233    ASSERT(eventTarget->executionContext());
234    ASSERT(isEnabled());
235    if (XMLHttpRequest* xhr = toXmlHttpRequest(eventTarget)) {
236        willHandleXHREvent(xhr, event);
237    } else {
238        ExecutionContext* context = eventTarget->executionContext();
239        if (ExecutionContextData* data = m_executionContextDataMap.get(context))
240            setCurrentAsyncCallChain(context, data->m_eventCallChains.get(event));
241        else
242            setCurrentAsyncCallChain(context, nullptr);
243    }
244}
245
246void AsyncCallStackTracker::willLoadXHR(XMLHttpRequest* xhr, const ScriptValue& callFrames)
247{
248    ASSERT(xhr->executionContext());
249    ASSERT(isEnabled());
250    if (!validateCallFrames(callFrames))
251        return;
252    ExecutionContextData* data = createContextDataIfNeeded(xhr->executionContext());
253    data->m_xhrCallChains.set(xhr, createAsyncCallChain(xhrSendName, callFrames));
254}
255
256void AsyncCallStackTracker::didLoadXHR(XMLHttpRequest* xhr)
257{
258    ASSERT(xhr->executionContext());
259    ASSERT(isEnabled());
260    if (ExecutionContextData* data = m_executionContextDataMap.get(xhr->executionContext()))
261        data->m_xhrCallChains.remove(xhr);
262}
263
264void AsyncCallStackTracker::willHandleXHREvent(XMLHttpRequest* xhr, Event* event)
265{
266    ExecutionContext* context = xhr->executionContext();
267    ASSERT(context);
268    ASSERT(isEnabled());
269    if (ExecutionContextData* data = m_executionContextDataMap.get(context))
270        setCurrentAsyncCallChain(context, data->m_xhrCallChains.get(xhr));
271    else
272        setCurrentAsyncCallChain(context, nullptr);
273}
274
275void AsyncCallStackTracker::didEnqueueMutationRecord(ExecutionContext* context, MutationObserver* observer, const ScriptValue& callFrames)
276{
277    ASSERT(context);
278    ASSERT(isEnabled());
279    if (!validateCallFrames(callFrames))
280        return;
281    ExecutionContextData* data = createContextDataIfNeeded(context);
282    data->m_mutationObserverCallChains.set(observer, createAsyncCallChain(enqueueMutationRecordName, callFrames));
283}
284
285bool AsyncCallStackTracker::hasEnqueuedMutationRecord(ExecutionContext* context, MutationObserver* observer)
286{
287    ASSERT(context);
288    ASSERT(isEnabled());
289    if (ExecutionContextData* data = m_executionContextDataMap.get(context))
290        return data->m_mutationObserverCallChains.contains(observer);
291    return false;
292}
293
294void AsyncCallStackTracker::didClearAllMutationRecords(ExecutionContext* context, MutationObserver* observer)
295{
296    ASSERT(context);
297    ASSERT(isEnabled());
298    if (ExecutionContextData* data = m_executionContextDataMap.get(context))
299        data->m_mutationObserverCallChains.remove(observer);
300}
301
302void AsyncCallStackTracker::willDeliverMutationRecords(ExecutionContext* context, MutationObserver* observer)
303{
304    ASSERT(context);
305    ASSERT(isEnabled());
306    if (ExecutionContextData* data = m_executionContextDataMap.get(context))
307        setCurrentAsyncCallChain(context, data->m_mutationObserverCallChains.take(observer));
308    else
309        setCurrentAsyncCallChain(context, nullptr);
310}
311
312void AsyncCallStackTracker::didPostExecutionContextTask(ExecutionContext* context, ExecutionContextTask* task, const ScriptValue& callFrames)
313{
314    ASSERT(context);
315    ASSERT(isEnabled());
316    if (!validateCallFrames(callFrames))
317        return;
318    ExecutionContextData* data = createContextDataIfNeeded(context);
319    data->m_executionContextTaskCallChains.set(task, createAsyncCallChain(task->taskNameForInstrumentation(), callFrames));
320}
321
322void AsyncCallStackTracker::didKillAllExecutionContextTasks(ExecutionContext* context)
323{
324    ASSERT(context);
325    ASSERT(isEnabled());
326    if (ExecutionContextData* data = m_executionContextDataMap.get(context))
327        data->m_executionContextTaskCallChains.clear();
328}
329
330void AsyncCallStackTracker::willPerformExecutionContextTask(ExecutionContext* context, ExecutionContextTask* task)
331{
332    ASSERT(context);
333    ASSERT(isEnabled());
334    if (ExecutionContextData* data = m_executionContextDataMap.get(context))
335        setCurrentAsyncCallChain(context, data->m_executionContextTaskCallChains.take(task));
336    else
337        setCurrentAsyncCallChain(context, nullptr);
338}
339
340static String makeV8AsyncTaskUniqueId(const String& eventName, int id)
341{
342    StringBuilder builder;
343    builder.append(eventName);
344    builder.appendNumber(id);
345    return builder.toString();
346}
347
348void AsyncCallStackTracker::didEnqueueV8AsyncTask(ExecutionContext* context, const String& eventName, int id, const ScriptValue& callFrames)
349{
350    ASSERT(context);
351    ASSERT(isEnabled());
352    if (!validateCallFrames(callFrames))
353        return;
354    ExecutionContextData* data = createContextDataIfNeeded(context);
355    data->m_v8AsyncTaskCallChains.set(makeV8AsyncTaskUniqueId(eventName, id), createAsyncCallChain(eventName, callFrames));
356}
357
358void AsyncCallStackTracker::willHandleV8AsyncTask(ExecutionContext* context, const String& eventName, int id)
359{
360    ASSERT(context);
361    ASSERT(isEnabled());
362    if (ExecutionContextData* data = m_executionContextDataMap.get(context))
363        setCurrentAsyncCallChain(context, data->m_v8AsyncTaskCallChains.take(makeV8AsyncTaskUniqueId(eventName, id)));
364    else
365        setCurrentAsyncCallChain(context, nullptr);
366}
367
368int AsyncCallStackTracker::traceAsyncOperationStarting(ExecutionContext* context, const String& operationName, const ScriptValue& callFrames)
369{
370    ASSERT(context);
371    ASSERT(isEnabled());
372    if (!validateCallFrames(callFrames))
373        return 0;
374    ExecutionContextData* data = createContextDataIfNeeded(context);
375    int id = data->circularSequentialID();
376    while (data->m_asyncOperationCallChains.contains(id))
377        id = data->circularSequentialID();
378    data->m_asyncOperationCallChains.set(id, createAsyncCallChain(operationName, callFrames));
379    return id;
380}
381
382void AsyncCallStackTracker::traceAsyncOperationCompleted(ExecutionContext* context, int operationId)
383{
384    ASSERT(context);
385    ASSERT(isEnabled());
386    if (operationId <= 0)
387        return;
388    if (ExecutionContextData* data = m_executionContextDataMap.get(context))
389        data->m_asyncOperationCallChains.remove(operationId);
390}
391
392void AsyncCallStackTracker::traceAsyncCallbackStarting(ExecutionContext* context, int operationId)
393{
394    ASSERT(context);
395    ASSERT(isEnabled());
396    if (ExecutionContextData* data = m_executionContextDataMap.get(context))
397        setCurrentAsyncCallChain(context, operationId > 0 ? data->m_asyncOperationCallChains.get(operationId) : nullptr);
398    else
399        setCurrentAsyncCallChain(context, nullptr);
400}
401
402void AsyncCallStackTracker::didFireAsyncCall()
403{
404    clearCurrentAsyncCallChain();
405}
406
407PassRefPtrWillBeRawPtr<AsyncCallStackTracker::AsyncCallChain> AsyncCallStackTracker::createAsyncCallChain(const String& description, const ScriptValue& callFrames)
408{
409    if (callFrames.isEmpty()) {
410        ASSERT(m_currentAsyncCallChain);
411        return m_currentAsyncCallChain; // Propogate async call stack chain.
412    }
413    RefPtrWillBeRawPtr<AsyncCallChain> chain = adoptRefWillBeNoop(m_currentAsyncCallChain ? new AsyncCallStackTracker::AsyncCallChain(*m_currentAsyncCallChain) : new AsyncCallStackTracker::AsyncCallChain());
414    ensureMaxAsyncCallChainDepth(chain.get(), m_maxAsyncCallStackDepth - 1);
415    chain->m_callStacks.prepend(adoptRefWillBeNoop(new AsyncCallStackTracker::AsyncCallStack(description, callFrames)));
416    return chain.release();
417}
418
419void AsyncCallStackTracker::setCurrentAsyncCallChain(ExecutionContext* context, PassRefPtrWillBeRawPtr<AsyncCallChain> chain)
420{
421    v8::Isolate* isolate = toIsolate(context);
422    int recursionLevel = V8RecursionScope::recursionLevel(isolate);
423    if (chain && (!recursionLevel || (recursionLevel == 1 && Microtask::performingCheckpoint(isolate)))) {
424        // Current AsyncCallChain corresponds to the bottommost JS call frame.
425        m_currentAsyncCallChain = chain;
426        m_nestedAsyncCallCount = 1;
427    } else {
428        if (m_currentAsyncCallChain)
429            ++m_nestedAsyncCallCount;
430    }
431}
432
433void AsyncCallStackTracker::clearCurrentAsyncCallChain()
434{
435    if (!m_nestedAsyncCallCount)
436        return;
437    --m_nestedAsyncCallCount;
438    if (!m_nestedAsyncCallCount)
439        m_currentAsyncCallChain.clear();
440}
441
442void AsyncCallStackTracker::ensureMaxAsyncCallChainDepth(AsyncCallChain* chain, unsigned maxDepth)
443{
444    while (chain->m_callStacks.size() > maxDepth)
445        chain->m_callStacks.removeLast();
446}
447
448bool AsyncCallStackTracker::validateCallFrames(const ScriptValue& callFrames)
449{
450    return !callFrames.isEmpty() || m_currentAsyncCallChain;
451}
452
453AsyncCallStackTracker::ExecutionContextData* AsyncCallStackTracker::createContextDataIfNeeded(ExecutionContext* context)
454{
455    ExecutionContextData* data = m_executionContextDataMap.get(context);
456    if (!data) {
457        data = m_executionContextDataMap.set(context, adoptPtrWillBeNoop(new AsyncCallStackTracker::ExecutionContextData(this, context)))
458            .storedValue->value.get();
459    }
460    return data;
461}
462
463void AsyncCallStackTracker::clear()
464{
465    m_currentAsyncCallChain.clear();
466    m_nestedAsyncCallCount = 0;
467    m_executionContextDataMap.clear();
468}
469
470void AsyncCallStackTracker::trace(Visitor* visitor)
471{
472    visitor->trace(m_currentAsyncCallChain);
473#if ENABLE(OILPAN)
474    visitor->trace(m_executionContextDataMap);
475#endif
476}
477
478} // namespace blink
479