Web Inspector: capture an async stack trace when web content calls addEventListener
authormattbaker@apple.com <mattbaker@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 29 Jul 2017 00:33:12 +0000 (00:33 +0000)
committermattbaker@apple.com <mattbaker@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 29 Jul 2017 00:33:12 +0000 (00:33 +0000)
https://bugs.webkit.org/show_bug.cgi?id=174739
<rdar://problem/33468197>

Reviewed by Brian Burg.

Source/JavaScriptCore:

Allow debugger agents to perform custom logic when asynchronous stack
trace data is cleared. For example, the PageDebuggerAgent would clear
its list of registered listeners for which call stacks have been recorded.

* inspector/agents/InspectorDebuggerAgent.cpp:
(Inspector::InspectorDebuggerAgent::clearAsyncStackTraceData):
* inspector/agents/InspectorDebuggerAgent.h:

Source/WebCore:

Test: inspector/debugger/async-stack-trace.html

Add instrumentation to EventTarget to support showing asynchronous
stack traces when the debugger breaks in a script event listener.

* dom/EventTarget.cpp:
(WebCore::EventTarget::addEventListener):
(WebCore::EventTarget::removeEventListener):
(WebCore::EventTarget::fireEventListeners):
* inspector/InspectorInstrumentation.cpp:
(WebCore::InspectorInstrumentation::didAddEventListenerImpl):
(WebCore::InspectorInstrumentation::willRemoveEventListenerImpl):
(WebCore::InspectorInstrumentation::willHandleEventImpl):
(WebCore::InspectorInstrumentation::didHandleEventImpl):
* inspector/InspectorInstrumentation.h:
(WebCore::InspectorInstrumentation::didAddEventListener):
(WebCore::InspectorInstrumentation::willRemoveEventListener):
(WebCore::InspectorInstrumentation::willHandleEvent):
(WebCore::InspectorInstrumentation::didHandleEvent):
* inspector/PageDebuggerAgent.cpp:
(WebCore::PageDebuggerAgent::didClearAsyncStackTraceData):
(WebCore::PageDebuggerAgent::didAddEventListener):
(WebCore::PageDebuggerAgent::willRemoveEventListener):
(WebCore::PageDebuggerAgent::willHandleEvent):
* inspector/PageDebuggerAgent.h:

LayoutTests:

Add test case checking that async stack traces are recorded for addEventListener.

* inspector/debugger/async-stack-trace-expected.txt:
* inspector/debugger/async-stack-trace.html:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@220036 268f45cc-cd09-0410-ab3c-d52691b4dbfc

12 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/debugger/async-stack-trace-expected.txt
LayoutTests/inspector/debugger/async-stack-trace.html
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp
Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.h
Source/WebCore/ChangeLog
Source/WebCore/dom/EventTarget.cpp
Source/WebCore/inspector/InspectorInstrumentation.cpp
Source/WebCore/inspector/InspectorInstrumentation.h
Source/WebCore/inspector/PageDebuggerAgent.cpp
Source/WebCore/inspector/PageDebuggerAgent.h

index 3873969..dbf4750 100644 (file)
@@ -1,3 +1,16 @@
+2017-07-28  Matt Baker  <mattbaker@apple.com>
+
+        Web Inspector: capture an async stack trace when web content calls addEventListener
+        https://bugs.webkit.org/show_bug.cgi?id=174739
+        <rdar://problem/33468197>
+
+        Reviewed by Brian Burg.
+
+        Add test case checking that async stack traces are recorded for addEventListener.
+
+        * inspector/debugger/async-stack-trace-expected.txt:
+        * inspector/debugger/async-stack-trace.html:
+
 2017-07-28  Matt Rajca  <mrajca@apple.com>
 
         Don't add autoplay restrictions to media elements created in response to user gestures.
index fe3ca56..6d42ded 100644 (file)
@@ -64,6 +64,18 @@ ASYNC CALL STACK:
 3: --- setInterval ---
 4: [F] testReferenceCounting
 5: [P] Global Code
+
+-- Running test case: CheckAsyncStackTrace.AddEventListener
+PAUSE #1
+CALL STACK:
+0: [F] pauseThenFinishTest
+1: [F] clickFired
+2: [F] testAddEventListener
+3: [P] Global Code
+ASYNC CALL STACK:
+4: --- addEventListener ---
+5: [F] testAddEventListener
+6: [P] Global Code
 -- Running test setup.
 Save DebuggerManager.asyncStackTraceDepth
 
index d1161b1..b0a0460 100644 (file)
@@ -41,6 +41,15 @@ function testReferenceCounting() {
     }, timerDelay);
 }
 
+function testAddEventListener() {
+    document.body.addEventListener("click", function clickFired() {
+        document.body.removeEventListener("click", clickFired);
+        pauseThenFinishTest();
+    });
+
+    document.body.click();
+}
+
 function recursiveCallThenTest(testFunction, depth) {
     if (depth) {
         recursiveCallThenTest(testFunction, depth - 1);
@@ -93,6 +102,7 @@ function test()
     addSimpleTestCase("SetInterval", "testSetInterval(3)");
     addSimpleTestCase("ChainedRequestAnimationFrame", "testChainedRequestAnimationFrame()");
     addSimpleTestCase("ReferenceCounting", "testReferenceCounting()");
+    addSimpleTestCase("AddEventListener", "testAddEventListener()");
 
     function setup(resolve) {
         InspectorTest.log("Save DebuggerManager.asyncStackTraceDepth");
index 35c7ee2..f58d1ae 100644 (file)
@@ -1,3 +1,19 @@
+2017-07-28  Matt Baker  <mattbaker@apple.com>
+
+        Web Inspector: capture an async stack trace when web content calls addEventListener
+        https://bugs.webkit.org/show_bug.cgi?id=174739
+        <rdar://problem/33468197>
+
+        Reviewed by Brian Burg.
+
+        Allow debugger agents to perform custom logic when asynchronous stack
+        trace data is cleared. For example, the PageDebuggerAgent would clear
+        its list of registered listeners for which call stacks have been recorded.
+
+        * inspector/agents/InspectorDebuggerAgent.cpp:
+        (Inspector::InspectorDebuggerAgent::clearAsyncStackTraceData):
+        * inspector/agents/InspectorDebuggerAgent.h:
+
 2017-07-28  Mark Lam  <mark.lam@apple.com>
 
         ObjectToStringAdaptiveStructureWatchpoint should not fire if it's dying imminently.
index 80eb4f3..24e9039 100644 (file)
@@ -1145,6 +1145,8 @@ void InspectorDebuggerAgent::clearAsyncStackTraceData()
 {
     m_pendingAsyncCalls.clear();
     m_currentAsyncCallIdentifier = std::nullopt;
+
+    didClearAsyncStackTraceData();
 }
 
 } // namespace Inspector
index 51eb38e..0b073f5 100644 (file)
@@ -93,6 +93,7 @@ public:
 
     enum class AsyncCallType {
         DOMTimer,
+        EventListener,
         RequestAnimationFrame,
     };
 
@@ -135,6 +136,7 @@ protected:
     virtual String sourceMapURLForScript(const Script&);
 
     void didClearGlobalObject();
+    virtual void didClearAsyncStackTraceData() { }
 
 private:
     Ref<Inspector::Protocol::Array<Inspector::Protocol::Debugger::CallFrame>> currentCallFrames(const InjectedScript&);
index 7fcd169..95ec4b7 100644 (file)
@@ -1,3 +1,37 @@
+2017-07-28  Matt Baker  <mattbaker@apple.com>
+
+        Web Inspector: capture an async stack trace when web content calls addEventListener
+        https://bugs.webkit.org/show_bug.cgi?id=174739
+        <rdar://problem/33468197>
+
+        Reviewed by Brian Burg.
+
+        Test: inspector/debugger/async-stack-trace.html
+
+        Add instrumentation to EventTarget to support showing asynchronous
+        stack traces when the debugger breaks in a script event listener.
+
+        * dom/EventTarget.cpp:
+        (WebCore::EventTarget::addEventListener):
+        (WebCore::EventTarget::removeEventListener):
+        (WebCore::EventTarget::fireEventListeners):
+        * inspector/InspectorInstrumentation.cpp:
+        (WebCore::InspectorInstrumentation::didAddEventListenerImpl):
+        (WebCore::InspectorInstrumentation::willRemoveEventListenerImpl):
+        (WebCore::InspectorInstrumentation::willHandleEventImpl):
+        (WebCore::InspectorInstrumentation::didHandleEventImpl):
+        * inspector/InspectorInstrumentation.h:
+        (WebCore::InspectorInstrumentation::didAddEventListener):
+        (WebCore::InspectorInstrumentation::willRemoveEventListener):
+        (WebCore::InspectorInstrumentation::willHandleEvent):
+        (WebCore::InspectorInstrumentation::didHandleEvent):
+        * inspector/PageDebuggerAgent.cpp:
+        (WebCore::PageDebuggerAgent::didClearAsyncStackTraceData):
+        (WebCore::PageDebuggerAgent::didAddEventListener):
+        (WebCore::PageDebuggerAgent::willRemoveEventListener):
+        (WebCore::PageDebuggerAgent::willHandleEvent):
+        * inspector/PageDebuggerAgent.h:
+
 2017-07-28  Matt Rajca  <mrajca@apple.com>
 
         Don't add autoplay restrictions to media elements created in response to user gestures.
index 04cb3bf..6a52374 100644 (file)
@@ -68,7 +68,15 @@ bool EventTarget::isMessagePort() const
 
 bool EventTarget::addEventListener(const AtomicString& eventType, Ref<EventListener>&& listener, const AddEventListenerOptions& options)
 {
-    return ensureEventTargetData().eventListenerMap.add(eventType, WTFMove(listener), { options.capture, options.passive, options.once });
+    bool listenerCreatedFromScript = listener->type() == EventListener::JSEventListenerType && !listener->wasCreatedFromMarkup();
+
+    if (!ensureEventTargetData().eventListenerMap.add(eventType, WTFMove(listener), { options.capture, options.passive, options.once }))
+        return false;
+
+    if (listenerCreatedFromScript)
+        InspectorInstrumentation::didAddEventListener(*this, eventType);
+
+    return true;
 }
 
 void EventTarget::addEventListenerForBindings(const AtomicString& eventType, RefPtr<EventListener>&& listener, AddEventListenerOptionsOrBoolean&& variant)
@@ -102,7 +110,12 @@ void EventTarget::removeEventListenerForBindings(const AtomicString& eventType,
 bool EventTarget::removeEventListener(const AtomicString& eventType, EventListener& listener, const ListenerOptions& options)
 {
     auto* data = eventTargetData();
-    return data && data->eventListenerMap.remove(eventType, listener, options.capture);
+    if (!data)
+        return false;
+
+    InspectorInstrumentation::willRemoveEventListener(*this, eventType, listener, options.capture);
+
+    return data->eventListenerMap.remove(eventType, listener, options.capture);
 }
 
 bool EventTarget::setAttributeEventListener(const AtomicString& eventType, RefPtr<EventListener>&& listener, DOMWrapperWorld& isolatedWorld)
@@ -260,8 +273,9 @@ void EventTarget::fireEventListeners(Event& event, EventListenerVector listeners
         if (registeredListener->isPassive())
             event.setInPassiveListener(true);
 
-        InspectorInstrumentation::willHandleEvent(&context, event);
+        InspectorInstrumentation::willHandleEvent(context, event, *registeredListener);
         registeredListener->callback().handleEvent(context, event);
+        InspectorInstrumentation::didHandleEvent(context);
 
         if (registeredListener->isPassive())
             event.setInPassiveListener(false);
index eeef843..c7492c9 100644 (file)
@@ -61,6 +61,7 @@
 #include "RenderObject.h"
 #include "RenderView.h"
 #include "ScriptController.h"
+#include "ScriptExecutionContext.h"
 #include "WebConsoleAgent.h"
 #include "WebGLRenderingContextBase.h"
 #include "WebSocketFrame.h"
@@ -350,6 +351,18 @@ void InspectorInstrumentation::didRemoveTimerImpl(InstrumentingAgents& instrumen
         timelineAgent->didRemoveTimer(timerId, frameForScriptExecutionContext(context));
 }
 
+void InspectorInstrumentation::didAddEventListenerImpl(InstrumentingAgents& instrumentingAgents, EventTarget& target, const AtomicString& eventType)
+{
+    if (PageDebuggerAgent* pageDebuggerAgent = instrumentingAgents.pageDebuggerAgent())
+        pageDebuggerAgent->didAddEventListener(target, eventType);
+}
+
+void InspectorInstrumentation::willRemoveEventListenerImpl(InstrumentingAgents& instrumentingAgents, EventTarget& target, const AtomicString& eventType, EventListener& listener, bool capture)
+{
+    if (PageDebuggerAgent* pageDebuggerAgent = instrumentingAgents.pageDebuggerAgent())
+        pageDebuggerAgent->willRemoveEventListener(target, eventType, listener, capture);
+}
+
 InspectorInstrumentationCookie InspectorInstrumentation::willCallFunctionImpl(InstrumentingAgents& instrumentingAgents, const String& scriptName, int scriptLine, ScriptExecutionContext* context)
 {
     int timelineAgentId = 0;
@@ -378,11 +391,20 @@ InspectorInstrumentationCookie InspectorInstrumentation::willDispatchEventImpl(I
     return InspectorInstrumentationCookie(instrumentingAgents, timelineAgentId);
 }
 
-void InspectorInstrumentation::willHandleEventImpl(InstrumentingAgents& instrumentingAgents, const Event& event)
+void InspectorInstrumentation::willHandleEventImpl(InstrumentingAgents& instrumentingAgents, const Event& event, const RegisteredEventListener& listener)
 {
+    if (PageDebuggerAgent* pageDebuggerAgent = instrumentingAgents.pageDebuggerAgent())
+        pageDebuggerAgent->willHandleEvent(listener);
+
     pauseOnNativeEventIfNeeded(instrumentingAgents, true, event.type(), false);
 }
 
+void InspectorInstrumentation::didHandleEventImpl(InstrumentingAgents& instrumentingAgents)
+{
+    if (InspectorDebuggerAgent* debuggerAgent = instrumentingAgents.inspectorDebuggerAgent())
+        debuggerAgent->didDispatchAsyncCall();
+}
+
 void InspectorInstrumentation::didDispatchEventImpl(const InspectorInstrumentationCookie& cookie)
 {
     if (InspectorTimelineAgent* timelineAgent = retrieveTimelineAgent(cookie))
index 61f42c7..88dd931 100644 (file)
@@ -36,6 +36,7 @@
 #include "CanvasRenderingContext.h"
 #include "DocumentThreadableLoader.h"
 #include "Element.h"
+#include "EventTarget.h"
 #include "FormData.h"
 #include "Frame.h"
 #include "HTMLCanvasElement.h"
@@ -43,7 +44,6 @@
 #include "InspectorController.h"
 #include "InspectorInstrumentationCookie.h"
 #include "Page.h"
-#include "ScriptExecutionContext.h"
 #include "StorageArea.h"
 #include "WorkerGlobalScope.h"
 #include "WorkerInspectorController.h"
@@ -65,17 +65,20 @@ class DOMWrapperWorld;
 class Database;
 class Document;
 class DocumentLoader;
+class EventListener;
 class HTTPHeaderMap;
 class InspectorTimelineAgent;
 class InstrumentingAgents;
 class NetworkLoadMetrics;
 class Node;
 class PseudoElement;
+class RegisteredEventListener;
 class RenderLayer;
 class RenderObject;
 class ResourceLoader;
 class ResourceRequest;
 class ResourceResponse;
+class ScriptExecutionContext;
 class SecurityOrigin;
 class ShadowRoot;
 class URL;
@@ -129,9 +132,12 @@ public:
 
     static InspectorInstrumentationCookie willCallFunction(ScriptExecutionContext*, const String& scriptName, int scriptLine);
     static void didCallFunction(const InspectorInstrumentationCookie&, ScriptExecutionContext*);
+    static void didAddEventListener(EventTarget&, const AtomicString& eventType);
+    static void willRemoveEventListener(EventTarget&, const AtomicString& eventType, EventListener&, bool capture);
     static InspectorInstrumentationCookie willDispatchEvent(Document&, const Event&, bool hasEventListeners);
     static void didDispatchEvent(const InspectorInstrumentationCookie&);
-    static void willHandleEvent(ScriptExecutionContext*, const Event&);
+    static void willHandleEvent(ScriptExecutionContext&, const Event&, const RegisteredEventListener&);
+    static void didHandleEvent(ScriptExecutionContext&);
     static InspectorInstrumentationCookie willDispatchEventOnWindow(Frame*, const Event&, DOMWindow&);
     static void didDispatchEventOnWindow(const InspectorInstrumentationCookie&);
     static InspectorInstrumentationCookie willEvaluateScript(Frame&, const String& url, int lineNumber);
@@ -289,8 +295,11 @@ private:
 
     static InspectorInstrumentationCookie willCallFunctionImpl(InstrumentingAgents&, const String& scriptName, int scriptLine, ScriptExecutionContext*);
     static void didCallFunctionImpl(const InspectorInstrumentationCookie&, ScriptExecutionContext*);
+    static void didAddEventListenerImpl(InstrumentingAgents&, EventTarget&, const AtomicString& eventType);
+    static void willRemoveEventListenerImpl(InstrumentingAgents&, EventTarget&, const AtomicString& eventType, EventListener&, bool capture);
     static InspectorInstrumentationCookie willDispatchEventImpl(InstrumentingAgents&, Document&, const Event&, bool hasEventListeners);
-    static void willHandleEventImpl(InstrumentingAgents&, const Event&);
+    static void willHandleEventImpl(InstrumentingAgents&, const Event&, const RegisteredEventListener&);
+    static void didHandleEventImpl(InstrumentingAgents&);
     static void didDispatchEventImpl(const InspectorInstrumentationCookie&);
     static InspectorInstrumentationCookie willDispatchEventOnWindowImpl(InstrumentingAgents&, const Event&, DOMWindow&);
     static void didDispatchEventOnWindowImpl(const InspectorInstrumentationCookie&);
@@ -635,6 +644,20 @@ inline void InspectorInstrumentation::didRemoveTimer(ScriptExecutionContext& con
         didRemoveTimerImpl(*instrumentingAgents, timerId, context);
 }
 
+inline void InspectorInstrumentation::didAddEventListener(EventTarget& target, const AtomicString& eventType)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(void());
+    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForContext(target.scriptExecutionContext()))
+        didAddEventListenerImpl(*instrumentingAgents, target, eventType);
+}
+
+inline void InspectorInstrumentation::willRemoveEventListener(EventTarget& target, const AtomicString& eventType, EventListener& listener, bool capture)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(void());
+    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForContext(target.scriptExecutionContext()))
+        willRemoveEventListenerImpl(*instrumentingAgents, target, eventType, listener, capture);
+}
+
 inline InspectorInstrumentationCookie InspectorInstrumentation::willCallFunction(ScriptExecutionContext* context, const String& scriptName, int scriptLine)
 {
     FAST_RETURN_IF_NO_FRONTENDS(InspectorInstrumentationCookie());
@@ -665,11 +688,18 @@ inline void InspectorInstrumentation::didDispatchEvent(const InspectorInstrument
         didDispatchEventImpl(cookie);
 }
 
-inline void InspectorInstrumentation::willHandleEvent(ScriptExecutionContext* context, const Event& event)
+inline void InspectorInstrumentation::willHandleEvent(ScriptExecutionContext& context, const Event& event, const RegisteredEventListener& listener)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(void());
+    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForContext(context))
+        return willHandleEventImpl(*instrumentingAgents, event, listener);
+}
+
+inline void InspectorInstrumentation::didHandleEvent(ScriptExecutionContext& context)
 {
     FAST_RETURN_IF_NO_FRONTENDS(void());
     if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForContext(context))
-        return willHandleEventImpl(*instrumentingAgents, event);
+        return didHandleEventImpl(*instrumentingAgents);
 }
 
 inline InspectorInstrumentationCookie InspectorInstrumentation::willDispatchEventOnWindow(Frame* frame, const Event& event, DOMWindow& window)
index 36f06f5..6a06ba3 100644 (file)
@@ -34,6 +34,8 @@
 
 #include "CachedResource.h"
 #include "Document.h"
+#include "EventListener.h"
+#include "EventTarget.h"
 #include "InspectorOverlay.h"
 #include "InspectorPageAgent.h"
 #include "InstrumentingAgents.h"
@@ -41,6 +43,7 @@
 #include "Page.h"
 #include "PageConsoleClient.h"
 #include "PageScriptDebugServer.h"
+#include "ScriptExecutionContext.h"
 #include "ScriptState.h"
 #include <inspector/InjectedScript.h>
 #include <inspector/InjectedScriptManager.h>
@@ -93,6 +96,12 @@ String PageDebuggerAgent::sourceMapURLForScript(const Script& script)
     return InspectorDebuggerAgent::sourceMapURLForScript(script);
 }
 
+void PageDebuggerAgent::didClearAsyncStackTraceData()
+{
+    m_registeredEventListeners.clear();
+    m_nextEventListenerIdentifier = 1;
+}
+
 void PageDebuggerAgent::muteConsole()
 {
     PageConsoleClient::mute();
@@ -151,6 +160,51 @@ void PageDebuggerAgent::mainFrameNavigated()
     setSuppressAllPauses(false);
 }
 
+void PageDebuggerAgent::didAddEventListener(EventTarget& target, const AtomicString& eventType)
+{
+    if (!breakpointsActive())
+        return;
+
+    auto& eventListeners = target.eventListeners(eventType);
+    const RefPtr<RegisteredEventListener>& listener = eventListeners.last();
+    if (m_registeredEventListeners.contains(listener.get())) {
+        ASSERT_NOT_REACHED();
+        return;
+    }
+
+    JSC::ExecState* scriptState = target.scriptExecutionContext()->execState();
+    if (!scriptState)
+        return;
+
+    int identifier = m_nextEventListenerIdentifier++;
+    m_registeredEventListeners.set(listener.get(), identifier);
+
+    didScheduleAsyncCall(scriptState, InspectorDebuggerAgent::AsyncCallType::EventListener, identifier, listener->isOnce());
+}
+
+void PageDebuggerAgent::willRemoveEventListener(EventTarget& target, const AtomicString& eventType, EventListener& listener, bool capture)
+{
+    auto& eventListeners = target.eventListeners(eventType);
+    size_t listenerIndex = eventListeners.findMatching([&](auto& registeredListener) {
+        return &registeredListener->callback() == &listener && registeredListener->useCapture() == capture;
+    });
+
+    if (listenerIndex == notFound)
+        return;
+
+    int identifier = m_registeredEventListeners.take(eventListeners[listenerIndex].get());
+    didCancelAsyncCall(InspectorDebuggerAgent::AsyncCallType::EventListener, identifier);
+}
+
+void PageDebuggerAgent::willHandleEvent(const RegisteredEventListener& listener)
+{
+    auto it = m_registeredEventListeners.find(&listener);
+    if (it == m_registeredEventListeners.end())
+        return;
+
+    willDispatchAsyncCall(InspectorDebuggerAgent::AsyncCallType::EventListener, it->value);
+}
+
 void PageDebuggerAgent::didRequestAnimationFrame(int callbackId, Document& document)
 {
     if (!breakpointsActive())
index 1d7ec68..166f1dd 100644 (file)
 namespace WebCore {
 
 class Document;
+class EventListener;
+class EventTarget;
 class InspectorOverlay;
 class InspectorPageAgent;
 class Page;
+class RegisteredEventListener;
 
 class PageDebuggerAgent final : public WebDebuggerAgent {
     WTF_MAKE_NONCOPYABLE(PageDebuggerAgent);
@@ -57,12 +60,18 @@ public:
     void willFireAnimationFrame(int callbackId);
     void didCancelAnimationFrame(int callbackId);
 
+    void didAddEventListener(EventTarget&, const AtomicString& eventType);
+    void willRemoveEventListener(EventTarget&, const AtomicString& eventType, EventListener&, bool capture);
+    void willHandleEvent(const RegisteredEventListener&);
+
 protected:
     void enable() override;
     void disable(bool isBeingDestroyed) override;
 
     String sourceMapURLForScript(const Script&) override;
 
+    void didClearAsyncStackTraceData() override;
+
 private:
     void muteConsole() override;
     void unmuteConsole() override;
@@ -76,6 +85,9 @@ private:
 
     InspectorPageAgent* m_pageAgent;
     InspectorOverlay* m_overlay { nullptr };
+
+    HashMap<const RegisteredEventListener*, int> m_registeredEventListeners;
+    int m_nextEventListenerIdentifier { 1 };
 };
 
 } // namespace WebCore