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:
ExecutionContextData(AsyncCallStackTracker * tracker,ExecutionContext * executionContext)59 ExecutionContextData(AsyncCallStackTracker* tracker, ExecutionContext* executionContext)
60 : ContextLifecycleObserver(executionContext)
61 , m_tracker(tracker)
62 {
63 }
64
contextDestroyed()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
toXmlHttpRequest(EventTarget * eventTarget)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
AsyncCallStack(const String & description,const ScriptValue & callFrames)94 AsyncCallStackTracker::AsyncCallStack::AsyncCallStack(const String& description, const ScriptValue& callFrames)
95 : m_description(description)
96 , m_callFrames(callFrames)
97 {
98 }
99
~AsyncCallStack()100 AsyncCallStackTracker::AsyncCallStack::~AsyncCallStack()
101 {
102 }
103
AsyncCallStackTracker()104 AsyncCallStackTracker::AsyncCallStackTracker()
105 : m_maxAsyncCallStackDepth(0)
106 {
107 }
108
setAsyncCallStackDepth(int depth)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
currentAsyncCallChain() const119 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
didInstallTimer(ExecutionContext * context,int timerId,bool singleShot,const ScriptValue & callFrames)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
didRemoveTimer(ExecutionContext * context,int timerId)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
willFireTimer(ExecutionContext * context,int timerId)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
didRequestAnimationFrame(ExecutionContext * context,int callbackId,const ScriptValue & callFrames)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
didCancelAnimationFrame(ExecutionContext * context,int callbackId)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
willFireAnimationFrame(ExecutionContext * context,int callbackId)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
didEnqueueEvent(EventTarget * eventTarget,Event * event,const ScriptValue & callFrames)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
didRemoveEvent(EventTarget * eventTarget,Event * event)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
willHandleEvent(EventTarget * eventTarget,Event * event,EventListener * listener,bool useCapture)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
willLoadXHR(XMLHttpRequest * xhr,const ScriptValue & callFrames)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
willHandleXHREvent(XMLHttpRequest * xhr,EventTarget * eventTarget,Event * event)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
didEnqueueMutationRecord(ExecutionContext * context,MutationObserver * observer,const ScriptValue & callFrames)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
hasEnqueuedMutationRecord(ExecutionContext * context,MutationObserver * observer)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
didClearAllMutationRecords(ExecutionContext * context,MutationObserver * observer)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
willDeliverMutationRecords(ExecutionContext * context,MutationObserver * observer)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
didFireAsyncCall()295 void AsyncCallStackTracker::didFireAsyncCall()
296 {
297 clearCurrentAsyncCallChain();
298 }
299
createAsyncCallChain(const String & description,const ScriptValue & callFrames)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
setCurrentAsyncCallChain(PassRefPtr<AsyncCallChain> chain)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
clearCurrentAsyncCallChain()320 void AsyncCallStackTracker::clearCurrentAsyncCallChain()
321 {
322 if (!m_nestedAsyncCallCount)
323 return;
324 --m_nestedAsyncCallCount;
325 if (!m_nestedAsyncCallCount)
326 m_currentAsyncCallChain.clear();
327 }
328
ensureMaxAsyncCallChainDepth(AsyncCallChain * chain,unsigned maxDepth)329 void AsyncCallStackTracker::ensureMaxAsyncCallChainDepth(AsyncCallChain* chain, unsigned maxDepth)
330 {
331 while (chain->m_callStacks.size() > maxDepth)
332 chain->m_callStacks.removeLast();
333 }
334
validateCallFrames(const ScriptValue & callFrames)335 bool AsyncCallStackTracker::validateCallFrames(const ScriptValue& callFrames)
336 {
337 return !callFrames.isEmpty();
338 }
339
createContextDataIfNeeded(ExecutionContext * context)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
clear()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