WebKit Bugzilla
Attachment 347521 Details for
Bug 183138
: Web Inspector: allow breakpoints to be set for specific event listeners
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch
bug-183138-20180820115814.patch (text/plain), 55.67 KB, created by
Devin Rousso
on 2018-08-20 11:58:15 PDT
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Devin Rousso
Created:
2018-08-20 11:58:15 PDT
Size:
55.67 KB
patch
obsolete
>diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog >index a474cc17276ad560cf824150f0ef1a067665f15e..f0a90419639f2fc869b54481002c743598a0faf7 100644 >--- a/Source/JavaScriptCore/ChangeLog >+++ b/Source/JavaScriptCore/ChangeLog >@@ -1,3 +1,15 @@ >+2018-08-20 Devin Rousso <drousso@apple.com> >+ >+ Web Inspector: allow breakpoints to be set for specific event listeners >+ https://bugs.webkit.org/show_bug.cgi?id=183138 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * inspector/protocol/DOM.json: >+ Add `setBreakpointForEventListener` and `removeBreakpointForEventListener`, each of which >+ takes an `eventListenerId` and toggles whether that specific usage of that event listener >+ should have a breakpoint and pause before running. >+ > 2018-08-17 Saam barati <sbarati@apple.com> > > intersectionOfPastValuesAtHead must filter values after they've observed an invalidation point >diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog >index 2195422db9aba4b42b2a1f68c13807b5f8c21d2c..9af97fc216d386dc1958693b994d0bf03c7adbbb 100644 >--- a/Source/WebCore/ChangeLog >+++ b/Source/WebCore/ChangeLog >@@ -1,3 +1,46 @@ >+2018-08-20 Devin Rousso <drousso@apple.com> >+ >+ Web Inspector: allow breakpoints to be set for specific event listeners >+ https://bugs.webkit.org/show_bug.cgi?id=183138 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Test: inspector/dom/breakpoint-for-event-listener.html >+ >+ * inspector/agents/InspectorDOMAgent.h: >+ * inspector/agents/InspectorDOMAgent.cpp: >+ (WebCore::InspectorDOMAgent::getEventListenersForNode): >+ (WebCore::InspectorDOMAgent::setEventListenerDisabled): >+ (WebCore::InspectorDOMAgent::setBreakpointForEventListener): Added. >+ (WebCore::InspectorDOMAgent::removeBreakpointForEventListener): Added. >+ (WebCore::InspectorDOMAgent::buildObjectForEventListener): >+ (WebCore::InspectorDOMAgent::willRemoveEventListener): >+ (WebCore::InspectorDOMAgent::isEventListenerDisabled): >+ (WebCore::InspectorDOMAgent::hasBreakpointForEventListener): Added. >+ (WebCore::InspectorDOMAgent::idForEventListener): Added. >+ Rework the event listener data structure to be based on ID instead of `EventListener`, since >+ it is possible to have the same `EventListener` be used for multiple events. >+ >+ * inspector/agents/InspectorDOMDebuggerAgent.h: >+ * inspector/agents/InspectorDOMDebuggerAgent.cpp: >+ (WebCore::InspectorDOMDebuggerAgent::willHandleEvent): Added. >+ (WebCore::InspectorDOMDebuggerAgent::pauseOnNativeEventIfNeeded): >+ For DOM events, also check to see if there is a matching event listener specific breakpoint >+ set via the DOMAgent, and break on it if one is found. >+ >+ * inspector/InspectorInstrumentation.h: >+ * inspector/InspectorInstrumentation.cpp: >+ (WebCore::InspectorInstrumentation::didInstallTimerImpl): >+ (WebCore::InspectorInstrumentation::didRemoveTimerImpl): >+ (WebCore::InspectorInstrumentation::willHandleEventImpl): >+ (WebCore::InspectorInstrumentation::willFireTimerImpl): >+ (WebCore::InspectorInstrumentation::pauseOnNativeEventIfNeeded): >+ (WebCore::InspectorInstrumentation::didRequestAnimationFrameImpl): >+ (WebCore::InspectorInstrumentation::didCancelAnimationFrameImpl): >+ (WebCore::InspectorInstrumentation::willFireAnimationFrameImpl): >+ Split off `pauseOnNativeEventIfNeeded` to only handle non-DOM events, since all DOM events >+ would already only go through `willHandleEvent`. >+ > 2018-08-18 David Kilzer <ddkilzer@apple.com> > > Let Xcode have its way with the WebCore project >diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog >index 1d0a6261761057810a38ccd216ac3007a8d8ce7a..ae968007bd16bcd7b3c92b5f631ea57b1e0e753e 100644 >--- a/Source/WebInspectorUI/ChangeLog >+++ b/Source/WebInspectorUI/ChangeLog >@@ -1,3 +1,46 @@ >+2018-08-20 Devin Rousso <drousso@apple.com> >+ >+ Web Inspector: allow breakpoints to be set for specific event listeners >+ https://bugs.webkit.org/show_bug.cgi?id=183138 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * Localizations/en.lproj/localizedStrings.js: >+ >+ * UserInterface/Models/EventBreakpoint.js: >+ (WI.EventBreakpoint): >+ (WI.EventBreakpoint.fromPayload): Added. >+ (WI.EventBreakpoint.prototype.get eventListener): Added. >+ >+ * UserInterface/Controllers/DOMDebuggerManager.js: >+ (WI.DOMDebuggerManager): >+ >+ * UserInterface/Controllers/DOMTreeManager.js: >+ (WI.DOMTreeManager): >+ (WI.DOMTreeManager.prototype.get eventBreakpoints): Added. >+ (WI.DOMTreeManager.prototype._setDocument): >+ (WI.DOMTreeManager.prototype.setEventListenerDisabled): >+ (WI.DOMTreeManager.prototype.setBreakpointForEventListener): Added. >+ (WI.DOMTreeManager.prototype.removeBreakpointForEventListener): Added. >+ (WI.DOMTreeManager.prototype.breakpointForEventListenerId): Added. >+ >+ * UserInterface/Controllers/EventBreakpointTreeController.js: >+ (WI.EventBreakpointTreeController): >+ >+ * UserInterface/Views/DebuggerSidebarPanel.js: >+ (WI.DebuggerSidebarPanel.prototype._updatePauseReasonSection): >+ >+ * UserInterface/Views/EventListenerSectionGroup.js: >+ (WI.EventListenerSectionGroup): >+ (WI.EventListenerSectionGroup.prototype._createDisabledToggleRow): >+ (WI.EventListenerSectionGroup.prototype._createBreakpointToggleRow): Added. >+ >+ * UserInterface/Views/EventBreakpointTreeElement.js: >+ (WI.EventBreakpointTreeElement): >+ (WI.EventBreakpointTreeElement.prototype.ondelete): >+ (WI.EventBreakpointTreeElement.prototype.populateContextMenu): >+ (WI.EventBreakpointTreeElement.prototype._toggleBreakpoint): >+ > 2018-08-16 Devin Rousso <drousso@apple.com> > > Web Inspector: support breakpoints for arbitrary event names >diff --git a/Source/JavaScriptCore/inspector/protocol/DOM.json b/Source/JavaScriptCore/inspector/protocol/DOM.json >index bdd988d81325e5cc45b9554157f568498c3ae60e..afdf4dbfdade6debf970437cccb5b004191899c3 100644 >--- a/Source/JavaScriptCore/inspector/protocol/DOM.json >+++ b/Source/JavaScriptCore/inspector/protocol/DOM.json >@@ -90,7 +90,8 @@ > { "name": "handler", "$ref": "Runtime.RemoteObject", "optional": true, "description": "Event handler function value." }, > { "name": "passive", "type": "boolean", "optional": true, "description": "<code>EventListener</code>'s passive." }, > { "name": "once", "type": "boolean", "optional": true, "description": "<code>EventListener</code>'s once." }, >- { "name": "disabled", "type": "boolean", "optional": true } >+ { "name": "disabled", "type": "boolean", "optional": true }, >+ { "name": "hasBreakpoint", "type": "boolean", "optional": true } > ] > }, > { >@@ -273,6 +274,20 @@ > { "name": "disabled", "type": "boolean" } > ] > }, >+ { >+ "name": "setBreakpointForEventListener", >+ "description": "Set a breakpoint on the given event listener.", >+ "parameters": [ >+ { "name": "eventListenerId", "$ref": "EventListenerId" } >+ ] >+ }, >+ { >+ "name": "removeBreakpointForEventListener", >+ "description": "Remove any breakpoints on the given event listener.", >+ "parameters": [ >+ { "name": "eventListenerId", "$ref": "EventListenerId" } >+ ] >+ }, > { > "name": "getAccessibilityPropertiesForNode", > "description": "Returns a dictionary of accessibility properties for the node.", >diff --git a/Source/WebCore/inspector/InspectorInstrumentation.cpp b/Source/WebCore/inspector/InspectorInstrumentation.cpp >index 5d3488c9feadf70a00e800e0e394e49d2880a750..41b7877161fa1bd8da5851d74da73b0fbefa32bf 100644 >--- a/Source/WebCore/inspector/InspectorInstrumentation.cpp >+++ b/Source/WebCore/inspector/InspectorInstrumentation.cpp >@@ -294,7 +294,7 @@ void InspectorInstrumentation::willSendXMLHttpRequestImpl(InstrumentingAgents& i > > void InspectorInstrumentation::didInstallTimerImpl(InstrumentingAgents& instrumentingAgents, int timerId, Seconds timeout, bool singleShot, ScriptExecutionContext& context) > { >- pauseOnNativeEventIfNeeded(instrumentingAgents, false, setTimerEventName, true); >+ pauseOnNativeEventIfNeeded(instrumentingAgents, setTimerEventName, true); > > if (InspectorDebuggerAgent* debuggerAgent = instrumentingAgents.inspectorDebuggerAgent()) > debuggerAgent->didScheduleAsyncCall(context.execState(), InspectorDebuggerAgent::AsyncCallType::DOMTimer, timerId, singleShot); >@@ -305,7 +305,7 @@ void InspectorInstrumentation::didInstallTimerImpl(InstrumentingAgents& instrume > > void InspectorInstrumentation::didRemoveTimerImpl(InstrumentingAgents& instrumentingAgents, int timerId, ScriptExecutionContext& context) > { >- pauseOnNativeEventIfNeeded(instrumentingAgents, false, clearTimerEventName, true); >+ pauseOnNativeEventIfNeeded(instrumentingAgents, clearTimerEventName, true); > > if (InspectorDebuggerAgent* debuggerAgent = instrumentingAgents.inspectorDebuggerAgent()) > debuggerAgent->didCancelAsyncCall(InspectorDebuggerAgent::AsyncCallType::DOMTimer, timerId); >@@ -393,7 +393,8 @@ void InspectorInstrumentation::willHandleEventImpl(InstrumentingAgents& instrume > if (PageDebuggerAgent* pageDebuggerAgent = instrumentingAgents.pageDebuggerAgent()) > pageDebuggerAgent->willHandleEvent(listener); > >- pauseOnNativeEventIfNeeded(instrumentingAgents, true, event.type(), false); >+ if (InspectorDOMDebuggerAgent* domDebuggerAgent = instrumentingAgents.inspectorDOMDebuggerAgent()) >+ domDebuggerAgent->willHandleEvent(event, listener); > } > > void InspectorInstrumentation::didHandleEventImpl(InstrumentingAgents& instrumentingAgents) >@@ -444,7 +445,7 @@ void InspectorInstrumentation::didEvaluateScriptImpl(const InspectorInstrumentat > > InspectorInstrumentationCookie InspectorInstrumentation::willFireTimerImpl(InstrumentingAgents& instrumentingAgents, int timerId, ScriptExecutionContext& context) > { >- pauseOnNativeEventIfNeeded(instrumentingAgents, false, timerFiredEventName, false); >+ pauseOnNativeEventIfNeeded(instrumentingAgents, timerFiredEventName, false); > > if (InspectorDebuggerAgent* debuggerAgent = instrumentingAgents.inspectorDebuggerAgent()) > debuggerAgent->willDispatchAsyncCall(InspectorDebuggerAgent::AsyncCallType::DOMTimer, timerId); >@@ -1043,15 +1044,15 @@ bool InspectorInstrumentation::timelineAgentEnabled(ScriptExecutionContext* scri > return instrumentingAgents && instrumentingAgents->inspectorTimelineAgent(); > } > >-void InspectorInstrumentation::pauseOnNativeEventIfNeeded(InstrumentingAgents& instrumentingAgents, bool isDOMEvent, const String& eventName, bool synchronous) >+void InspectorInstrumentation::pauseOnNativeEventIfNeeded(InstrumentingAgents& instrumentingAgents, const String& eventName, bool synchronous) > { > if (InspectorDOMDebuggerAgent* domDebuggerAgent = instrumentingAgents.inspectorDOMDebuggerAgent()) >- domDebuggerAgent->pauseOnNativeEventIfNeeded(isDOMEvent, eventName, synchronous); >+ domDebuggerAgent->pauseOnNativeEventIfNeeded(eventName, synchronous); > } > > void InspectorInstrumentation::didRequestAnimationFrameImpl(InstrumentingAgents& instrumentingAgents, int callbackId, Document& document) > { >- pauseOnNativeEventIfNeeded(instrumentingAgents, false, requestAnimationFrameEventName, true); >+ pauseOnNativeEventIfNeeded(instrumentingAgents, requestAnimationFrameEventName, true); > > if (PageDebuggerAgent* pageDebuggerAgent = instrumentingAgents.pageDebuggerAgent()) > pageDebuggerAgent->didRequestAnimationFrame(callbackId, document); >@@ -1061,7 +1062,7 @@ void InspectorInstrumentation::didRequestAnimationFrameImpl(InstrumentingAgents& > > void InspectorInstrumentation::didCancelAnimationFrameImpl(InstrumentingAgents& instrumentingAgents, int callbackId, Document& document) > { >- pauseOnNativeEventIfNeeded(instrumentingAgents, false, cancelAnimationFrameEventName, true); >+ pauseOnNativeEventIfNeeded(instrumentingAgents, cancelAnimationFrameEventName, true); > > if (PageDebuggerAgent* pageDebuggerAgent = instrumentingAgents.pageDebuggerAgent()) > pageDebuggerAgent->didCancelAnimationFrame(callbackId); >@@ -1071,7 +1072,7 @@ void InspectorInstrumentation::didCancelAnimationFrameImpl(InstrumentingAgents& > > InspectorInstrumentationCookie InspectorInstrumentation::willFireAnimationFrameImpl(InstrumentingAgents& instrumentingAgents, int callbackId, Document& document) > { >- pauseOnNativeEventIfNeeded(instrumentingAgents, false, animationFrameFiredEventName, false); >+ pauseOnNativeEventIfNeeded(instrumentingAgents, animationFrameFiredEventName, false); > > if (PageDebuggerAgent* pageDebuggerAgent = instrumentingAgents.pageDebuggerAgent()) > pageDebuggerAgent->willFireAnimationFrame(callbackId); >diff --git a/Source/WebCore/inspector/InspectorInstrumentation.h b/Source/WebCore/inspector/InspectorInstrumentation.h >index fcce4a7384cc5697aa1e1f55d88d7763022a820f..622fc5602070a4481c72ab65ed5b6e6c71be0d46 100644 >--- a/Source/WebCore/inspector/InspectorInstrumentation.h >+++ b/Source/WebCore/inspector/InspectorInstrumentation.h >@@ -453,7 +453,7 @@ private: > > static InspectorTimelineAgent* retrieveTimelineAgent(const InspectorInstrumentationCookie&); > >- static void pauseOnNativeEventIfNeeded(InstrumentingAgents&, bool isDOMEvent, const String& eventName, bool synchronous); >+ static void pauseOnNativeEventIfNeeded(InstrumentingAgents&, const String& eventName, bool synchronous); > > WEBCORE_EXPORT static int s_frontendCounter; > }; >diff --git a/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp b/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp >index 9869b63871aa14d097a49ebf7e06c955715d39f1..cea47e2b6fb9433b2e5fecc4ae0127a8e76a6fe1 100644 >--- a/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp >+++ b/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp >@@ -841,20 +841,27 @@ void InspectorDOMAgent::getEventListenersForNode(ErrorString& errorString, int n > auto addListener = [&] (RegisteredEventListener& listener, const EventListenerInfo& info) { > int identifier = 0; > bool disabled = false; >+ bool hasBreakpoint = false; > >- auto it = m_eventListenerEntries.find(&listener.callback()); >- if (it == m_eventListenerEntries.end()) { >- InspectorEventListener inspectorEventListener(m_lastEventListenerId++, *info.node, info.eventType, listener.useCapture()); >- m_eventListenerEntries.add(&listener.callback(), inspectorEventListener); >+ for (auto& inspectorEventListener : m_eventListenerEntries.values()) { >+ if (inspectorEventListener.matches(*info.node, info.eventType, listener.callback(), listener.useCapture())) { >+ identifier = inspectorEventListener.identifier; >+ disabled = inspectorEventListener.disabled; >+ hasBreakpoint = inspectorEventListener.hasBreakpoint; >+ } >+ } >+ >+ if (!identifier) { >+ InspectorEventListener inspectorEventListener(m_lastEventListenerId++, *info.node, info.eventType, listener.callback(), listener.useCapture()); > > identifier = inspectorEventListener.identifier; > disabled = inspectorEventListener.disabled; >- } else { >- identifier = it->value.identifier; >- disabled = it->value.disabled; >+ hasBreakpoint = inspectorEventListener.hasBreakpoint; >+ >+ m_eventListenerEntries.add(identifier, inspectorEventListener); > } > >- listenersArray->addItem(buildObjectForEventListener(listener, identifier, info.eventType, info.node, objectGroup, disabled)); >+ listenersArray->addItem(buildObjectForEventListener(listener, identifier, info.eventType, info.node, objectGroup, disabled, hasBreakpoint)); > }; > > // Get Capturing Listeners (in this order) >@@ -910,14 +917,35 @@ void InspectorDOMAgent::getEventListeners(Node* node, Vector<EventListenerInfo>& > > void InspectorDOMAgent::setEventListenerDisabled(ErrorString& errorString, int eventListenerId, bool disabled) > { >- for (InspectorEventListener& inspectorEventListener : m_eventListenerEntries.values()) { >- if (inspectorEventListener.identifier == eventListenerId) { >- inspectorEventListener.disabled = disabled; >- return; >- } >+ auto it = m_eventListenerEntries.find(eventListenerId); >+ if (it == m_eventListenerEntries.end()) { >+ errorString = "No event listener for given identifier."_s; >+ return; > } > >- errorString = "No event listener for given identifier."_s; >+ it->value.disabled = disabled; >+} >+ >+void InspectorDOMAgent::setBreakpointForEventListener(ErrorString& errorString, int eventListenerId) >+{ >+ auto it = m_eventListenerEntries.find(eventListenerId); >+ if (it == m_eventListenerEntries.end()) { >+ errorString = "No event listener for given identifier."_s; >+ return; >+ } >+ >+ it->value.hasBreakpoint = true; >+} >+ >+void InspectorDOMAgent::removeBreakpointForEventListener(ErrorString& errorString, int eventListenerId) >+{ >+ auto it = m_eventListenerEntries.find(eventListenerId); >+ if (it == m_eventListenerEntries.end()) { >+ errorString = "No event listener for given identifier."_s; >+ return; >+ } >+ >+ it->value.hasBreakpoint = false; > } > > void InspectorDOMAgent::getAccessibilityPropertiesForNode(ErrorString& errorString, int nodeId, RefPtr<Inspector::Protocol::DOM::AccessibilityProperties>& axProperties) >@@ -1593,7 +1621,7 @@ RefPtr<JSON::ArrayOf<Inspector::Protocol::DOM::Node>> InspectorDOMAgent::buildAr > return WTFMove(pseudoElements); > } > >-Ref<Inspector::Protocol::DOM::EventListener> InspectorDOMAgent::buildObjectForEventListener(const RegisteredEventListener& registeredEventListener, int identifier, const AtomicString& eventType, Node* node, const String* objectGroupId, bool disabled) >+Ref<Inspector::Protocol::DOM::EventListener> InspectorDOMAgent::buildObjectForEventListener(const RegisteredEventListener& registeredEventListener, int identifier, const AtomicString& eventType, Node* node, const String* objectGroupId, bool disabled, bool hasBreakpoint) > { > Ref<EventListener> eventListener = registeredEventListener.callback(); > >@@ -1653,6 +1681,8 @@ Ref<Inspector::Protocol::DOM::EventListener> InspectorDOMAgent::buildObjectForEv > value->setOnce(true); > if (disabled) > value->setDisabled(disabled); >+ if (hasBreakpoint) >+ value->setHasBreakpoint(hasBreakpoint); > return value; > } > >@@ -2271,21 +2301,38 @@ void InspectorDOMAgent::willRemoveEventListener(EventTarget& target, const Atomi > if (!listenerExists) > return; > >- m_eventListenerEntries.remove(&listener); >+ m_eventListenerEntries.removeIf([&] (auto& entry) { >+ return entry.value.matches(target, eventType, listener, capture); >+ }); > > m_frontendDispatcher->willRemoveEventListener(nodeId); > } > > bool InspectorDOMAgent::isEventListenerDisabled(EventTarget& target, const AtomicString& eventType, EventListener& listener, bool capture) > { >- auto it = m_eventListenerEntries.find(&listener); >- if (it == m_eventListenerEntries.end()) >- return false; >+ for (auto& inspectorEventListener : m_eventListenerEntries.values()) { >+ if (inspectorEventListener.matches(target, eventType, listener, capture)) >+ return inspectorEventListener.disabled; >+ } >+ return false; >+} > >- if (!it->value.disabled) >- return false; >+bool InspectorDOMAgent::hasBreakpointForEventListener(EventTarget& target, const AtomicString& eventType, EventListener& listener, bool capture) >+{ >+ for (auto& inspectorEventListener : m_eventListenerEntries.values()) { >+ if (inspectorEventListener.matches(target, eventType, listener, capture)) >+ return inspectorEventListener.hasBreakpoint; >+ } >+ return false; >+} > >- return it->value.eventTarget.get() == &target && it->value.eventType == eventType && it->value.useCapture == capture; >+int InspectorDOMAgent::idForEventListener(EventTarget& target, const AtomicString& eventType, EventListener& listener, bool capture) >+{ >+ for (auto& inspectorEventListener : m_eventListenerEntries.values()) { >+ if (inspectorEventListener.matches(target, eventType, listener, capture)) >+ return inspectorEventListener.identifier; >+ } >+ return 0; > } > > Node* InspectorDOMAgent::nodeForPath(const String& path) >diff --git a/Source/WebCore/inspector/agents/InspectorDOMAgent.h b/Source/WebCore/inspector/agents/InspectorDOMAgent.h >index 89bd9d1832b59199c261b81c5c35f1a8228ba0b9..22a48573c48a2cd2d32f31898ca1595389257cc1 100644 >--- a/Source/WebCore/inspector/agents/InspectorDOMAgent.h >+++ b/Source/WebCore/inspector/agents/InspectorDOMAgent.h >@@ -125,6 +125,8 @@ public: > void setNodeValue(ErrorString&, int nodeId, const String& value) override; > void getEventListenersForNode(ErrorString&, int nodeId, const WTF::String* objectGroup, RefPtr<JSON::ArrayOf<Inspector::Protocol::DOM::EventListener>>& listenersArray) override; > void setEventListenerDisabled(ErrorString&, int eventListenerId, bool disabled) override; >+ void setBreakpointForEventListener(ErrorString&, int eventListenerId) override; >+ void removeBreakpointForEventListener(ErrorString&, int eventListenerId) override; > void getAccessibilityPropertiesForNode(ErrorString&, int nodeId, RefPtr<Inspector::Protocol::DOM::AccessibilityProperties>& axProperties) override; > void performSearch(ErrorString&, const String& whitespaceTrimmedQuery, const JSON::Array* nodeIds, String* searchId, int* resultCount) override; > void getSearchResults(ErrorString&, const String& searchId, int fromIndex, int toIndex, RefPtr<JSON::ArrayOf<int>>&) override; >@@ -213,6 +215,9 @@ public: > // Methods called from other agents. > InspectorPageAgent* pageAgent() { return m_pageAgent; } > >+ bool hasBreakpointForEventListener(EventTarget&, const AtomicString& eventType, EventListener&, bool capture); >+ int idForEventListener(EventTarget&, const AtomicString& eventType, EventListener&, bool capture); >+ > private: > void highlightMousedOverNode(); > void setSearchingForNode(ErrorString&, bool enabled, const JSON::Object* highlightConfig); >@@ -233,7 +238,7 @@ private: > Ref<JSON::ArrayOf<String>> buildArrayForElementAttributes(Element*); > Ref<JSON::ArrayOf<Inspector::Protocol::DOM::Node>> buildArrayForContainerChildren(Node* container, int depth, NodeToIdMap* nodesMap); > RefPtr<JSON::ArrayOf<Inspector::Protocol::DOM::Node>> buildArrayForPseudoElements(const Element&, NodeToIdMap* nodesMap); >- Ref<Inspector::Protocol::DOM::EventListener> buildObjectForEventListener(const RegisteredEventListener&, int identifier, const AtomicString& eventType, Node*, const String* objectGroupId, bool disabled = false); >+ Ref<Inspector::Protocol::DOM::EventListener> buildObjectForEventListener(const RegisteredEventListener&, int identifier, const AtomicString& eventType, Node*, const String* objectGroupId, bool disabled, bool hasBreakpoint); > RefPtr<Inspector::Protocol::DOM::AccessibilityProperties> buildObjectForAccessibilityProperties(Node*); > void processAccessibilityChildren(RefPtr<AccessibilityObject>&&, RefPtr<JSON::ArrayOf<int>>&&); > >@@ -278,22 +283,30 @@ private: > struct InspectorEventListener { > int identifier { 1 }; > RefPtr<EventTarget> eventTarget; >+ RefPtr<EventListener> eventListener; > AtomicString eventType; > bool useCapture { false }; > bool disabled { false }; >+ bool hasBreakpoint { false }; > > InspectorEventListener() { } > >- InspectorEventListener(int identifier, EventTarget& eventTarget, const AtomicString& eventType, bool useCapture) >+ InspectorEventListener(int identifier, EventTarget& target, const AtomicString& type, EventListener& listener, bool capture) > : identifier(identifier) >- , eventTarget(&eventTarget) >- , eventType(eventType) >- , useCapture(useCapture) >+ , eventTarget(&target) >+ , eventListener(&listener) >+ , eventType(type) >+ , useCapture(capture) >+ { >+ } >+ >+ bool matches(EventTarget& target, const AtomicString& type, EventListener& listener, bool capture) > { >+ return eventTarget.get() == &target && eventListener.get() == &listener && eventType == type && useCapture == capture; > } > }; > >- HashMap<EventListener*, InspectorEventListener> m_eventListenerEntries; >+ HashMap<int, InspectorEventListener> m_eventListenerEntries; > int m_lastEventListenerId { 1 }; > }; > >diff --git a/Source/WebCore/inspector/agents/InspectorDOMDebuggerAgent.cpp b/Source/WebCore/inspector/agents/InspectorDOMDebuggerAgent.cpp >index 36f8aecc43ecfbb4e99a3545d1296bb8bd039c96..3792cfdc711164aa41ca5ff4a3bc6f9f117a28f0 100644 >--- a/Source/WebCore/inspector/agents/InspectorDOMDebuggerAgent.cpp >+++ b/Source/WebCore/inspector/agents/InspectorDOMDebuggerAgent.cpp >@@ -32,10 +32,12 @@ > #include "config.h" > #include "InspectorDOMDebuggerAgent.h" > >+#include "Event.h" > #include "Frame.h" > #include "HTMLElement.h" > #include "InspectorDOMAgent.h" > #include "InstrumentingAgents.h" >+#include "RegisteredEventListener.h" > #include <JavaScriptCore/ContentSearchUtilities.h> > #include <JavaScriptCore/InspectorFrontendDispatchers.h> > #include <JavaScriptCore/RegularExpression.h> >@@ -362,11 +364,30 @@ void InspectorDOMDebuggerAgent::updateSubtreeBreakpoints(Node* node, uint32_t ro > updateSubtreeBreakpoints(child, newRootMask, set); > } > >-void InspectorDOMDebuggerAgent::pauseOnNativeEventIfNeeded(bool isDOMEvent, const String& eventName, bool synchronous) >+void InspectorDOMDebuggerAgent::willHandleEvent(const Event& event, const RegisteredEventListener& registeredEventListener) > { >- String fullEventName = (isDOMEvent ? listenerEventCategoryType : instrumentationEventCategoryType) + eventName; >+ bool shouldPause = m_debuggerAgent->pauseOnNextStatementEnabled() || m_eventListenerBreakpoints.contains(listenerEventCategoryType + event.type()); > >- bool shouldPause = m_debuggerAgent->pauseOnNextStatementEnabled() || m_eventListenerBreakpoints.contains(fullEventName); >+ if (!shouldPause && m_domAgent) >+ shouldPause = m_domAgent->hasBreakpointForEventListener(*event.currentTarget(), event.type(), registeredEventListener.callback(), registeredEventListener.useCapture()); >+ >+ if (!shouldPause) >+ return; >+ >+ Ref<JSON::Object> eventData = JSON::Object::create(); >+ eventData->setString("eventName"_s, event.type()); >+ if (m_domAgent) { >+ int eventListenerId = m_domAgent->idForEventListener(*event.currentTarget(), event.type(), registeredEventListener.callback(), registeredEventListener.useCapture()); >+ if (eventListenerId) >+ eventData->setInteger("eventListenerId"_s, eventListenerId); >+ } >+ >+ m_debuggerAgent->schedulePauseOnNextStatement(Inspector::DebuggerFrontendDispatcher::Reason::EventListener, WTFMove(eventData)); >+} >+ >+void InspectorDOMDebuggerAgent::pauseOnNativeEventIfNeeded(const String& eventName, bool synchronous) >+{ >+ bool shouldPause = m_debuggerAgent->pauseOnNextStatementEnabled() || m_eventListenerBreakpoints.contains(instrumentationEventCategoryType + eventName); > if (!shouldPause) > return; > >diff --git a/Source/WebCore/inspector/agents/InspectorDOMDebuggerAgent.h b/Source/WebCore/inspector/agents/InspectorDOMDebuggerAgent.h >index d5581a5192f450bd6555537c95d4faa1fda2d5b9..0b306313a697c66045fd2fe3d42d599881fbeae2 100644 >--- a/Source/WebCore/inspector/agents/InspectorDOMDebuggerAgent.h >+++ b/Source/WebCore/inspector/agents/InspectorDOMDebuggerAgent.h >@@ -41,9 +41,11 @@ > namespace WebCore { > > class Element; >+class Event; > class Frame; > class InspectorDOMAgent; > class Node; >+class RegisteredEventListener; > > typedef String ErrorString; > >@@ -72,8 +74,10 @@ public: > void didRemoveDOMNode(Node&); > void willModifyDOMAttr(Element&); > void willSendXMLHttpRequest(const String& url); >- void pauseOnNativeEventIfNeeded(bool isDOMEvent, const String& eventName, bool synchronous); > void frameDocumentUpdated(Frame&); >+ void willHandleEvent(const Event&, const RegisteredEventListener&); >+ void pauseOnNativeEventIfNeeded(const String& eventName, bool synchronous); >+ void mainFrameDOMContentLoaded(); > > void didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*) final; > void willDestroyFrontendAndBackend(Inspector::DisconnectReason) final; >diff --git a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js >index aa25795db1be469139c29092f92150281f717ee2..0deb8a113b46764afacf1b695ebe6ce73cd2b2ce 100644 >--- a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js >+++ b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js >@@ -146,10 +146,12 @@ localizedStrings["Boundary"] = "Boundary"; > localizedStrings["Box Model"] = "Box Model"; > localizedStrings["Box Shadow"] = "Box Shadow"; > localizedStrings["Break on events with name:"] = "Break on events with name:"; >+localizedStrings["Break on listeners for event with name:"] = "Break on listeners for event with name:"; > localizedStrings["Break on request with URL:"] = "Break on request with URL:"; > localizedStrings["Break onâ¦"] = "Break onâ¦"; > localizedStrings["Breakdown"] = "Breakdown"; > localizedStrings["Breakdown of each memory category at the end of the selected time range"] = "Breakdown of each memory category at the end of the selected time range"; >+localizedStrings["Breakpoint"] = "Breakpoint"; > localizedStrings["Breakpoints"] = "Breakpoints"; > localizedStrings["Breakpoints disabled"] = "Breakpoints disabled"; > localizedStrings["Bubbling"] = "Bubbling"; >diff --git a/Source/WebInspectorUI/UserInterface/Controllers/DOMDebuggerManager.js b/Source/WebInspectorUI/UserInterface/Controllers/DOMDebuggerManager.js >index 7371ac013163a697de0ef8ed73324091d99019ff..cbabcab72f7be001eb4c57d4d78f476e68f102da 100644 >--- a/Source/WebInspectorUI/UserInterface/Controllers/DOMDebuggerManager.js >+++ b/Source/WebInspectorUI/UserInterface/Controllers/DOMDebuggerManager.js >@@ -62,10 +62,8 @@ WI.DOMDebuggerManager = class DOMDebuggerManager extends WI.Object > this.addDOMBreakpoint(breakpoint); > } > >- for (let cookie of this._eventBreakpointSetting.value) { >- let breakpoint = new WI.EventBreakpoint(cookie.eventName, cookie.disabled); >- this.addEventBreakpoint(breakpoint); >- } >+ for (let payload of this._eventBreakpointSetting.value) >+ this.addEventBreakpoint(WI.EventBreakpoint.fromPayload(payload)); > > for (let cookie of this._xhrBreakpointsSetting.value) { > let breakpoint = new WI.XHRBreakpoint(cookie.type, cookie.url, cookie.disabled); >diff --git a/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js b/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js >index dc56f939409ba61983f1572ccdaf168dff0d7306..a3eabfe7a9e70ef9d03e617ddb8286887a47f51f 100644 >--- a/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js >+++ b/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js >@@ -43,11 +43,18 @@ WI.DOMTreeManager = class DOMTreeManager extends WI.Object > this._loadNodeAttributesTimeout = 0; > this._inspectedNode = null; > >+ this._breakpointsForEventListeners = new Map; >+ > WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); > } > > // Public > >+ get eventBreakpoints() >+ { >+ return Array.from(this._breakpointsForEventListeners.values()); >+ } >+ > requestDocument(callback) > { > if (this._document) { >@@ -219,6 +226,7 @@ WI.DOMTreeManager = class DOMTreeManager extends WI.Object > _setDocument(payload) > { > this._idToDOMNode = {}; >+ this._breakpointsForEventListeners.clear(); > > let newDocument = null; > if (payload && "nodeId" in payload) >@@ -538,9 +546,50 @@ WI.DOMTreeManager = class DOMTreeManager extends WI.Object > DOMAgent.setInspectedNode(node.id, callback); > } > >- setEventListenerDisabled(eventListenerId, disabled) >+ setEventListenerDisabled(eventListener, disabled) >+ { >+ DOMAgent.setEventListenerDisabled(eventListener.eventListenerId, disabled, (error) => { >+ if (error) >+ console.error(error); >+ }); >+ } >+ >+ setBreakpointForEventListener(eventListener) >+ { >+ let breakpoint = new WI.EventBreakpoint(eventListener.type, {eventListener}); >+ this._breakpointsForEventListeners.set(eventListener.eventListenerId, breakpoint); >+ >+ DOMAgent.setBreakpointForEventListener(eventListener.eventListenerId, (error) => { >+ if (error) { >+ console.error(error); >+ return; >+ } >+ >+ WI.domDebuggerManager.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointAdded, {breakpoint}); >+ }); >+ } >+ >+ removeBreakpointForEventListener(eventListener) >+ { >+ let breakpoint = this._breakpointsForEventListeners.get(eventListener.eventListenerId); >+ console.assert(breakpoint); >+ >+ this._breakpointsForEventListeners.delete(eventListener.eventListenerId); >+ >+ DOMAgent.removeBreakpointForEventListener(eventListener.eventListenerId, (error) => { >+ if (error) { >+ console.error(error); >+ return; >+ } >+ >+ if (breakpoint) >+ WI.domDebuggerManager.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointRemoved, {breakpoint}); >+ }); >+ } >+ >+ breakpointForEventListenerId(eventListenerId) > { >- DOMAgent.setEventListenerDisabled(eventListenerId, disabled); >+ return this._breakpointsForEventListeners.get(eventListenerId) || null; > } > > _buildHighlightConfig(mode = "all") >diff --git a/Source/WebInspectorUI/UserInterface/Controllers/EventBreakpointTreeController.js b/Source/WebInspectorUI/UserInterface/Controllers/EventBreakpointTreeController.js >index 8960f2ba53990a44efe23eb337df75861109df7b..6f2f2fc957504d3e2176aa1c861e3740fb8135bf 100644 >--- a/Source/WebInspectorUI/UserInterface/Controllers/EventBreakpointTreeController.js >+++ b/Source/WebInspectorUI/UserInterface/Controllers/EventBreakpointTreeController.js >@@ -32,6 +32,9 @@ WI.EventBreakpointTreeController = class EventBreakpointTreeController > WI.domDebuggerManager.addEventListener(WI.DOMDebuggerManager.Event.EventBreakpointAdded, this._eventBreakpointAdded, this); > WI.domDebuggerManager.addEventListener(WI.DOMDebuggerManager.Event.EventBreakpointRemoved, this._eventBreakpointRemoved, this); > >+ for (let breakpoint of WI.domTreeManager.eventBreakpoints) >+ this._addTreeElement(breakpoint); >+ > for (let breakpoint of WI.domDebuggerManager.eventBreakpoints) > this._addTreeElement(breakpoint); > } >diff --git a/Source/WebInspectorUI/UserInterface/Models/EventBreakpoint.js b/Source/WebInspectorUI/UserInterface/Models/EventBreakpoint.js >index 89c9273c771381d3931e18341784e38f3b90f310..7dd7f36705f866bb9c048821bf75c9b9722ae827 100644 >--- a/Source/WebInspectorUI/UserInterface/Models/EventBreakpoint.js >+++ b/Source/WebInspectorUI/UserInterface/Models/EventBreakpoint.js >@@ -25,19 +25,31 @@ > > WI.EventBreakpoint = class EventBreakpoint extends WI.Object > { >- constructor(eventName, disabled) >+ constructor(eventName, {disabled, eventListener} = {}) > { > super(); > > console.assert(typeof eventName === "string"); > > this._eventName = eventName; >+ > this._disabled = disabled || false; >+ this._eventListener = eventListener || null; >+ } >+ >+ // Static >+ >+ static fromPayload(payload) >+ { >+ return new WI.EventBreakpoint(payload.eventName, { >+ disabled: !!payload.disabled, >+ }); > } > > // Public > > get eventName() { return this._eventName; } >+ get eventListener() { return this._eventListener; } > > get disabled() > { >diff --git a/Source/WebInspectorUI/UserInterface/Views/DebuggerSidebarPanel.js b/Source/WebInspectorUI/UserInterface/Views/DebuggerSidebarPanel.js >index 63ba0a59b7854dad37d6cc8602e8ed86b913c6ce..5fd456ef60818484f40d004aa24176840cdbe1aa 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/DebuggerSidebarPanel.js >+++ b/Source/WebInspectorUI/UserInterface/Views/DebuggerSidebarPanel.js >@@ -1057,7 +1057,11 @@ WI.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WI.NavigationSideba > case WI.DebuggerManager.PauseReason.EventListener: > console.assert(pauseData, "Expected data with an event listener, but found none."); > if (pauseData) { >- let eventBreakpoint = WI.domDebuggerManager.eventBreakpointForEventName(pauseData.eventName); >+ let eventBreakpoint = null; >+ if (pauseData.eventListenerId) >+ eventBreakpoint = WI.domTreeManager.breakpointForEventListenerId(pauseData.eventListenerId); >+ if (!eventBreakpoint) >+ eventBreakpoint = WI.domDebuggerManager.eventBreakpointForEventName(pauseData.eventName); > console.assert(eventBreakpoint, "Expected Event Listener breakpoint for event name.", pauseData.eventName); > > this._pauseReasonTreeOutline = this.createContentTreeOutline(true); >@@ -1068,7 +1072,15 @@ WI.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WI.NavigationSideba > let eventBreakpointRow = new WI.DetailsSectionRow; > eventBreakpointRow.element.appendChild(this._pauseReasonTreeOutline.element); > >- this._pauseReasonGroup.rows = [eventBreakpointRow]; >+ let rows = [eventBreakpointRow]; >+ >+ let eventListener = eventBreakpoint.eventListener; >+ if (eventListener && eventListener.eventListenerId === pauseData.eventListenerId) { >+ let ownerElementRow = new WI.DetailsSectionSimpleRow(WI.UIString("Element"), WI.linkifyNodeReference(eventListener.node)); >+ rows.push(ownerElementRow); >+ } >+ >+ this._pauseReasonGroup.rows = rows; > } > return true; > >diff --git a/Source/WebInspectorUI/UserInterface/Views/EventBreakpointTreeElement.js b/Source/WebInspectorUI/UserInterface/Views/EventBreakpointTreeElement.js >index 630ff0bcfc43c420dc47213533b112c7c07baf62..4be95068321eca039e344f97eb2e4b3e3cb1e2d6 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/EventBreakpointTreeElement.js >+++ b/Source/WebInspectorUI/UserInterface/Views/EventBreakpointTreeElement.js >@@ -36,7 +36,10 @@ WI.EventBreakpointTreeElement = class EventBreakpointTreeElement extends WI.Gene > if (!title) > title = breakpoint.eventName; > >- const subtitle = null; >+ let subtitle = null; >+ if (breakpoint.eventListener) >+ subtitle = WI.linkifyNodeReference(breakpoint.eventListener.node); >+ > super(classNames, title, subtitle, breakpoint); > > this._statusImageElement = document.createElement("img"); >@@ -78,7 +81,10 @@ WI.EventBreakpointTreeElement = class EventBreakpointTreeElement extends WI.Gene > > ondelete() > { >- WI.domDebuggerManager.removeEventBreakpoint(this.representedObject); >+ if (this.representedObject.eventListener) >+ WI.domTreeManager.removeBreakpointForEventListener(this.representedObject.eventListener); >+ else >+ WI.domDebuggerManager.removeEventBreakpoint(this.representedObject); > return true; > } > >@@ -97,13 +103,18 @@ WI.EventBreakpointTreeElement = class EventBreakpointTreeElement extends WI.Gene > populateContextMenu(contextMenu, event) > { > let breakpoint = this.representedObject; >- let label = breakpoint.disabled ? WI.UIString("Enable Breakpoint") : WI.UIString("Disable Breakpoint"); >- contextMenu.appendItem(label, this._toggleBreakpoint.bind(this)); >+ if (!breakpoint.eventListener) { >+ let label = breakpoint.disabled ? WI.UIString("Enable Breakpoint") : WI.UIString("Disable Breakpoint"); >+ contextMenu.appendItem(label, this._toggleBreakpoint.bind(this)); >+ } > > if (WI.domDebuggerManager.isBreakpointRemovable(breakpoint)) { > contextMenu.appendSeparator(); > contextMenu.appendItem(WI.UIString("Delete Breakpoint"), () => { >- WI.domDebuggerManager.removeEventBreakpoint(breakpoint); >+ if (breakpoint.eventListener) >+ WI.domTreeManager.removeBreakpointForEventListener(breakpoint.eventListener); >+ else >+ WI.domDebuggerManager.removeEventBreakpoint(breakpoint); > }); > } > } >@@ -129,6 +140,11 @@ WI.EventBreakpointTreeElement = class EventBreakpointTreeElement extends WI.Gene > > _toggleBreakpoint() > { >+ if (this.representedObject.eventListener) { >+ WI.domTreeManager.removeBreakpointForEventListener(this.representedObject.eventListener); >+ return; >+ } >+ > this.representedObject.disabled = !this.representedObject.disabled; > } > >diff --git a/Source/WebInspectorUI/UserInterface/Views/EventListenerSectionGroup.js b/Source/WebInspectorUI/UserInterface/Views/EventListenerSectionGroup.js >index 67e7e3112bc0890ec33f26c08531656c4fb8004a..0ab20cb354e1772161aee501259b4df99167fb10 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/EventListenerSectionGroup.js >+++ b/Source/WebInspectorUI/UserInterface/Views/EventListenerSectionGroup.js >@@ -52,8 +52,13 @@ WI.EventListenerSectionGroup = class EventListenerSectionGroup extends WI.Detail > if (this._eventListener.once) > rows.push(new WI.DetailsSectionSimpleRow(WI.UIString("Once"), WI.UIString("Yes"))); > >- if (DOMAgent.setEventListenerDisabled && this._eventListener.eventListenerId) >- rows.push(this._createDisabledToggleRow()); >+ if (this._eventListener.eventListenerId) { >+ if (DOMAgent.setEventListenerDisabled) >+ rows.push(this._createDisabledToggleRow()); >+ >+ if (DOMAgent.setBreakpointForEventListener && DOMAgent.removeBreakpointForEventListener) >+ rows.push(this._createBreakpointToggleRow()); >+ } > > this.rows = rows; > } >@@ -125,7 +130,7 @@ WI.EventListenerSectionGroup = class EventListenerSectionGroup extends WI.Detail > > toggleElement.addEventListener("change", (event) => { > this._eventListener.disabled = !toggleElement.checked; >- WI.domTreeManager.setEventListenerDisabled(this._eventListener.eventListenerId, this._eventListener.disabled); >+ WI.domTreeManager.setEventListenerDisabled(this._eventListener, this._eventListener.disabled); > updateTitle(); > }); > >@@ -137,4 +142,38 @@ WI.EventListenerSectionGroup = class EventListenerSectionGroup extends WI.Detail > > return new WI.DetailsSectionSimpleRow(toggleLabel, toggleElement); > } >+ >+ _createBreakpointToggleRow() >+ { >+ let checkboxElement = document.createElement("input"); >+ checkboxElement.type = "checkbox"; >+ checkboxElement.checked = !!this._eventListener.hasBreakpoint; >+ >+ let updateTitle = () => { >+ if (this._eventListener.hasBreakpoint) >+ checkboxElement.title = WI.UIString("Delete Breakpoint"); >+ else >+ checkboxElement.title = WI.UIString("Add Breakpoint"); >+ }; >+ >+ updateTitle(); >+ >+ checkboxElement.addEventListener("change", (event) => { >+ this._eventListener.hasBreakpoint = !!checkboxElement.checked; >+ if (this._eventListener.hasBreakpoint) >+ WI.domTreeManager.setBreakpointForEventListener(this._eventListener); >+ else >+ WI.domTreeManager.removeBreakpointForEventListener(this._eventListener); >+ >+ updateTitle(); >+ }); >+ >+ let labelElement = document.createElement("span"); >+ labelElement.textContent = WI.UIString("Breakpoint"); >+ labelElement.addEventListener("click", (event) => { >+ checkboxElement.click(); >+ }); >+ >+ return new WI.DetailsSectionSimpleRow(labelElement, checkboxElement); >+ } > }; >diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog >index 3ac24c363dc74ee9dd2c7df30425d55cb65c6326..2f00f03badf0ae151887d5895b41eba8183f5035 100644 >--- a/LayoutTests/ChangeLog >+++ b/LayoutTests/ChangeLog >@@ -1,3 +1,13 @@ >+2018-08-20 Devin Rousso <drousso@apple.com> >+ >+ Web Inspector: allow breakpoints to be set for specific event listeners >+ https://bugs.webkit.org/show_bug.cgi?id=183138 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * inspector/dom/breakpoint-for-event-listener-expected.txt: Added. >+ * inspector/dom/breakpoint-for-event-listener.html: Added. >+ > 2018-08-18 Ali Juma <ajuma@chromium.org> > > [IntersectionObserver] Fire an initial dummy notification >diff --git a/LayoutTests/inspector/dom/breakpoint-for-event-listener-expected.txt b/LayoutTests/inspector/dom/breakpoint-for-event-listener-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..033722469db6bb6b18f954f1c1725eddc8740050 >--- /dev/null >+++ b/LayoutTests/inspector/dom/breakpoint-for-event-listener-expected.txt >@@ -0,0 +1,27 @@ >+Testing DOMAgent.setBreakpointForEventListener and DOMAgent.removeBreakpointForEventListener. >+ >+ >+ >+== Running test suite: DOM.BreakpointForEventListener >+-- Running test case: DOM.setBreakpointForEventListener >+Adding breakpoint for event listener... >+Clicking button2... >+PASS: Should not pause before button2 event handler is run. >+Clicking button1... >+PASS: Should pause before button1 event handler is run. >+ >+-- Running test case: DOM.removeBreakpointForEventListener >+Removing breakpoint for event listener... >+Clicking button2... >+PASS: Should not pause before button2 event handler is run. >+Clicking button1... >+PASS: Should not pause before button1 event handler is run. >+ >+-- Running test case: DOM.setBreakpointForEventListener.Invalid >+PASS: Should produce an error. >+Error: No event listener for given identifier. >+ >+-- Running test case: DOM.removeBreakpointForEventListener.Invalid >+PASS: Should produce an error. >+Error: No event listener for given identifier. >+ >diff --git a/LayoutTests/inspector/dom/breakpoint-for-event-listener.html b/LayoutTests/inspector/dom/breakpoint-for-event-listener.html >new file mode 100644 >index 0000000000000000000000000000000000000000..9038ca446e1a6ea737b3bd2ae25a0710612be083 >--- /dev/null >+++ b/LayoutTests/inspector/dom/breakpoint-for-event-listener.html >@@ -0,0 +1,217 @@ >+<!doctype html> >+<html> >+<head> >+<script src="../../http/tests/inspector/resources/inspector-test.js"></script> >+<script> >+function handleButton1Click(event) { >+ TestPage.dispatchEventToFrontend("TestPageButton1Click"); >+} >+ >+function handleButton2Click(event) { >+ TestPage.dispatchEventToFrontend("TestPageButton2Click"); >+} >+ >+function clickButton1() { >+ document.getElementById("button1").click(); >+} >+ >+function clickButton2() { >+ document.getElementById("button2").click(); >+} >+ >+function test() { >+ let button1Node = null; >+ let button2Node = null; >+ >+ function awaitGetClickEventListener(nodeId) { >+ return DOMAgent.getEventListenersForNode(nodeId) >+ .then(({listeners}) => listeners.find((eventListener) => eventListener.type === "click")); >+ } >+ >+ let suite = InspectorTest.createAsyncSuite("DOM.BreakpointForEventListener"); >+ >+ suite.addTestCase({ >+ name: "DOM.setBreakpointForEventListener", >+ description: "Check that the debugger pauses for this event.", >+ test(resolve, reject) { >+ let paused = false; >+ let clickingButton1 = false; >+ >+ let listener = WI.debuggerManager.singleFireEventListener(WI.DebuggerManager.Event.Paused, (event) => { >+ if (clickingButton1) >+ InspectorTest.pass("Should pause before button1 event handler is run."); >+ else >+ InspectorTest.fail("Should not pause before button2 event handler is run."); >+ >+ if (paused) >+ InspectorTest.fail("Should not pause twice."); >+ >+ paused = true; >+ >+ let targetData = WI.debuggerManager.dataForTarget(WI.debuggerManager.activeCallFrame.target); >+ InspectorTest.assert(targetData.pauseReason === WI.DebuggerManager.PauseReason.EventListener, `Pause reason should be EventListener.`); >+ InspectorTest.assert(targetData.pauseData.eventName === "click", `Pause data eventName should be "click".`); >+ >+ awaitGetClickEventListener(button1Node.id) >+ .then((clickEventListener) => { >+ InspectorTest.assert(targetData.pauseData.eventListenerId === clickEventListener.eventListenerId, `Pause data eventListenerId should be "${clickEventListener.eventListenerId}".`); >+ InspectorTest.assert(clickEventListener.hasBreakpoint, "Click event listener for button1 should have a breakpoint."); >+ >+ WI.debuggerManager.resume(); >+ }); >+ }); >+ >+ InspectorTest.singleFireEventListener("TestPageButton1Click", (event) => { >+ if (!paused) { >+ WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.Paused, listener); >+ >+ InspectorTest.fail("Should pause before button1 event handler is run."); >+ } >+ >+ resolve(); >+ }); >+ >+ InspectorTest.singleFireEventListener("TestPageButton2Click", (event) => { >+ if (!paused) >+ InspectorTest.pass("Should not pause before button2 event handler is run."); >+ >+ InspectorTest.log("Clicking button1..."); >+ clickingButton1 = true; >+ InspectorTest.evaluateInPage(`clickButton1()`); >+ }); >+ >+ awaitGetClickEventListener(button1Node.id) >+ .then((clickEventListener) => { >+ InspectorTest.assert(!clickEventListener.hasBreakpoint, "Click event listener for button1 should not have a breakpoint."); >+ >+ InspectorTest.log("Adding breakpoint for event listener..."); >+ return DOMAgent.setBreakpointForEventListener(clickEventListener.eventListenerId); >+ }) >+ .then(() => { >+ InspectorTest.log("Clicking button2..."); >+ return InspectorTest.evaluateInPage(`clickButton2()`); >+ }) >+ .catch(reject); >+ } >+ }); >+ >+ suite.addTestCase({ >+ name: "DOM.removeBreakpointForEventListener", >+ description: "Check that the debugger does the not pause for this event.", >+ test(resolve, reject) { >+ let paused = false; >+ let clickingButton1 = false; >+ >+ let listener = WI.debuggerManager.singleFireEventListener(WI.DebuggerManager.Event.Paused, (event) => { >+ if (clickingButton1) >+ InspectorTest.fail("Should not pause before button1 event handler is run."); >+ else >+ InspectorTest.fail("Should not pause before button2 event handler is run."); >+ >+ if (paused) >+ InspectorTest.fail("Should not pause twice."); >+ >+ paused = true; >+ >+ let targetData = WI.debuggerManager.dataForTarget(WI.debuggerManager.activeCallFrame.target); >+ InspectorTest.assert(targetData.pauseReason === WI.DebuggerManager.PauseReason.EventListener, `Pause reason should be EventListener.`); >+ InspectorTest.assert(targetData.pauseData.eventName === "click", `Pause data eventName should be "click".`); >+ >+ awaitGetClickEventListener(button1Node.id) >+ .then((clickEventListener) => { >+ InspectorTest.assert(targetData.pauseData.eventListenerId === clickEventListener.eventListenerId, `Pause data eventListenerId should be "${clickEventListener.eventListenerId}".`); >+ InspectorTest.assert(clickEventListener.hasBreakpoint, "Click event listener for button1 should have a breakpoint."); >+ >+ WI.debuggerManager.resume(); >+ }); >+ }); >+ >+ InspectorTest.singleFireEventListener("TestPageButton1Click", (event) => { >+ if (!paused) { >+ WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.Paused, listener); >+ >+ InspectorTest.pass("Should not pause before button1 event handler is run."); >+ } >+ >+ resolve(); >+ }); >+ >+ InspectorTest.singleFireEventListener("TestPageButton2Click", (event) => { >+ if (!paused) >+ InspectorTest.pass("Should not pause before button2 event handler is run."); >+ >+ InspectorTest.log("Clicking button1..."); >+ clickingButton1 = true; >+ InspectorTest.evaluateInPage(`clickButton1()`); >+ }); >+ >+ awaitGetClickEventListener(button1Node.id) >+ .then((clickEventListener) => { >+ InspectorTest.assert(clickEventListener.hasBreakpoint, "Click event listener for button1 should have a breakpoint."); >+ >+ InspectorTest.log("Removing breakpoint for event listener..."); >+ return DOMAgent.removeBreakpointForEventListener(clickEventListener.eventListenerId); >+ }) >+ .then(() => { >+ InspectorTest.log("Clicking button2..."); >+ return InspectorTest.evaluateInPage(`clickButton2()`); >+ }) >+ .catch(reject); >+ } >+ }); >+ >+ suite.addTestCase({ >+ name: "DOM.setBreakpointForEventListener.Invalid", >+ description: "Invalid event listener identifiers should cause an error.", >+ test(resolve, reject) { >+ const invalidEventListenerId = 9999999; >+ DOMAgent.setBreakpointForEventListener(invalidEventListenerId, (error) => { >+ InspectorTest.expectThat(error, "Should produce an error."); >+ InspectorTest.log("Error: " + error); >+ resolve(); >+ }); >+ } >+ }); >+ >+ suite.addTestCase({ >+ name: "DOM.removeBreakpointForEventListener.Invalid", >+ description: "Invalid event listener identifiers should cause an error.", >+ test(resolve, reject) { >+ const invalidEventListenerId = 9999999; >+ DOMAgent.removeBreakpointForEventListener(invalidEventListenerId, (error) => { >+ InspectorTest.expectThat(error, "Should produce an error."); >+ InspectorTest.log("Error: " + error); >+ resolve(); >+ }); >+ } >+ }); >+ >+ WI.domTreeManager.requestDocument((documentNode) => { >+ Promise.all([ >+ WI.domTreeManager.querySelector(documentNode.id, "#button1"), >+ WI.domTreeManager.querySelector(documentNode.id, "#button2"), >+ ]) >+ .then(([button1Id, button2Id]) => { >+ button1Node = WI.domTreeManager.nodeForId(button1Id); >+ button2Node = WI.domTreeManager.nodeForId(button2Id); >+ >+ suite.runTestCasesAndFinish(); >+ }) >+ .catch(() => { >+ InspectorTest.fail("Unable to retrieve nodes."); >+ InspectorTest.completeTest(); >+ }); >+ }); >+} >+</script> >+</head> >+<body onload="runTest()"> >+ <p>Testing DOMAgent.setBreakpointForEventListener and DOMAgent.removeBreakpointForEventListener.</p> >+ <button id="button1"></button> >+ <button id="button2"></button> >+ <script> >+ document.getElementById("button1").addEventListener("click", handleButton1Click); >+ document.getElementById("button2").addEventListener("click", handleButton2Click); >+ </script> >+</body> >+</html>
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 183138
:
334671
|
334672
|
347283
|
347388
|
347404
|
347407
|
347420
|
347428
|
347432
|
347434
|
347435
|
347437
|
347446
|
347449
|
347521
|
347554
|
347573