From 1de3590b0b4e74778084a1733a262849e87f0d96 Mon Sep 17 00:00:00 2001 From: Devin Rousso Date: Sat, 14 Oct 2017 18:56:32 +0000 Subject: [PATCH] Web Inspector: provide a way to enable/disable event listeners https://bugs.webkit.org/show_bug.cgi?id=177451 Reviewed by Joseph Pecoraro. Source/JavaScriptCore: * inspector/protocol/DOM.json: Add `setEventListenerDisabled` command that enables/disables a specific event listener during event dispatch. When a disabled event listener is fired, the listener's callback will not be called. Source/WebCore: Test: inspector/dom/setEventListenerDisabled.html * dom/EventTarget.cpp: (WebCore::EventTarget::fireEventListeners): Add InspectorInstrumentation call to isEventListenerDisabled. If true, the event listener's callback will not be called. * inspector/InspectorDOMAgent.h: * inspector/InspectorDOMAgent.cpp: (WebCore::InspectorDOMAgent::discardBindings): (WebCore::InspectorDOMAgent::getEventListenersForNode): (WebCore::InspectorDOMAgent::setEventListenerDisabled): (WebCore::InspectorDOMAgent::buildObjectForEventListener): (WebCore::InspectorDOMAgent::willRemoveEventListener): (WebCore::InspectorDOMAgent::isEventListenerDisabled): Introduce a mapping of `EventListener*` to `InspectorEventListener`, a struct for uniquely identifying event listeners so they can be referenced from the frontend. We only add items to this mapping when `getEventListenersForNode` is called, as that is when EventListener data is sent to the frontend. This allows us to defer creating an Inspector "mirror" object for each EventListener until it is needed. Items are removed whenever an event listener is removed or when the document changes. * inspector/InspectorInstrumentation.h: (WebCore::InspectorInstrumentation::isEventListenerDisabled): * inspector/InspectorInstrumentation.cpp: (WebCore::InspectorInstrumentation::willRemoveEventListenerImpl): (WebCore::InspectorInstrumentation::isEventListenerDisabledImpl): Pass additional parameters to InspectorDOMAgent so it can determine if the event listener actually exists. If not, don't dispatch an event to the frontend as nothing will change. Source/WebInspectorUI: * Localizations/en.lproj/localizedStrings.js: * UserInterface/Controllers/DOMTreeManager.js: (WI.DOMTreeManager.prototype.setEventListenerDisabled): * UserInterface/Views/DOMNodeDetailsSidebarPanel.js: (WI.DOMNodeDetailsSidebarPanel.prototype.attached): (WI.DOMNodeDetailsSidebarPanel.prototype.detached): (WI.DOMNodeDetailsSidebarPanel.prototype._eventListenersChanged): (WI.DOMNodeDetailsSidebarPanel.prototype.addEventListeners): Deleted. (WI.DOMNodeDetailsSidebarPanel.prototype.removeEventListeners): Deleted. Listen for `WI.DOMNode.Event.EventListenersChanged` on all instances of WI.DOMNode, since we will still want to refresh the event listeners section in the event that an event listener is removed from a parent node. * UserInterface/Views/EventListenerSectionGroup.js: (WI.EventListenerSectionGroup): (WI.EventListenerSectionGroup.prototype._eventText): (WI.EventListenerSectionGroup.prototype._nodeTextOrLink): (WI.EventListenerSectionGroup.prototype._createDisabledToggleElement): (WI.EventListenerSectionGroup.prototype._createDisabledToggleElement.updateTitle): * UserInterface/Views/EventListenerSectionGroup.css: (.event-listener-section > .content input[type="checkbox"]): * UserInterface/Views/DetailsSectionSimpleRow.js: (WI.DetailsSectionSimpleRow.prototype.get label): (WI.DetailsSectionSimpleRow.prototype.set label): LayoutTests: * inspector/dom/setEventListenerDisabled-expected.txt: Added. * inspector/dom/setEventListenerDisabled.html: Added. Canonical link: https://commits.webkit.org/194528@main git-svn-id: https://svn.webkit.org/repository/webkit/trunk@223321 268f45cc-cd09-0410-ab3c-d52691b4dbfc --- LayoutTests/ChangeLog | 10 ++ .../dom/setEventListenerDisabled-expected.txt | 22 +++ .../dom/setEventListenerDisabled.html | 129 ++++++++++++++++++ Source/JavaScriptCore/ChangeLog | 12 ++ .../inspector/protocol/DOM.json | 17 ++- Source/WebCore/ChangeLog | 37 +++++ Source/WebCore/dom/EventTarget.cpp | 3 + .../WebCore/inspector/InspectorDOMAgent.cpp | 68 ++++++++- Source/WebCore/inspector/InspectorDOMAgent.h | 27 +++- .../inspector/InspectorInstrumentation.cpp | 9 +- .../inspector/InspectorInstrumentation.h | 10 ++ Source/WebInspectorUI/ChangeLog | 35 +++++ .../en.lproj/localizedStrings.js | 3 + .../Controllers/DOMTreeManager.js | 5 + .../Views/DOMNodeDetailsSidebarPanel.js | 27 ++-- .../Views/DetailsSectionSimpleRow.js | 10 +- .../Views/EventListenerSectionGroup.css | 6 + .../Views/EventListenerSectionGroup.js | 33 +++++ 18 files changed, 442 insertions(+), 21 deletions(-) create mode 100644 LayoutTests/inspector/dom/setEventListenerDisabled-expected.txt create mode 100644 LayoutTests/inspector/dom/setEventListenerDisabled.html diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog index 96e3749b7d60..9722d298d4e6 100644 --- a/LayoutTests/ChangeLog +++ b/LayoutTests/ChangeLog @@ -1,3 +1,13 @@ +2017-10-14 Devin Rousso + + Web Inspector: provide a way to enable/disable event listeners + https://bugs.webkit.org/show_bug.cgi?id=177451 + + Reviewed by Joseph Pecoraro. + + * inspector/dom/setEventListenerDisabled-expected.txt: Added. + * inspector/dom/setEventListenerDisabled.html: Added. + 2017-10-14 Per Arne Vollan Mark fast/frames/frame-unload-navigate-and-setTimeout-assert-fail.html as a flaky crash on Windows. diff --git a/LayoutTests/inspector/dom/setEventListenerDisabled-expected.txt b/LayoutTests/inspector/dom/setEventListenerDisabled-expected.txt new file mode 100644 index 000000000000..e911640fd715 --- /dev/null +++ b/LayoutTests/inspector/dom/setEventListenerDisabled-expected.txt @@ -0,0 +1,22 @@ +Testing DOMAgent.setEventListenerDisabled. + + +== Running test suite: DOM.setEventListenerDisabled +-- Running test case: DOM.setEventListenerDisabled.DisabledClickEvent +Click event listener is enabled. +Disabling event listener... + clicked. +PASS: Click event listener did not fire. +Click event listener is disabled. + +-- Running test case: DOM.setEventListenerDisabled.ReenabledClickEvent +Click event listener is disabled. +Enabling event listener... + clicked. +PASS: Click event listener fired. +Click event listener is enabled. + +-- Running test case: DOM.setEventListenerDisabled.Invalid +PASS: Should produce an error. +Error: No event listener for given identifier. + diff --git a/LayoutTests/inspector/dom/setEventListenerDisabled.html b/LayoutTests/inspector/dom/setEventListenerDisabled.html new file mode 100644 index 000000000000..c665dc5d608b --- /dev/null +++ b/LayoutTests/inspector/dom/setEventListenerDisabled.html @@ -0,0 +1,129 @@ + + + + + + + +

Testing DOMAgent.setEventListenerDisabled.

+ + + + diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog index 969a8cabe6e6..e40469da505d 100644 --- a/Source/JavaScriptCore/ChangeLog +++ b/Source/JavaScriptCore/ChangeLog @@ -1,3 +1,15 @@ +2017-10-14 Devin Rousso + + Web Inspector: provide a way to enable/disable event listeners + https://bugs.webkit.org/show_bug.cgi?id=177451 + + Reviewed by Joseph Pecoraro. + + * inspector/protocol/DOM.json: + Add `setEventListenerDisabled` command that enables/disables a specific event listener + during event dispatch. When a disabled event listener is fired, the listener's callback will + not be called. + 2017-10-14 Yusuke Suzuki Reland "Add Above/Below comparisons for UInt32 patterns" diff --git a/Source/JavaScriptCore/inspector/protocol/DOM.json b/Source/JavaScriptCore/inspector/protocol/DOM.json index f9df66d3163d..038f3090b97c 100644 --- a/Source/JavaScriptCore/inspector/protocol/DOM.json +++ b/Source/JavaScriptCore/inspector/protocol/DOM.json @@ -13,6 +13,11 @@ "type": "integer", "description": "Unique DOM node identifier used to reference a node that may not have been pushed to the front-end." }, + { + "id": "EventListenerId", + "type": "integer", + "description": "Unique event listener identifier." + }, { "id": "PseudoType", "type": "string", @@ -74,6 +79,7 @@ "type": "object", "description": "A structure holding event listener properties.", "properties": [ + { "name": "eventListenerId", "$ref": "EventListenerId" }, { "name": "type", "type": "string", "description": "EventListener's type." }, { "name": "useCapture", "type": "boolean", "description": "EventListener's useCapture." }, { "name": "isAttribute", "type": "boolean", "description": "EventListener's isAttribute." }, @@ -83,7 +89,8 @@ { "name": "sourceName", "type": "string", "optional": true, "description": "Source script URL." }, { "name": "handler", "$ref": "Runtime.RemoteObject", "optional": true, "description": "Event handler function value." }, { "name": "passive", "type": "boolean", "optional": true, "description": "EventListener's passive." }, - { "name": "once", "type": "boolean", "optional": true, "description": "EventListener's once." } + { "name": "once", "type": "boolean", "optional": true, "description": "EventListener's once." }, + { "name": "disabled", "type": "boolean", "optional": true } ] }, { @@ -258,6 +265,14 @@ { "name": "listeners", "type": "array", "items": { "$ref": "EventListener"}, "description": "Array of relevant listeners." } ] }, + { + "name": "setEventListenerDisabled", + "description": "Enable/disable the given event listener. A disabled event listener will not fire.", + "parameters": [ + { "name": "eventListenerId", "$ref": "EventListenerId" }, + { "name": "disabled", "type": "boolean" } + ] + }, { "name": "getAccessibilityPropertiesForNode", "description": "Returns a dictionary of accessibility properties for the node.", diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog index 26318ab83e75..991c8e2a1bf3 100644 --- a/Source/WebCore/ChangeLog +++ b/Source/WebCore/ChangeLog @@ -1,3 +1,40 @@ +2017-10-14 Devin Rousso + + Web Inspector: provide a way to enable/disable event listeners + https://bugs.webkit.org/show_bug.cgi?id=177451 + + Reviewed by Joseph Pecoraro. + + Test: inspector/dom/setEventListenerDisabled.html + + * dom/EventTarget.cpp: + (WebCore::EventTarget::fireEventListeners): + Add InspectorInstrumentation call to isEventListenerDisabled. If true, the event listener's + callback will not be called. + + * inspector/InspectorDOMAgent.h: + * inspector/InspectorDOMAgent.cpp: + (WebCore::InspectorDOMAgent::discardBindings): + (WebCore::InspectorDOMAgent::getEventListenersForNode): + (WebCore::InspectorDOMAgent::setEventListenerDisabled): + (WebCore::InspectorDOMAgent::buildObjectForEventListener): + (WebCore::InspectorDOMAgent::willRemoveEventListener): + (WebCore::InspectorDOMAgent::isEventListenerDisabled): + Introduce a mapping of `EventListener*` to `InspectorEventListener`, a struct for uniquely + identifying event listeners so they can be referenced from the frontend. We only add items + to this mapping when `getEventListenersForNode` is called, as that is when EventListener + data is sent to the frontend. This allows us to defer creating an Inspector "mirror" object + for each EventListener until it is needed. Items are removed whenever an event listener is + removed or when the document changes. + + * inspector/InspectorInstrumentation.h: + (WebCore::InspectorInstrumentation::isEventListenerDisabled): + * inspector/InspectorInstrumentation.cpp: + (WebCore::InspectorInstrumentation::willRemoveEventListenerImpl): + (WebCore::InspectorInstrumentation::isEventListenerDisabledImpl): + Pass additional parameters to InspectorDOMAgent so it can determine if the event listener + actually exists. If not, don't dispatch an event to the frontend as nothing will change. + 2017-10-14 Sam Weinig Remove HashCountedSet's copyToVector functions diff --git a/Source/WebCore/dom/EventTarget.cpp b/Source/WebCore/dom/EventTarget.cpp index 5a330a1d437d..ddf553ea673e 100644 --- a/Source/WebCore/dom/EventTarget.cpp +++ b/Source/WebCore/dom/EventTarget.cpp @@ -278,6 +278,9 @@ void EventTarget::fireEventListeners(Event& event, EventListenerVector listeners if (event.eventPhase() == Event::BUBBLING_PHASE && registeredListener->useCapture()) continue; + if (InspectorInstrumentation::isEventListenerDisabled(*this, event.type(), registeredListener->callback(), registeredListener->useCapture())) + continue; + // If stopImmediatePropagation has been called, we just break out immediately, without // handling any more events on this target. if (event.immediatePropagationStopped()) diff --git a/Source/WebCore/inspector/InspectorDOMAgent.cpp b/Source/WebCore/inspector/InspectorDOMAgent.cpp index 66b4750bcfa1..009318d744d8 100644 --- a/Source/WebCore/inspector/InspectorDOMAgent.cpp +++ b/Source/WebCore/inspector/InspectorDOMAgent.cpp @@ -486,6 +486,7 @@ void InspectorDOMAgent::discardBindings() { m_documentNodeToIdMap.clear(); m_idToNode.clear(); + m_eventListenerEntries.clear(); releaseDanglingNodes(); m_childrenRequested.clear(); m_backendIdToNode.clear(); @@ -830,12 +831,31 @@ void InspectorDOMAgent::getEventListenersForNode(ErrorString& errorString, int n Vector eventInformation; getEventListeners(node, eventInformation, true); + auto addListener = [&] (RegisteredEventListener& listener, const EventListenerInfo& info) { + int identifier = 0; + bool disabled = 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); + + identifier = inspectorEventListener.identifier; + disabled = inspectorEventListener.disabled; + } else { + identifier = it->value.identifier; + disabled = it->value.disabled; + } + + listenersArray->addItem(buildObjectForEventListener(listener, identifier, info.eventType, info.node, objectGroup, disabled)); + }; + // Get Capturing Listeners (in this order) size_t eventInformationLength = eventInformation.size(); for (auto& info : eventInformation) { for (auto& listener : info.eventListenerVector) { if (listener->useCapture()) - listenersArray->addItem(buildObjectForEventListener(*listener, info.eventType, info.node, objectGroup)); + addListener(*listener, info); } } @@ -844,7 +864,7 @@ void InspectorDOMAgent::getEventListenersForNode(ErrorString& errorString, int n const EventListenerInfo& info = eventInformation[i - 1]; for (auto& listener : info.eventListenerVector) { if (!listener->useCapture()) - listenersArray->addItem(buildObjectForEventListener(*listener, info.eventType, info.node, objectGroup)); + addListener(*listener, info); } } } @@ -881,6 +901,18 @@ void InspectorDOMAgent::getEventListeners(Node* node, Vector& } } +void InspectorDOMAgent::setEventListenerDisabled(ErrorString& errorString, int eventListenerId, bool disabled) +{ + for (InspectorEventListener& inspectorEventListener : m_eventListenerEntries.values()) { + if (inspectorEventListener.identifier == eventListenerId) { + inspectorEventListener.disabled = disabled; + return; + } + } + + errorString = ASCIILiteral("No event listener for given identifier."); +} + void InspectorDOMAgent::getAccessibilityPropertiesForNode(ErrorString& errorString, int nodeId, RefPtr& axProperties) { Node* node = assertNode(errorString, nodeId); @@ -1554,7 +1586,7 @@ RefPtr> InspectorDOMA return WTFMove(pseudoElements); } -Ref InspectorDOMAgent::buildObjectForEventListener(const RegisteredEventListener& registeredEventListener, const AtomicString& eventType, Node* node, const String* objectGroupId) +Ref InspectorDOMAgent::buildObjectForEventListener(const RegisteredEventListener& registeredEventListener, int identifier, const AtomicString& eventType, Node* node, const String* objectGroupId, bool disabled) { Ref eventListener = registeredEventListener.callback(); @@ -1585,6 +1617,7 @@ Ref InspectorDOMAgent::buildObjectForEv } auto value = Inspector::Protocol::DOM::EventListener::create() + .setEventListenerId(identifier) .setType(eventType) .setUseCapture(registeredEventListener.useCapture()) .setIsAttribute(eventListener->isAttribute()) @@ -1610,6 +1643,8 @@ Ref InspectorDOMAgent::buildObjectForEv value->setPassive(true); if (registeredEventListener.isOnce()) value->setOnce(true); + if (disabled) + value->setDisabled(disabled); return value; } @@ -2220,7 +2255,7 @@ void InspectorDOMAgent::didAddEventListener(EventTarget& target) m_frontendDispatcher->didAddEventListener(nodeId); } -void InspectorDOMAgent::willRemoveEventListener(EventTarget& target) +void InspectorDOMAgent::willRemoveEventListener(EventTarget& target, const AtomicString& eventType, EventListener& listener, bool capture) { Node* node = target.toNode(); if (!node) @@ -2230,9 +2265,34 @@ void InspectorDOMAgent::willRemoveEventListener(EventTarget& target) if (!nodeId) return; + bool listenerExists = false; + for (const RefPtr& item : node->eventListeners(eventType)) { + if (item->callback() == listener && item->useCapture() == capture) { + listenerExists = true; + break; + } + } + + if (!listenerExists) + return; + + m_eventListenerEntries.remove(&listener); + 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; + + if (!it->value.disabled) + return false; + + return it->value.eventTarget.get() == &target && it->value.eventType == eventType && it->value.useCapture == capture; +} + Node* InspectorDOMAgent::nodeForPath(const String& path) { // The path is of form "1,HTML,2,BODY,1,DIV" diff --git a/Source/WebCore/inspector/InspectorDOMAgent.h b/Source/WebCore/inspector/InspectorDOMAgent.h index 6e5a34338a48..f7b133fad02a 100644 --- a/Source/WebCore/inspector/InspectorDOMAgent.h +++ b/Source/WebCore/inspector/InspectorDOMAgent.h @@ -123,6 +123,7 @@ class InspectorDOMAgent final : public InspectorAgentBase, public Inspector::DOM void setOuterHTML(ErrorString&, int nodeId, const String& outerHTML) override; void setNodeValue(ErrorString&, int nodeId, const String& value) override; void getEventListenersForNode(ErrorString&, int nodeId, const WTF::String* const objectGroup, RefPtr>& listenersArray) override; + void setEventListenerDisabled(ErrorString&, int eventListenerId, bool disabled) override; void getAccessibilityPropertiesForNode(ErrorString&, int nodeId, RefPtr& axProperties) override; void performSearch(ErrorString&, const String& whitespaceTrimmedQuery, const Inspector::InspectorArray* nodeIds, String* searchId, int* resultCount) override; void getSearchResults(ErrorString&, const String& searchId, int fromIndex, int toIndex, RefPtr>&) override; @@ -168,7 +169,8 @@ class InspectorDOMAgent final : public InspectorAgentBase, public Inspector::DOM void pseudoElementCreated(PseudoElement&); void pseudoElementDestroyed(PseudoElement&); void didAddEventListener(EventTarget&); - void willRemoveEventListener(EventTarget&); + void willRemoveEventListener(EventTarget&, const AtomicString& eventType, EventListener&, bool capture); + bool isEventListenerDisabled(EventTarget&, const AtomicString& eventType, EventListener&, bool capture); // Callbacks that don't directly correspond to an instrumentation entry point. void setDocument(Document*); @@ -232,7 +234,7 @@ class InspectorDOMAgent final : public InspectorAgentBase, public Inspector::DOM Ref> buildArrayForElementAttributes(Element*); Ref> buildArrayForContainerChildren(Node* container, int depth, NodeToIdMap* nodesMap); RefPtr> buildArrayForPseudoElements(const Element&, NodeToIdMap* nodesMap); - Ref buildObjectForEventListener(const RegisteredEventListener&, const AtomicString& eventType, Node*, const String* objectGroupId); + Ref buildObjectForEventListener(const RegisteredEventListener&, int identifier, const AtomicString& eventType, Node*, const String* objectGroupId, bool disabled = false); RefPtr buildObjectForAccessibilityProperties(Node*); void processAccessibilityChildren(RefPtr&&, RefPtr>&&); @@ -273,6 +275,27 @@ class InspectorDOMAgent final : public InspectorAgentBase, public Inspector::DOM bool m_searchingForNode { false }; bool m_suppressAttributeModifiedEvent { false }; bool m_documentRequested { false }; + + struct InspectorEventListener { + int identifier { 1 }; + RefPtr eventTarget; + AtomicString eventType; + bool useCapture { false }; + bool disabled { false }; + + InspectorEventListener() { } + + InspectorEventListener(int identifier, EventTarget& eventTarget, const AtomicString& eventType, bool useCapture) + : identifier(identifier) + , eventTarget(&eventTarget) + , eventType(eventType) + , useCapture(useCapture) + { + } + }; + + HashMap m_eventListenerEntries; + int m_lastEventListenerId { 1 }; }; } // namespace WebCore diff --git a/Source/WebCore/inspector/InspectorInstrumentation.cpp b/Source/WebCore/inspector/InspectorInstrumentation.cpp index c7d5818d7030..c901fff34650 100644 --- a/Source/WebCore/inspector/InspectorInstrumentation.cpp +++ b/Source/WebCore/inspector/InspectorInstrumentation.cpp @@ -327,7 +327,14 @@ void InspectorInstrumentation::willRemoveEventListenerImpl(InstrumentingAgents& if (PageDebuggerAgent* pageDebuggerAgent = instrumentingAgents.pageDebuggerAgent()) pageDebuggerAgent->willRemoveEventListener(target, eventType, listener, capture); if (InspectorDOMAgent* domAgent = instrumentingAgents.inspectorDOMAgent()) - domAgent->willRemoveEventListener(target); + domAgent->willRemoveEventListener(target, eventType, listener, capture); +} + +bool InspectorInstrumentation::isEventListenerDisabledImpl(InstrumentingAgents& instrumentingAgents, EventTarget& target, const AtomicString& eventType, EventListener& listener, bool capture) +{ + if (InspectorDOMAgent* domAgent = instrumentingAgents.inspectorDOMAgent()) + return domAgent->isEventListenerDisabled(target, eventType, listener, capture); + return false; } void InspectorInstrumentation::didPostMessageImpl(InstrumentingAgents& instrumentingAgents, const TimerBase& timer, JSC::ExecState& state) diff --git a/Source/WebCore/inspector/InspectorInstrumentation.h b/Source/WebCore/inspector/InspectorInstrumentation.h index 633b5d0097d4..568d3389417e 100644 --- a/Source/WebCore/inspector/InspectorInstrumentation.h +++ b/Source/WebCore/inspector/InspectorInstrumentation.h @@ -146,6 +146,7 @@ class InspectorInstrumentation { static void didCallFunction(const InspectorInstrumentationCookie&, ScriptExecutionContext*); static void didAddEventListener(EventTarget&, const AtomicString& eventType); static void willRemoveEventListener(EventTarget&, const AtomicString& eventType, EventListener&, bool capture); + static bool isEventListenerDisabled(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&, const RegisteredEventListener&); @@ -323,6 +324,7 @@ class InspectorInstrumentation { 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 bool isEventListenerDisabledImpl(InstrumentingAgents&, EventTarget&, const AtomicString& eventType, EventListener&, bool capture); static InspectorInstrumentationCookie willDispatchEventImpl(InstrumentingAgents&, Document&, const Event&, bool hasEventListeners); static void willHandleEventImpl(InstrumentingAgents&, const Event&, const RegisteredEventListener&); static void didHandleEventImpl(InstrumentingAgents&); @@ -687,6 +689,14 @@ inline void InspectorInstrumentation::willRemoveEventListener(EventTarget& targe willRemoveEventListenerImpl(*instrumentingAgents, target, eventType, listener, capture); } +inline bool InspectorInstrumentation::isEventListenerDisabled(EventTarget& target, const AtomicString& eventType, EventListener& listener, bool capture) +{ + FAST_RETURN_IF_NO_FRONTENDS(false); + if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForContext(target.scriptExecutionContext())) + return isEventListenerDisabledImpl(*instrumentingAgents, target, eventType, listener, capture); + return false; +} + inline void InspectorInstrumentation::didPostMessage(Frame& frame, TimerBase& timer, JSC::ExecState& state) { FAST_RETURN_IF_NO_FRONTENDS(void()); diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog index ba2b97c0a89d..417fe113419e 100644 --- a/Source/WebInspectorUI/ChangeLog +++ b/Source/WebInspectorUI/ChangeLog @@ -1,3 +1,38 @@ +2017-10-14 Devin Rousso + + Web Inspector: provide a way to enable/disable event listeners + https://bugs.webkit.org/show_bug.cgi?id=177451 + + Reviewed by Joseph Pecoraro. + + * Localizations/en.lproj/localizedStrings.js: + + * UserInterface/Controllers/DOMTreeManager.js: + (WI.DOMTreeManager.prototype.setEventListenerDisabled): + + * UserInterface/Views/DOMNodeDetailsSidebarPanel.js: + (WI.DOMNodeDetailsSidebarPanel.prototype.attached): + (WI.DOMNodeDetailsSidebarPanel.prototype.detached): + (WI.DOMNodeDetailsSidebarPanel.prototype._eventListenersChanged): + (WI.DOMNodeDetailsSidebarPanel.prototype.addEventListeners): Deleted. + (WI.DOMNodeDetailsSidebarPanel.prototype.removeEventListeners): Deleted. + Listen for `WI.DOMNode.Event.EventListenersChanged` on all instances of WI.DOMNode, since we + will still want to refresh the event listeners section in the event that an event listener + is removed from a parent node. + + * UserInterface/Views/EventListenerSectionGroup.js: + (WI.EventListenerSectionGroup): + (WI.EventListenerSectionGroup.prototype._eventText): + (WI.EventListenerSectionGroup.prototype._nodeTextOrLink): + (WI.EventListenerSectionGroup.prototype._createDisabledToggleElement): + (WI.EventListenerSectionGroup.prototype._createDisabledToggleElement.updateTitle): + * UserInterface/Views/EventListenerSectionGroup.css: + (.event-listener-section > .content input[type="checkbox"]): + + * UserInterface/Views/DetailsSectionSimpleRow.js: + (WI.DetailsSectionSimpleRow.prototype.get label): + (WI.DetailsSectionSimpleRow.prototype.set label): + 2017-10-13 Devin Rousso Web Inspector: make split console full width of view diff --git a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js index a13e4f33cf59..81fc614f881a 100644 --- a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js +++ b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js @@ -293,6 +293,7 @@ localizedStrings["Dimensions"] = "Dimensions"; localizedStrings["Direction"] = "Direction"; localizedStrings["Disable Breakpoint"] = "Disable Breakpoint"; localizedStrings["Disable Breakpoints"] = "Disable Breakpoints"; +localizedStrings["Disable Event Listener"] = "Disable Event Listener"; localizedStrings["Disable Program"] = "Disable Program"; localizedStrings["Disable all breakpoints (%s)"] = "Disable all breakpoints (%s)"; localizedStrings["Disable paint flashing"] = "Disable paint flashing"; @@ -358,11 +359,13 @@ localizedStrings["Elements"] = "Elements"; localizedStrings["Enable Breakpoint"] = "Enable Breakpoint"; localizedStrings["Enable Breakpoints"] = "Enable Breakpoints"; localizedStrings["Enable Canvas Tab"] = "Enable Canvas Tab"; +localizedStrings["Enable Event Listener"] = "Enable Event Listener"; localizedStrings["Enable Layers Tab"] = "Enable Layers Tab"; localizedStrings["Enable Program"] = "Enable Program"; localizedStrings["Enable all breakpoints (%s)"] = "Enable all breakpoints (%s)"; localizedStrings["Enable breakpoints"] = "Enable breakpoints"; localizedStrings["Enable paint flashing"] = "Enable paint flashing"; +localizedStrings["Enabled"] = "Enabled"; localizedStrings["Encoded"] = "Encoded"; localizedStrings["Encoding"] = "Encoding"; localizedStrings["Enter Tag"] = "Enter Tag"; diff --git a/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js b/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js index 82782fe78ad0..67f3be946af5 100644 --- a/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js +++ b/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js @@ -526,6 +526,11 @@ WI.DOMTreeManager = class DOMTreeManager extends WI.Object DOMAgent.setInspectedNode(node.id, callback); } + setEventListenerDisabled(eventListenerId, disabled) + { + DOMAgent.setEventListenerDisabled(eventListenerId, disabled); + } + _buildHighlightConfig(mode = "all") { let highlightConfig = {showInfo: mode === "all"}; diff --git a/Source/WebInspectorUI/UserInterface/Views/DOMNodeDetailsSidebarPanel.js b/Source/WebInspectorUI/UserInterface/Views/DOMNodeDetailsSidebarPanel.js index 82c4ccf1b509..9a8190eeca03 100644 --- a/Source/WebInspectorUI/UserInterface/Views/DOMNodeDetailsSidebarPanel.js +++ b/Source/WebInspectorUI/UserInterface/Views/DOMNodeDetailsSidebarPanel.js @@ -45,16 +45,6 @@ WI.DOMNodeDetailsSidebarPanel = class DOMNodeDetailsSidebarPanel extends WI.DOMD super.closed(); } - addEventListeners() - { - this.domNode.addEventListener(WI.DOMNode.Event.EventListenersChanged, this._eventListenersChanged, this); - } - - removeEventListeners() - { - this.domNode.removeEventListener(WI.DOMNode.Event.EventListenersChanged, this._eventListenersChanged, this); - } - // Protected initialLayout() @@ -169,6 +159,20 @@ WI.DOMNodeDetailsSidebarPanel = class DOMNodeDetailsSidebarPanel extends WI.DOMD this._attributesDataGridRow.sizeDidChange(); } + attached() + { + super.attached(); + + WI.DOMNode.addEventListener(WI.DOMNode.Event.EventListenersChanged, this._eventListenersChanged, this); + } + + detached() + { + WI.DOMNode.removeEventListener(WI.DOMNode.Event.EventListenersChanged, this._eventListenersChanged, this); + + super.detached(); + } + // Private _accessibilitySupported() @@ -753,7 +757,8 @@ WI.DOMNodeDetailsSidebarPanel = class DOMNodeDetailsSidebarPanel extends WI.DOMD _eventListenersChanged(event) { - this._refreshEventListeners(); + if (event.target === this.domNode || event.target.isAncestor(this.domNode)) + this._refreshEventListeners(); } _attributesChanged(event) diff --git a/Source/WebInspectorUI/UserInterface/Views/DetailsSectionSimpleRow.js b/Source/WebInspectorUI/UserInterface/Views/DetailsSectionSimpleRow.js index 8e4c34ca99c3..e5a5fec1eabb 100644 --- a/Source/WebInspectorUI/UserInterface/Views/DetailsSectionSimpleRow.js +++ b/Source/WebInspectorUI/UserInterface/Views/DetailsSectionSimpleRow.js @@ -73,12 +73,18 @@ WI.DetailsSectionSimpleRow = class DetailsSectionSimpleRow extends WI.DetailsSec get label() { - return this._labelElement.textContent; + return this._label; } set label(label) { - this._labelElement.textContent = label; + this._label = label || ""; + + if (this._label instanceof Node) { + this._labelElement.removeChildren(); + this._labelElement.appendChild(this._label); + } else + this._labelElement.textContent = this._label; } get value() diff --git a/Source/WebInspectorUI/UserInterface/Views/EventListenerSectionGroup.css b/Source/WebInspectorUI/UserInterface/Views/EventListenerSectionGroup.css index 00b18892e12e..4765752e2f00 100644 --- a/Source/WebInspectorUI/UserInterface/Views/EventListenerSectionGroup.css +++ b/Source/WebInspectorUI/UserInterface/Views/EventListenerSectionGroup.css @@ -28,3 +28,9 @@ overflow: hidden; text-overflow: ellipsis; } + +.event-listener-section > .content input[type="checkbox"] { + width: 12px; + height: 12px; + margin: 0; +} diff --git a/Source/WebInspectorUI/UserInterface/Views/EventListenerSectionGroup.js b/Source/WebInspectorUI/UserInterface/Views/EventListenerSectionGroup.js index cdf987ebd631..7156d5498e9b 100644 --- a/Source/WebInspectorUI/UserInterface/Views/EventListenerSectionGroup.js +++ b/Source/WebInspectorUI/UserInterface/Views/EventListenerSectionGroup.js @@ -52,6 +52,9 @@ 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) + rows.push(this._createDisabledToggleRow()); + this.rows = rows; } @@ -104,4 +107,34 @@ WI.EventListenerSectionGroup = class EventListenerSectionGroup extends WI.Detail fragment.append(linkElement, functionName); return fragment; } + + _createDisabledToggleRow() + { + let toggleElement = document.createElement("input"); + toggleElement.type = "checkbox"; + toggleElement.checked = !this._eventListener.disabled; + + let updateTitle = () => { + if (this._eventListener.disabled) + toggleElement.title = WI.UIString("Enable Event Listener"); + else + toggleElement.title = WI.UIString("Disable Event Listener"); + }; + + updateTitle(); + + toggleElement.addEventListener("change", (event) => { + this._eventListener.disabled = !toggleElement.checked; + WI.domTreeManager.setEventListenerDisabled(this._eventListener.eventListenerId, this._eventListener.disabled); + updateTitle(); + }); + + let toggleLabel = document.createElement("span"); + toggleLabel.textContent = WI.UIString("Enabled"); + toggleLabel.addEventListener("click", (event) => { + toggleElement.click(); + }); + + return new WI.DetailsSectionSimpleRow(toggleLabel, toggleElement); + } };