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