Home | History | Annotate | Download | only in inspector
      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/v8/V8RecursionScope.h"
     35 #include "core/dom/ContextLifecycleObserver.h"
     36 #include "core/dom/ExecutionContext.h"
     37 #include "core/events/EventTarget.h"
     38 #include "core/xml/XMLHttpRequest.h"
     39 #include "core/xml/XMLHttpRequestUpload.h"
     40 #include "wtf/text/AtomicStringHash.h"
     41 #include "wtf/text/StringBuilder.h"
     42 #include <v8.h>
     43 
     44 namespace {
     45 
     46 static const char setTimeoutName[] = "setTimeout";
     47 static const char setIntervalName[] = "setInterval";
     48 static const char requestAnimationFrameName[] = "requestAnimationFrame";
     49 static const char xhrSendName[] = "XMLHttpRequest.send";
     50 static const char enqueueMutationRecordName[] = "Mutation";
     51 
     52 }
     53 
     54 namespace WebCore {
     55 
     56 class AsyncCallStackTracker::ExecutionContextData FINAL : public ContextLifecycleObserver {
     57     WTF_MAKE_FAST_ALLOCATED;
     58 public:
     59     ExecutionContextData(AsyncCallStackTracker* tracker, ExecutionContext* executionContext)
     60         : ContextLifecycleObserver(executionContext)
     61         , m_tracker(tracker)
     62     {
     63     }
     64 
     65     virtual void contextDestroyed() OVERRIDE
     66     {
     67         ASSERT(executionContext());
     68         ExecutionContextData* self = m_tracker->m_executionContextDataMap.take(executionContext());
     69         ASSERT(self == this);
     70         ContextLifecycleObserver::contextDestroyed();
     71         delete self;
     72     }
     73 
     74 public:
     75     AsyncCallStackTracker* m_tracker;
     76     HashSet<int> m_intervalTimerIds;
     77     HashMap<int, RefPtr<AsyncCallChain> > m_timerCallChains;
     78     HashMap<int, RefPtr<AsyncCallChain> > m_animationFrameCallChains;
     79     HashMap<Event*, RefPtr<AsyncCallChain> > m_eventCallChains;
     80     HashMap<EventTarget*, RefPtr<AsyncCallChain> > m_xhrCallChains;
     81     HashMap<MutationObserver*, RefPtr<AsyncCallChain> > m_mutationObserverCallChains;
     82 };
     83 
     84 static XMLHttpRequest* toXmlHttpRequest(EventTarget* eventTarget)
     85 {
     86     const AtomicString& interfaceName = eventTarget->interfaceName();
     87     if (interfaceName == EventTargetNames::XMLHttpRequest)
     88         return static_cast<XMLHttpRequest*>(eventTarget);
     89     if (interfaceName == EventTargetNames::XMLHttpRequestUpload)
     90         return static_cast<XMLHttpRequestUpload*>(eventTarget)->xmlHttpRequest();
     91     return 0;
     92 }
     93 
     94 AsyncCallStackTracker::AsyncCallStack::AsyncCallStack(const String& description, const ScriptValue& callFrames)
     95     : m_description(description)
     96     , m_callFrames(callFrames)
     97 {
     98 }
     99 
    100 AsyncCallStackTracker::AsyncCallStack::~AsyncCallStack()
    101 {
    102 }
    103 
    104 AsyncCallStackTracker::AsyncCallStackTracker()
    105     : m_maxAsyncCallStackDepth(0)
    106 {
    107 }
    108 
    109 void AsyncCallStackTracker::setAsyncCallStackDepth(int depth)
    110 {
    111     if (depth <= 0) {
    112         m_maxAsyncCallStackDepth = 0;
    113         clear();
    114     } else {
    115         m_maxAsyncCallStackDepth = depth;
    116     }
    117 }
    118 
    119 const AsyncCallStackTracker::AsyncCallChain* AsyncCallStackTracker::currentAsyncCallChain() const
    120 {
    121     if (m_currentAsyncCallChain)
    122         ensureMaxAsyncCallChainDepth(m_currentAsyncCallChain.get(), m_maxAsyncCallStackDepth);
    123     return m_currentAsyncCallChain.get();
    124 }
    125 
    126 void AsyncCallStackTracker::didInstallTimer(ExecutionContext* context, int timerId, bool singleShot, const ScriptValue& callFrames)
    127 {
    128     ASSERT(context);
    129     ASSERT(isEnabled());
    130     if (!validateCallFrames(callFrames))
    131         return;
    132     ASSERT(timerId > 0);
    133     ExecutionContextData* data = createContextDataIfNeeded(context);
    134     data->m_timerCallChains.set(timerId, createAsyncCallChain(singleShot ? setTimeoutName : setIntervalName, callFrames));
    135     if (!singleShot)
    136         data->m_intervalTimerIds.add(timerId);
    137 }
    138 
    139 void AsyncCallStackTracker::didRemoveTimer(ExecutionContext* context, int timerId)
    140 {
    141     ASSERT(context);
    142     ASSERT(isEnabled());
    143     if (timerId <= 0)
    144         return;
    145     ExecutionContextData* data = m_executionContextDataMap.get(context);
    146     if (!data)
    147         return;
    148     data->m_intervalTimerIds.remove(timerId);
    149     data->m_timerCallChains.remove(timerId);
    150 }
    151 
    152 void AsyncCallStackTracker::willFireTimer(ExecutionContext* context, int timerId)
    153 {
    154     ASSERT(context);
    155     ASSERT(isEnabled());
    156     ASSERT(timerId > 0);
    157     ASSERT(!m_currentAsyncCallChain);
    158     if (ExecutionContextData* data = m_executionContextDataMap.get(context)) {
    159         if (data->m_intervalTimerIds.contains(timerId))
    160             setCurrentAsyncCallChain(data->m_timerCallChains.get(timerId));
    161         else
    162             setCurrentAsyncCallChain(data->m_timerCallChains.take(timerId));
    163     } else {
    164         setCurrentAsyncCallChain(nullptr);
    165     }
    166 }
    167 
    168 void AsyncCallStackTracker::didRequestAnimationFrame(ExecutionContext* context, int callbackId, const ScriptValue& callFrames)
    169 {
    170     ASSERT(context);
    171     ASSERT(isEnabled());
    172     if (!validateCallFrames(callFrames))
    173         return;
    174     ASSERT(callbackId > 0);
    175     ExecutionContextData* data = createContextDataIfNeeded(context);
    176     data->m_animationFrameCallChains.set(callbackId, createAsyncCallChain(requestAnimationFrameName, callFrames));
    177 }
    178 
    179 void AsyncCallStackTracker::didCancelAnimationFrame(ExecutionContext* context, int callbackId)
    180 {
    181     ASSERT(context);
    182     ASSERT(isEnabled());
    183     if (callbackId <= 0)
    184         return;
    185     if (ExecutionContextData* data = m_executionContextDataMap.get(context))
    186         data->m_animationFrameCallChains.remove(callbackId);
    187 }
    188 
    189 void AsyncCallStackTracker::willFireAnimationFrame(ExecutionContext* context, int callbackId)
    190 {
    191     ASSERT(context);
    192     ASSERT(isEnabled());
    193     ASSERT(callbackId > 0);
    194     ASSERT(!m_currentAsyncCallChain);
    195     if (ExecutionContextData* data = m_executionContextDataMap.get(context))
    196         setCurrentAsyncCallChain(data->m_animationFrameCallChains.take(callbackId));
    197     else
    198         setCurrentAsyncCallChain(nullptr);
    199 }
    200 
    201 void AsyncCallStackTracker::didEnqueueEvent(EventTarget* eventTarget, Event* event, const ScriptValue& callFrames)
    202 {
    203     ASSERT(eventTarget->executionContext());
    204     ASSERT(isEnabled());
    205     if (!validateCallFrames(callFrames))
    206         return;
    207     ExecutionContextData* data = createContextDataIfNeeded(eventTarget->executionContext());
    208     data->m_eventCallChains.set(event, createAsyncCallChain(event->type(), callFrames));
    209 }
    210 
    211 void AsyncCallStackTracker::didRemoveEvent(EventTarget* eventTarget, Event* event)
    212 {
    213     ASSERT(eventTarget->executionContext());
    214     ASSERT(isEnabled());
    215     if (ExecutionContextData* data = m_executionContextDataMap.get(eventTarget->executionContext()))
    216         data->m_eventCallChains.remove(event);
    217 }
    218 
    219 void AsyncCallStackTracker::willHandleEvent(EventTarget* eventTarget, Event* event, EventListener* listener, bool useCapture)
    220 {
    221     ASSERT(eventTarget->executionContext());
    222     ASSERT(isEnabled());
    223     if (XMLHttpRequest* xhr = toXmlHttpRequest(eventTarget)) {
    224         willHandleXHREvent(xhr, eventTarget, event);
    225     } else {
    226         if (ExecutionContextData* data = m_executionContextDataMap.get(eventTarget->executionContext()))
    227             setCurrentAsyncCallChain(data->m_eventCallChains.get(event));
    228         else
    229             setCurrentAsyncCallChain(nullptr);
    230     }
    231 }
    232 
    233 void AsyncCallStackTracker::willLoadXHR(XMLHttpRequest* xhr, const ScriptValue& callFrames)
    234 {
    235     ASSERT(xhr->executionContext());
    236     ASSERT(isEnabled());
    237     if (!validateCallFrames(callFrames))
    238         return;
    239     ExecutionContextData* data = createContextDataIfNeeded(xhr->executionContext());
    240     data->m_xhrCallChains.set(xhr, createAsyncCallChain(xhrSendName, callFrames));
    241 }
    242 
    243 void AsyncCallStackTracker::willHandleXHREvent(XMLHttpRequest* xhr, EventTarget* eventTarget, Event* event)
    244 {
    245     ASSERT(xhr->executionContext());
    246     ASSERT(isEnabled());
    247     if (ExecutionContextData* data = m_executionContextDataMap.get(xhr->executionContext())) {
    248         bool isXHRDownload = (xhr == eventTarget);
    249         if (isXHRDownload && event->type() == EventTypeNames::loadend)
    250             setCurrentAsyncCallChain(data->m_xhrCallChains.take(xhr));
    251         else
    252             setCurrentAsyncCallChain(data->m_xhrCallChains.get(xhr));
    253     } else {
    254         setCurrentAsyncCallChain(nullptr);
    255     }
    256 }
    257 
    258 void AsyncCallStackTracker::didEnqueueMutationRecord(ExecutionContext* context, MutationObserver* observer, const ScriptValue& callFrames)
    259 {
    260     ASSERT(context);
    261     ASSERT(isEnabled());
    262     if (!validateCallFrames(callFrames))
    263         return;
    264     ExecutionContextData* data = createContextDataIfNeeded(context);
    265     data->m_mutationObserverCallChains.set(observer, createAsyncCallChain(enqueueMutationRecordName, callFrames));
    266 }
    267 
    268 bool AsyncCallStackTracker::hasEnqueuedMutationRecord(ExecutionContext* context, MutationObserver* observer)
    269 {
    270     ASSERT(context);
    271     ASSERT(isEnabled());
    272     if (ExecutionContextData* data = m_executionContextDataMap.get(context))
    273         return data->m_mutationObserverCallChains.contains(observer);
    274     return false;
    275 }
    276 
    277 void AsyncCallStackTracker::didClearAllMutationRecords(ExecutionContext* context, MutationObserver* observer)
    278 {
    279     ASSERT(context);
    280     ASSERT(isEnabled());
    281     if (ExecutionContextData* data = m_executionContextDataMap.get(context))
    282         data->m_mutationObserverCallChains.remove(observer);
    283 }
    284 
    285 void AsyncCallStackTracker::willDeliverMutationRecords(ExecutionContext* context, MutationObserver* observer)
    286 {
    287     ASSERT(context);
    288     ASSERT(isEnabled());
    289     if (ExecutionContextData* data = m_executionContextDataMap.get(context))
    290         setCurrentAsyncCallChain(data->m_mutationObserverCallChains.take(observer));
    291     else
    292         setCurrentAsyncCallChain(nullptr);
    293 }
    294 
    295 void AsyncCallStackTracker::didFireAsyncCall()
    296 {
    297     clearCurrentAsyncCallChain();
    298 }
    299 
    300 PassRefPtr<AsyncCallStackTracker::AsyncCallChain> AsyncCallStackTracker::createAsyncCallChain(const String& description, const ScriptValue& callFrames)
    301 {
    302     RefPtr<AsyncCallChain> chain = adoptRef(m_currentAsyncCallChain ? new AsyncCallStackTracker::AsyncCallChain(*m_currentAsyncCallChain) : new AsyncCallStackTracker::AsyncCallChain());
    303     ensureMaxAsyncCallChainDepth(chain.get(), m_maxAsyncCallStackDepth - 1);
    304     chain->m_callStacks.prepend(adoptRef(new AsyncCallStackTracker::AsyncCallStack(description, callFrames)));
    305     return chain.release();
    306 }
    307 
    308 void AsyncCallStackTracker::setCurrentAsyncCallChain(PassRefPtr<AsyncCallChain> chain)
    309 {
    310     if (V8RecursionScope::recursionLevel(v8::Isolate::GetCurrent())) {
    311         if (m_currentAsyncCallChain)
    312             ++m_nestedAsyncCallCount;
    313     } else {
    314         // Current AsyncCallChain corresponds to the bottommost JS call frame.
    315         m_currentAsyncCallChain = chain;
    316         m_nestedAsyncCallCount = m_currentAsyncCallChain ? 1 : 0;
    317     }
    318 }
    319 
    320 void AsyncCallStackTracker::clearCurrentAsyncCallChain()
    321 {
    322     if (!m_nestedAsyncCallCount)
    323         return;
    324     --m_nestedAsyncCallCount;
    325     if (!m_nestedAsyncCallCount)
    326         m_currentAsyncCallChain.clear();
    327 }
    328 
    329 void AsyncCallStackTracker::ensureMaxAsyncCallChainDepth(AsyncCallChain* chain, unsigned maxDepth)
    330 {
    331     while (chain->m_callStacks.size() > maxDepth)
    332         chain->m_callStacks.removeLast();
    333 }
    334 
    335 bool AsyncCallStackTracker::validateCallFrames(const ScriptValue& callFrames)
    336 {
    337     return !callFrames.isEmpty();
    338 }
    339 
    340 AsyncCallStackTracker::ExecutionContextData* AsyncCallStackTracker::createContextDataIfNeeded(ExecutionContext* context)
    341 {
    342     ExecutionContextData* data = m_executionContextDataMap.get(context);
    343     if (!data) {
    344         data = new AsyncCallStackTracker::ExecutionContextData(this, context);
    345         m_executionContextDataMap.set(context, data);
    346     }
    347     return data;
    348 }
    349 
    350 void AsyncCallStackTracker::clear()
    351 {
    352     m_currentAsyncCallChain.clear();
    353     m_nestedAsyncCallCount = 0;
    354     ExecutionContextDataMap copy;
    355     m_executionContextDataMap.swap(copy);
    356     for (ExecutionContextDataMap::const_iterator it = copy.begin(); it != copy.end(); ++it)
    357         delete it->value;
    358 }
    359 
    360 } // namespace WebCore
    361