Web Inspector: DOM: include window as part of any event listener chain
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 20 Mar 2019 21:55:05 +0000 (21:55 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 20 Mar 2019 21:55:05 +0000 (21:55 +0000)
https://bugs.webkit.org/show_bug.cgi?id=195730
<rdar://problem/48916872>

Reviewed by Timothy Hatcher.

Source/JavaScriptCore:

* inspector/protocol/DOM.json:
Modify `DOM.getEventListenersForNode` to not save the handler object, as that was never
used by the frontend. Add an `onWindow` optional property to `DOM.EventListener` that is set
when the event listener was retrieved from the `window` object.

Source/WebCore:

Test: inspector/dom/getEventListenersForNode.html

* inspector/agents/InspectorDOMAgent.h:
(WebCore::EventListenerInfo::EventListenerInfo): Deleted.
* inspector/agents/InspectorDOMAgent.cpp:
(WebCore::InspectorDOMAgent::getEventListenersForNode):
(WebCore::InspectorDOMAgent::buildObjectForEventListener):
(WebCore::InspectorDOMAgent::getEventListeners): Deleted.

Source/WebInspectorUI:

Allow non-nodes (e.g. `window`) to be listed as the target of an event listener.
Add support for the same concept when showing breakpoint details after pausing on a specific
event listener in the Debugger/Sources navigation sidebar.

* UserInterface/Views/DOMNodeDetailsSidebarPanel.js:
(WI.DOMNodeDetailsSidebarPanel.prototype.initialLayout):
(WI.DOMNodeDetailsSidebarPanel.prototype._refreshEventListeners.generateGroupsByEvent):
(WI.DOMNodeDetailsSidebarPanel.prototype._refreshEventListeners.generateGroupsByTarget): Added.
(WI.DOMNodeDetailsSidebarPanel.prototype._refreshEventListeners.eventListenersCallback):
(WI.DOMNodeDetailsSidebarPanel.prototype._refreshEventListeners):
(WI.DOMNodeDetailsSidebarPanel.prototype._refreshEventListeners.generateGroupsByNode): Deleted.

* UserInterface/Views/EventListenerSectionGroup.js:
(WI.EventListenerSectionGroup.prototype._targetTextOrLink): Added.
(WI.EventListenerSectionGroup.prototype._nodeTextOrLink): Deleted.

* UserInterface/Views/DebuggerSidebarPanel.js:
(WI.DebuggerSidebarPanel.prototype._addBreakpoint):
(WI.DebuggerSidebarPanel.prototype._breakpointTreeOutlineDeleteTreeElement):
(WI.DebuggerSidebarPanel.prototype._treeSelectionDidChange):
(WI.DebuggerSidebarPanel.prototype._updatePauseReasonSection):
* UserInterface/Views/DebuggerSidebarPanel.css:
(.sidebar > .panel.navigation.debugger > .content > .breakpoints .tree-outline .item.event-target-window .icon): Added.

* UserInterface/Views/SourcesNavigationSidebarPanel.js:
(WI.SourcesNavigationSidebarPanel):
(WI.SourcesNavigationSidebarPanel.prototype._addBreakpoint):
(WI.SourcesNavigationSidebarPanel.prototype._updatePauseReasonSection):
(WI.SourcesNavigationSidebarPanel.prototype._handleTreeSelectionDidChange):
* UserInterface/Views/SourcesNavigationSidebarPanel.css:
(.sidebar > .panel.navigation.sources > .content > .breakpoints .tree-outline .item.event-target-window .icon): Added.

* Localizations/en.lproj/localizedStrings.js:

LayoutTests:

* inspector/dom/getEventListenersForNode.html:
* inspector/dom/getEventListenersForNode-expected.txt:
* inspector/dom/setEventListenerDisabled.html:
* inspector/dom/event-listener-add-remove.html:

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

18 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/dom/event-listener-add-remove.html
LayoutTests/inspector/dom/getEventListenersForNode-expected.txt
LayoutTests/inspector/dom/getEventListenersForNode.html
LayoutTests/inspector/dom/setEventListenerDisabled.html
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/protocol/DOM.json
Source/WebCore/ChangeLog
Source/WebCore/inspector/agents/InspectorDOMAgent.cpp
Source/WebCore/inspector/agents/InspectorDOMAgent.h
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Views/DOMNodeDetailsSidebarPanel.js
Source/WebInspectorUI/UserInterface/Views/DebuggerSidebarPanel.css
Source/WebInspectorUI/UserInterface/Views/DebuggerSidebarPanel.js
Source/WebInspectorUI/UserInterface/Views/EventListenerSectionGroup.js
Source/WebInspectorUI/UserInterface/Views/SourcesNavigationSidebarPanel.css
Source/WebInspectorUI/UserInterface/Views/SourcesNavigationSidebarPanel.js

index 9fabecf..5cf48e7 100644 (file)
@@ -1,5 +1,18 @@
 2019-03-20  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: DOM: include window as part of any event listener chain
+        https://bugs.webkit.org/show_bug.cgi?id=195730
+        <rdar://problem/48916872>
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/dom/getEventListenersForNode.html:
+        * inspector/dom/getEventListenersForNode-expected.txt:
+        * inspector/dom/setEventListenerDisabled.html:
+        * inspector/dom/event-listener-add-remove.html:
+
+2019-03-20  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: Debugger: virtualize the list of variables in the Scope sidebar
         https://bugs.webkit.org/show_bug.cgi?id=192648
         <rdar://problem/46800949>
index 87eac81..978619b 100644 (file)
@@ -53,10 +53,12 @@ function test() {
     }
 
     function logListeners(expectedCount) {
-        return DOMAgent.getEventListenersForNode(node.id).then((payload) => {
-            InspectorTest.expectEqual(payload.listeners.length, expectedCount, `There should be ${expectedCount} event listeners.`);
+        return DOMAgent.getEventListenersForNode(node.id).then(({listeners}) => {
+            listeners = listeners.filter((listener) => listener.nodeId === node.id);
 
-            for (let eventListener of payload.listeners)
+            InspectorTest.expectEqual(listeners.length, expectedCount, `There should be ${expectedCount} event listeners.`);
+
+            for (let eventListener of listeners)
                 InspectorTest.log(`  - "${eventListener.type}"`);
         });
     }
@@ -67,10 +69,12 @@ function test() {
         name: "DOM.eventListeners.Initial",
         description: "Test that the document has no event listeners.",
         test(resolve, reject) {
-            DOMAgent.getEventListenersForNode(node.id).then((payload) => {
-                InspectorTest.expectEqual(payload.listeners.length, 0, "There should be no event listeners.");
+            DOMAgent.getEventListenersForNode(node.id).then(({listeners}) => {
+                listeners = listeners.filter((listener) => listener.nodeId === node.id);
+
+                InspectorTest.expectEqual(listeners.length, 0, "There should be no event listeners.");
 
-                for (let eventListener of payload.listeners)
+                for (let eventListener of listeners)
                     InspectorTest.log(eventListener.type);
             }).then(resolve, reject);
         }
index 91194eb..d5ab0fb 100644 (file)
@@ -4,73 +4,85 @@ Testing DOMAgent.getEventListenersForNode.
 == Running test suite: DOM.getEventListenersForNode
 -- Running test case: DOM.getEventListenersForNode.Basic
 Event: A
-Node: body
+Target: body
 Capture: true
 Attribute: false
 Handler Name: bodyA
-PASS: The Event Listener has a source location.
+The Event Listener has a source location.
 
 Event: B
-Node: body
+Target: body
 Capture: true
 Attribute: false
-PASS: The Event Listener has a source location.
+The Event Listener has a source location.
 
 Event: E
-Node: div#x
+Target: div#x
 Capture: false
 Attribute: false
 Handler Name: ObjectEventHandler
-PASS: The Event Listener has a source location.
+The Event Listener has a source location.
 
 Event: D
-Node: div#x
+Target: div#x
 Capture: false
 Attribute: false
 Handler Name: handleEvent
-PASS: The Event Listener has a source location.
+The Event Listener has a source location.
 
 Event: C
-Node: div#x
+Target: div#x
 Capture: false
 Attribute: false
-PASS: The Event Listener has a source location.
+The Event Listener has a source location.
 
 Event: B
-Node: div#x
+Target: div#x
 Capture: false
 Attribute: false
 Handler Name: xB
 Once: true
-PASS: The Event Listener has a source location.
+The Event Listener has a source location.
 
 Event: A
-Node: div#x
+Target: div#x
 Capture: false
 Attribute: false
 Handler Name: xA
-PASS: The Event Listener has a source location.
+The Event Listener has a source location.
 
 Event: click
-Node: div#x
+Target: div#x
 Capture: false
 Attribute: true
 Handler Name: onclick
-PASS: The Event Listener has a source location.
+The Event Listener has a source location.
 
 Event: B
-Node: #document
+Target: #document
 Capture: false
 Attribute: false
 Passive: true
-PASS: The Event Listener has a source location.
+The Event Listener has a source location.
 
 Event: A
-Node: #document
+Target: #document
 Capture: false
 Attribute: false
 Handler Name: documentA
 Passive: true
-PASS: The Event Listener has a source location.
+The Event Listener has a source location.
+
+Event: load
+Target: window
+Capture: false
+Attribute: true
+Handler Name: onload
+The Event Listener has a source location.
+
+Event: error
+Target: window
+Capture: false
+Attribute: true
 
 
index 0d3e667..6028226 100644 (file)
@@ -19,11 +19,13 @@ function test() {
                 }
 
                 for (let eventListener of eventListeners) {
-                    let node = WI.domManager.nodeForId(eventListener.nodeId);
-
-
                     InspectorTest.log(`Event: ${eventListener.type}`);
-                    InspectorTest.log(`Node: ${node.displayName}`);
+                    if (eventListener.nodeId) {
+                        let node = WI.domManager.nodeForId(eventListener.nodeId);
+                        InspectorTest.log(`Target: ${node.displayName}`);
+                    }
+                    if (eventListener.onWindow)
+                        InspectorTest.log("Target: window");
                     InspectorTest.log(`Capture: ${eventListener.useCapture}`);
                     InspectorTest.log(`Attribute: ${eventListener.isAttribute}`);
 
@@ -33,8 +35,8 @@ function test() {
                         InspectorTest.log(`Passive: ${eventListener.passive}`);
                     if (eventListener.once)
                         InspectorTest.log(`Once: ${eventListener.once}`);
-
-                    InspectorTest.expectThat(eventListener.location, "The Event Listener has a source location.");
+                    if (eventListener.location)
+                        InspectorTest.log("The Event Listener has a source location.");
 
                     InspectorTest.log("");
                 }
index 97db0db..986bc82 100644 (file)
@@ -16,6 +16,7 @@ function test() {
 
     function logListener() {
         return DOMAgent.getEventListenersForNode(clickEventListener.nodeId).then(({listeners}) => {
+            listeners = listeners.filter((listener) => listener.nodeId === clickEventListener.nodeId);
             InspectorTest.assert(listeners.length === 1, "There should only be one event listener.");
             InspectorTest.assert(listeners[0].type === "click", `There event listener should be for "click".`);
             InspectorTest.log("Click event listener is " + (listeners[0].disabled ? "disabled" : "enabled") + ".");
index 66ec261..2ba4a0c 100644 (file)
@@ -1,5 +1,18 @@
 2019-03-20  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: DOM: include window as part of any event listener chain
+        https://bugs.webkit.org/show_bug.cgi?id=195730
+        <rdar://problem/48916872>
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/protocol/DOM.json:
+        Modify `DOM.getEventListenersForNode` to not save the handler object, as that was never
+        used by the frontend. Add an `onWindow` optional property to `DOM.EventListener` that is set
+        when the event listener was retrieved from the `window` object.
+
+2019-03-20  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: Runtime: lazily create the agent
         https://bugs.webkit.org/show_bug.cgi?id=195972
         <rdar://problem/49039655>
index 0edc68b..38ef776 100644 (file)
                 { "name": "type", "type": "string", "description": "<code>EventListener</code>'s type." },
                 { "name": "useCapture", "type": "boolean", "description": "<code>EventListener</code>'s useCapture." },
                 { "name": "isAttribute", "type": "boolean", "description": "<code>EventListener</code>'s isAttribute." },
-                { "name": "nodeId", "$ref": "NodeId", "description": "Target <code>DOMNode</code> id." },
+                { "name": "nodeId", "$ref": "NodeId", "optional": true, "description": "The target <code>DOMNode</code> id if the event listener is for a node." },
+                { "name": "onWindow", "type": "boolean", "optional": true, "description": "True if the event listener was added to the window." },
                 { "name": "location", "$ref": "Debugger.Location", "optional": true, "description": "Handler code location." },
                 { "name": "handlerName", "type": "string", "optional": true, "description": "Event handler function name." },
-                { "name": "handlerObject", "$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": "getEventListenersForNode",
             "description": "Returns event listeners relevant to the node.",
             "parameters": [
-                { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to get listeners for." },
-                { "name": "objectGroup", "type": "string", "optional": true, "description": "Symbolic group name for handler value. Handler value is not returned without this parameter specified." }
+                { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to get listeners for." }
             ],
             "returns": [
                 { "name": "listeners", "type": "array", "items": { "$ref": "EventListener"}, "description": "Array of relevant listeners." }
index c91cb80..4a70d5f 100644 (file)
@@ -1,5 +1,22 @@
 2019-03-20  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: DOM: include window as part of any event listener chain
+        https://bugs.webkit.org/show_bug.cgi?id=195730
+        <rdar://problem/48916872>
+
+        Reviewed by Timothy Hatcher.
+
+        Test: inspector/dom/getEventListenersForNode.html
+
+        * inspector/agents/InspectorDOMAgent.h:
+        (WebCore::EventListenerInfo::EventListenerInfo): Deleted.
+        * inspector/agents/InspectorDOMAgent.cpp:
+        (WebCore::InspectorDOMAgent::getEventListenersForNode):
+        (WebCore::InspectorDOMAgent::buildObjectForEventListener):
+        (WebCore::InspectorDOMAgent::getEventListeners): Deleted.
+
+2019-03-20  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: Runtime: lazily create the agent
         https://bugs.webkit.org/show_bug.cgi?id=195972
         <rdar://problem/49039655>
index 5ebd416..dd9afee 100644 (file)
@@ -876,14 +876,40 @@ void InspectorDOMAgent::getAssociatedDataForNode(ErrorString& errorString, int /
     errorString = "Not supported"_s;
 }
 
-void InspectorDOMAgent::getEventListenersForNode(ErrorString& errorString, int nodeId, const String* objectGroup, RefPtr<JSON::ArrayOf<Inspector::Protocol::DOM::EventListener>>& listenersArray)
+void InspectorDOMAgent::getEventListenersForNode(ErrorString& errorString, int nodeId, RefPtr<JSON::ArrayOf<Inspector::Protocol::DOM::EventListener>>& listenersArray)
 {
     listenersArray = JSON::ArrayOf<Inspector::Protocol::DOM::EventListener>::create();
-    Node* node = assertNode(errorString, nodeId);
+
+    auto* node = assertNode(errorString, nodeId);
     if (!node)
         return;
+
+    Vector<RefPtr<EventTarget>> ancestors;
+    ancestors.append(node);
+    for (auto* ancestor = node->parentOrShadowHostNode(); ancestor; ancestor = ancestor->parentOrShadowHostNode())
+        ancestors.append(ancestor);
+    if (auto* window = node->document().domWindow())
+        ancestors.append(window);
+
+    struct EventListenerInfo {
+        RefPtr<EventTarget> eventTarget;
+        const AtomicString eventType;
+        const EventListenerVector eventListeners;
+    };
+
     Vector<EventListenerInfo> eventInformation;
-    getEventListeners(node, eventInformation, true);
+    for (size_t i = ancestors.size(); i; --i) {
+        auto& ancestor = ancestors[i - 1];
+        for (auto& eventType : ancestor->eventTypes()) {
+            EventListenerVector filteredListeners;
+            for (auto& listener : ancestor->eventListeners(eventType)) {
+                if (listener->callback().type() == EventListener::JSEventListenerType)
+                    filteredListeners.append(listener);
+            }
+            if (!filteredListeners.isEmpty())
+                eventInformation.append({ ancestor, eventType, WTFMove(filteredListeners) });
+        }
+    }
 
     auto addListener = [&] (RegisteredEventListener& listener, const EventListenerInfo& info) {
         int identifier = 0;
@@ -891,7 +917,7 @@ void InspectorDOMAgent::getEventListenersForNode(ErrorString& errorString, int n
         bool hasBreakpoint = false;
 
         for (auto& inspectorEventListener : m_eventListenerEntries.values()) {
-            if (inspectorEventListener.matches(*info.node, info.eventType, listener.callback(), listener.useCapture())) {
+            if (inspectorEventListener.matches(*info.eventTarget, info.eventType, listener.callback(), listener.useCapture())) {
                 identifier = inspectorEventListener.identifier;
                 disabled = inspectorEventListener.disabled;
                 hasBreakpoint = inspectorEventListener.hasBreakpoint;
@@ -900,7 +926,7 @@ void InspectorDOMAgent::getEventListenersForNode(ErrorString& errorString, int n
         }
 
         if (!identifier) {
-            InspectorEventListener inspectorEventListener(m_lastEventListenerId++, *info.node, info.eventType, listener.callback(), listener.useCapture());
+            InspectorEventListener inspectorEventListener(m_lastEventListenerId++, *info.eventTarget, info.eventType, listener.callback(), listener.useCapture());
 
             identifier = inspectorEventListener.identifier;
             disabled = inspectorEventListener.disabled;
@@ -909,13 +935,13 @@ void InspectorDOMAgent::getEventListenersForNode(ErrorString& errorString, int n
             m_eventListenerEntries.add(identifier, inspectorEventListener);
         }
 
-        listenersArray->addItem(buildObjectForEventListener(listener, identifier, info.eventType, info.node, objectGroup, disabled, hasBreakpoint));
+        listenersArray->addItem(buildObjectForEventListener(listener, identifier, *info.eventTarget, info.eventType, disabled, hasBreakpoint));
     };
 
     // Get Capturing Listeners (in this order)
     size_t eventInformationLength = eventInformation.size();
     for (auto& info : eventInformation) {
-        for (auto& listener : info.eventListenerVector) {
+        for (auto& listener : info.eventListeners) {
             if (listener->useCapture())
                 addListener(*listener, info);
         }
@@ -924,45 +950,13 @@ void InspectorDOMAgent::getEventListenersForNode(ErrorString& errorString, int n
     // Get Bubbling Listeners (reverse order)
     for (size_t i = eventInformationLength; i; --i) {
         const EventListenerInfo& info = eventInformation[i - 1];
-        for (auto& listener : info.eventListenerVector) {
+        for (auto& listener : info.eventListeners) {
             if (!listener->useCapture())
                 addListener(*listener, info);
         }
     }
 }
 
-void InspectorDOMAgent::getEventListeners(Node* node, Vector<EventListenerInfo>& eventInformation, bool includeAncestors)
-{
-    // The Node's Ancestors including self.
-    Vector<Node*> ancestors;
-    // Push this node as the firs element.
-    ancestors.append(node);
-    if (includeAncestors) {
-        for (ContainerNode* ancestor = node->parentOrShadowHostNode(); ancestor; ancestor = ancestor->parentOrShadowHostNode())
-            ancestors.append(ancestor);
-    }
-
-    // Nodes and their Listeners for the concerned event types (order is top to bottom)
-    for (size_t i = ancestors.size(); i; --i) {
-        Node* ancestor = ancestors[i - 1];
-        EventTargetData* d = ancestor->eventTargetData();
-        if (!d)
-            continue;
-        // Get the list of event types this Node is concerned with
-        for (auto& type : d->eventListenerMap.eventTypes()) {
-            auto& listeners = ancestor->eventListeners(type);
-            EventListenerVector filteredListeners;
-            filteredListeners.reserveInitialCapacity(listeners.size());
-            for (auto& listener : listeners) {
-                if (listener->callback().type() == EventListener::JSEventListenerType)
-                    filteredListeners.uncheckedAppend(listener);
-            }
-            if (!filteredListeners.isEmpty())
-                eventInformation.append(EventListenerInfo(ancestor, type, WTFMove(filteredListeners)));
-        }
-    }
-}
-
 void InspectorDOMAgent::setEventListenerDisabled(ErrorString& errorString, int eventListenerId, bool disabled)
 {
     auto it = m_eventListenerEntries.find(eventListenerId);
@@ -1660,13 +1654,10 @@ RefPtr<JSON::ArrayOf<Inspector::Protocol::DOM::Node>> InspectorDOMAgent::buildAr
     return pseudoElements;
 }
 
-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<Inspector::Protocol::DOM::EventListener> InspectorDOMAgent::buildObjectForEventListener(const RegisteredEventListener& registeredEventListener, int identifier, EventTarget& eventTarget, const AtomicString& eventType, bool disabled, bool hasBreakpoint)
 {
     Ref<EventListener> eventListener = registeredEventListener.callback();
 
-    JSC::ExecState* exec = nullptr;
-    JSC::JSObject* handlerObject = nullptr;
-    JSC::JSFunction* handlerFunction = nullptr;
     String handlerName;
     int lineNumber = 0;
     int columnNumber = 0;
@@ -1674,12 +1665,25 @@ Ref<Inspector::Protocol::DOM::EventListener> InspectorDOMAgent::buildObjectForEv
     if (is<JSEventListener>(eventListener.get())) {
         auto& scriptListener = downcast<JSEventListener>(eventListener.get());
 
+        Document* document = nullptr;
+        if (auto* scriptExecutionContext = eventTarget.scriptExecutionContext()) {
+            if (is<Document>(scriptExecutionContext))
+                document = downcast<Document>(scriptExecutionContext);
+        } else if (is<Node>(eventTarget))
+            document = &downcast<Node>(eventTarget).document();
+
+        JSC::JSObject* handlerObject = nullptr;
+        JSC::ExecState* exec = nullptr;
+
         JSC::JSLockHolder lock(scriptListener.isolatedWorld().vm());
 
-        exec = execStateFromNode(scriptListener.isolatedWorld(), &node->document());
-        handlerObject = scriptListener.jsFunction(node->document());
+        if (document) {
+            handlerObject = scriptListener.jsFunction(*document);
+            exec = execStateFromNode(scriptListener.isolatedWorld(), document);
+        }
+
         if (handlerObject && exec) {
-            handlerFunction = JSC::jsDynamicCast<JSC::JSFunction*>(exec->vm(), handlerObject);
+            JSC::JSFunction* handlerFunction = JSC::jsDynamicCast<JSC::JSFunction*>(exec->vm(), handlerObject);
 
             if (!handlerFunction) {
                 auto scope = DECLARE_CATCH_SCOPE(exec->vm());
@@ -1716,13 +1720,11 @@ Ref<Inspector::Protocol::DOM::EventListener> InspectorDOMAgent::buildObjectForEv
         .setType(eventType)
         .setUseCapture(registeredEventListener.useCapture())
         .setIsAttribute(eventListener->isAttribute())
-        .setNodeId(pushNodePathToFrontend(node))
         .release();
-    if (objectGroupId && handlerObject && exec) {
-        InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(exec);
-        if (!injectedScript.hasNoValue())
-            value->setHandlerObject(injectedScript.wrapObject(handlerObject, *objectGroupId));
-    }
+    if (is<Node>(eventTarget))
+        value->setNodeId(pushNodePathToFrontend(&downcast<Node>(eventTarget)));
+    else if (is<DOMWindow>(eventTarget))
+        value->setOnWindow(true);
     if (!scriptID.isNull()) {
         auto location = Inspector::Protocol::Debugger::Location::create()
             .setScriptId(scriptID)
index cc7e226..ff59b56 100644 (file)
@@ -77,19 +77,6 @@ struct HighlightConfig;
 
 typedef String ErrorString;
 
-struct EventListenerInfo {
-    EventListenerInfo(Node* node, const AtomicString& eventType, EventListenerVector&& eventListenerVector)
-        : node(node)
-        , eventType(eventType)
-        , eventListenerVector(WTFMove(eventListenerVector))
-    {
-    }
-
-    Node* node;
-    const AtomicString eventType;
-    const EventListenerVector eventListenerVector;
-};
-
 class InspectorDOMAgent final : public InspectorAgentBase, public Inspector::DOMBackendDispatcherHandler {
     WTF_MAKE_NONCOPYABLE(InspectorDOMAgent);
     WTF_MAKE_FAST_ALLOCATED;
@@ -129,7 +116,7 @@ public:
     void getSupportedEventNames(ErrorString&, RefPtr<JSON::ArrayOf<String>>& eventNames) override;
     void getDataBindingsForNode(ErrorString&, int nodeId, RefPtr<JSON::ArrayOf<Inspector::Protocol::DOM::DataBinding>>& dataArray) override;
     void getAssociatedDataForNode(ErrorString&, int nodeId, Optional<String>& associatedData) override;
-    void getEventListenersForNode(ErrorString&, int nodeId, const WTF::String* objectGroup, RefPtr<JSON::ArrayOf<Inspector::Protocol::DOM::EventListener>>& listenersArray) override;
+    void getEventListenersForNode(ErrorString&, int nodeId, 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;
@@ -156,9 +143,6 @@ public:
     void focus(ErrorString&, int nodeId) override;
     void setInspectedNode(ErrorString&, int nodeId) override;
 
-    void getEventListeners(Node*, Vector<EventListenerInfo>& listenersArray, bool includeAncestors);
-
-
     // InspectorInstrumentation
     int identifierForNode(Node&);
     void addEventListenersToNode(Node&);
@@ -248,7 +232,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, bool hasBreakpoint);
+    Ref<Inspector::Protocol::DOM::EventListener> buildObjectForEventListener(const RegisteredEventListener&, int identifier, EventTarget&, const AtomicString& eventType, bool disabled, bool hasBreakpoint);
     RefPtr<Inspector::Protocol::DOM::AccessibilityProperties> buildObjectForAccessibilityProperties(Node*);
     void processAccessibilityChildren(AccessibilityObject&, JSON::ArrayOf<int>&);
     
index 128a594..b40a53c 100644 (file)
@@ -1,5 +1,47 @@
 2019-03-20  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: DOM: include window as part of any event listener chain
+        https://bugs.webkit.org/show_bug.cgi?id=195730
+        <rdar://problem/48916872>
+
+        Reviewed by Timothy Hatcher.
+
+        Allow non-nodes (e.g. `window`) to be listed as the target of an event listener.
+        Add support for the same concept when showing breakpoint details after pausing on a specific
+        event listener in the Debugger/Sources navigation sidebar.
+
+        * UserInterface/Views/DOMNodeDetailsSidebarPanel.js:
+        (WI.DOMNodeDetailsSidebarPanel.prototype.initialLayout):
+        (WI.DOMNodeDetailsSidebarPanel.prototype._refreshEventListeners.generateGroupsByEvent):
+        (WI.DOMNodeDetailsSidebarPanel.prototype._refreshEventListeners.generateGroupsByTarget): Added.
+        (WI.DOMNodeDetailsSidebarPanel.prototype._refreshEventListeners.eventListenersCallback):
+        (WI.DOMNodeDetailsSidebarPanel.prototype._refreshEventListeners):
+        (WI.DOMNodeDetailsSidebarPanel.prototype._refreshEventListeners.generateGroupsByNode): Deleted.
+
+        * UserInterface/Views/EventListenerSectionGroup.js:
+        (WI.EventListenerSectionGroup.prototype._targetTextOrLink): Added.
+        (WI.EventListenerSectionGroup.prototype._nodeTextOrLink): Deleted.
+
+        * UserInterface/Views/DebuggerSidebarPanel.js:
+        (WI.DebuggerSidebarPanel.prototype._addBreakpoint):
+        (WI.DebuggerSidebarPanel.prototype._breakpointTreeOutlineDeleteTreeElement):
+        (WI.DebuggerSidebarPanel.prototype._treeSelectionDidChange):
+        (WI.DebuggerSidebarPanel.prototype._updatePauseReasonSection):
+        * UserInterface/Views/DebuggerSidebarPanel.css:
+        (.sidebar > .panel.navigation.debugger > .content > .breakpoints .tree-outline .item.event-target-window .icon): Added.
+
+        * UserInterface/Views/SourcesNavigationSidebarPanel.js:
+        (WI.SourcesNavigationSidebarPanel):
+        (WI.SourcesNavigationSidebarPanel.prototype._addBreakpoint):
+        (WI.SourcesNavigationSidebarPanel.prototype._updatePauseReasonSection):
+        (WI.SourcesNavigationSidebarPanel.prototype._handleTreeSelectionDidChange):
+        * UserInterface/Views/SourcesNavigationSidebarPanel.css:
+        (.sidebar > .panel.navigation.sources > .content > .breakpoints .tree-outline .item.event-target-window .icon): Added.
+
+        * Localizations/en.lproj/localizedStrings.js:
+
+2019-03-20  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: Debugger: virtualize the list of variables in the Scope sidebar
         https://bugs.webkit.org/show_bug.cgi?id=192648
         <rdar://problem/46800949>
index 1ab67e2..f066b06 100644 (file)
@@ -515,8 +515,8 @@ localizedStrings["Grammar"] = "Grammar";
 localizedStrings["Group"] = "Group";
 localizedStrings["Group Media Requests"] = "Group Media Requests";
 localizedStrings["Group by Event"] = "Group by Event";
-localizedStrings["Group by Node"] = "Group by Node";
 localizedStrings["Group by Path"] = "Group by Path";
+localizedStrings["Group by Target"] = "Group by Target";
 localizedStrings["Group by Type"] = "Group by Type";
 localizedStrings["Grouping Method"] = "Grouping Method";
 localizedStrings["Grouping Mode"] = "Grouping Mode";
@@ -1028,6 +1028,7 @@ localizedStrings["Tab width:"] = "Tab width:";
 localizedStrings["Tabs"] = "Tabs";
 localizedStrings["Tag"] = "Tag";
 localizedStrings["Take snapshot"] = "Take snapshot";
+localizedStrings["Target"] = "Target";
 localizedStrings["Template Content"] = "Template Content";
 localizedStrings["Text"] = "Text";
 localizedStrings["Text Frame"] = "Text Frame";
index b5e858c..89aec51 100644 (file)
@@ -90,7 +90,7 @@ WI.DOMNodeDetailsSidebarPanel = class DOMNodeDetailsSidebarPanel extends WI.DOMD
         }
 
         createOption(WI.UIString("Group by Event"), WI.DOMNodeDetailsSidebarPanel.EventListenerGroupingMethod.Event);
-        createOption(WI.UIString("Group by Node"), WI.DOMNodeDetailsSidebarPanel.EventListenerGroupingMethod.Node);
+        createOption(WI.UIString("Group by Target"), WI.DOMNodeDetailsSidebarPanel.EventListenerGroupingMethod.Target);
 
         eventListenersGroupMethodSelectElement.value = this._eventListenerGroupingMethodSetting.value;
 
@@ -341,6 +341,8 @@ WI.DOMNodeDetailsSidebarPanel = class DOMNodeDetailsSidebarPanel extends WI.DOMD
         if (!domNode)
             return;
 
+        const windowTargetIdentifier = Symbol("window");
+
         function createEventListenerSection(title, eventListeners, options = {}) {
             let groups = eventListeners.map((eventListener) => new WI.EventListenerSectionGroup(eventListener, options));
 
@@ -354,12 +356,13 @@ WI.DOMNodeDetailsSidebarPanel = class DOMNodeDetailsSidebarPanel extends WI.DOMD
         function generateGroupsByEvent(eventListeners) {
             let eventListenerTypes = new Map;
             for (let eventListener of eventListeners) {
-                eventListener.node = WI.domManager.nodeForId(eventListener.nodeId);
+                console.assert(eventListener.nodeId || eventListener.onWindow);
+                if (eventListener.nodeId)
+                    eventListener.node = WI.domManager.nodeForId(eventListener.nodeId);
 
                 let eventListenersForType = eventListenerTypes.get(eventListener.type);
                 if (!eventListenersForType)
                     eventListenerTypes.set(eventListener.type, eventListenersForType = []);
-
                 eventListenersForType.push(eventListener);
             }
 
@@ -373,33 +376,44 @@ WI.DOMNodeDetailsSidebarPanel = class DOMNodeDetailsSidebarPanel extends WI.DOMD
             return rows;
         }
 
-        function generateGroupsByNode(eventListeners) {
-            let eventListenerNodes = new Map;
+        function generateGroupsByTarget(eventListeners) {
+            let eventListenerTargets = new Map;
             for (let eventListener of eventListeners) {
-                eventListener.node = WI.domManager.nodeForId(eventListener.nodeId);
-
-                let eventListenersForNode = eventListenerNodes.get(eventListener.node);
-                if (!eventListenersForNode)
-                    eventListenerNodes.set(eventListener.node, eventListenersForNode = []);
-
-                eventListenersForNode.push(eventListener);
+                console.assert(eventListener.nodeId || eventListener.onWindow);
+                if (eventListener.nodeId)
+                    eventListener.node = WI.domManager.nodeForId(eventListener.nodeId);
+
+                let target = eventListener.onWindow ? windowTargetIdentifier : eventListener.node;
+                let eventListenersForTarget = eventListenerTargets.get(target);
+                if (!eventListenersForTarget)
+                    eventListenerTargets.set(target, eventListenersForTarget = []);
+                eventListenersForTarget.push(eventListener);
             }
 
             let rows = [];
 
-            let currentNode = domNode;
-            do {
-                let eventListenersForNode = eventListenerNodes.get(currentNode);
-                if (!eventListenersForNode)
-                    continue;
+            function generateSectionForTarget(target) {
+                let eventListenersForTarget = eventListenerTargets.get(target);
+                if (!eventListenersForTarget)
+                    return;
+
+                eventListenersForTarget.sort((a, b) => a.type.toLowerCase().extendedLocaleCompare(b.type.toLowerCase()));
 
-                eventListenersForNode.sort((a, b) => a.type.toLowerCase().extendedLocaleCompare(b.type.toLowerCase()));
+                let title = target === windowTargetIdentifier ? WI.unlocalizedString("window") : target.displayName;
 
-                let section = createEventListenerSection(currentNode.displayName, eventListenersForNode, {hideNode: true});
-                WI.bindInteractionsForNodeToElement(currentNode, section.titleElement, {ignoreClick: true});
+                let section = createEventListenerSection(title, eventListenersForTarget, {hideTarget: true});
+                if (target instanceof WI.DOMNode)
+                    WI.bindInteractionsForNodeToElement(target, section.titleElement, {ignoreClick: true});
                 rows.push(section);
+            }
+
+            let currentNode = domNode;
+            do {
+                generateSectionForTarget(currentNode);
             } while (currentNode = currentNode.parentNode);
 
+            generateSectionForTarget(windowTargetIdentifier);
+
             return rows;
         }
 
@@ -424,8 +438,8 @@ WI.DOMNodeDetailsSidebarPanel = class DOMNodeDetailsSidebarPanel extends WI.DOMD
                 this._eventListenersSectionGroup.rows = generateGroupsByEvent.call(this, eventListeners);
                 break;
 
-            case WI.DOMNodeDetailsSidebarPanel.EventListenerGroupingMethod.Node:
-                this._eventListenersSectionGroup.rows = generateGroupsByNode.call(this, eventListeners);
+            case WI.DOMNodeDetailsSidebarPanel.EventListenerGroupingMethod.Target:
+                this._eventListenersSectionGroup.rows = generateGroupsByTarget.call(this, eventListeners);
                 break;
             }
         }
@@ -950,5 +964,5 @@ WI.DOMNodeDetailsSidebarPanel = class DOMNodeDetailsSidebarPanel extends WI.DOMD
 
 WI.DOMNodeDetailsSidebarPanel.EventListenerGroupingMethod = {
     Event: "event",
-    Node: "node",
+    Target: "target",
 };
index c20aba1..da4ee0c 100644 (file)
     border-bottom: none;
 }
 
+.sidebar > .panel.navigation.debugger > .content > .breakpoints .tree-outline .item.event-target-window .icon {
+    content: url(../Images/TypeObject.svg);
+}
+
 @media (prefers-color-scheme: dark) {
     .sidebar > .panel.navigation.debugger .warning-banner {
         background-color: var(--yellow-highlight-background-color);
index d9a1ee8..dfb59de 100644 (file)
@@ -494,8 +494,23 @@ WI.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WI.NavigationSideba
         } else if (breakpoint instanceof WI.EventBreakpoint) {
             constructor = WI.EventBreakpointTreeElement;
 
-            if (breakpoint.eventListener)
-                parentTreeElement = getDOMNodeTreeElement(breakpoint.eventListener.node);
+            if (breakpoint.eventListener) {
+                let eventTargetTreeElement = null;
+                if (breakpoint.eventListener.onWindow) {
+                    if (!DebuggerSidebarPanel.__windowEventTargetRepresentedObject)
+                        DebuggerSidebarPanel.__windowEventTargetRepresentedObject = {__window: true};
+
+                    eventTargetTreeElement = this._breakpointsContentTreeOutline.findTreeElement(DebuggerSidebarPanel.__windowEventTargetRepresentedObject);
+                    if (!eventTargetTreeElement) {
+                        const subtitle = null;
+                        eventTargetTreeElement = new WI.GeneralTreeElement(["event-target-window"], WI.unlocalizedString("window"), subtitle, DebuggerSidebarPanel.__windowEventTargetRepresentedObject);
+                        this._addTreeElement(eventTargetTreeElement, parentTreeElement);
+                    }
+                } else if (breakpoint.eventListener.node)
+                    eventTargetTreeElement = getDOMNodeTreeElement(breakpoint.eventListener.node);
+                if (eventTargetTreeElement)
+                    parentTreeElement = eventTargetTreeElement;
+            }
         } else if (breakpoint instanceof WI.URLBreakpoint) {
             constructor = WI.URLBreakpointTreeElement;
 
@@ -899,6 +914,14 @@ WI.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WI.NavigationSideba
     _breakpointTreeOutlineDeleteTreeElement(treeElement)
     {
         console.assert(treeElement.selected);
+
+        if (treeElement.representedObject === DebuggerSidebarPanel.__windowEventTargetRepresentedObject) {
+            let eventBreakpointsOnWindow = WI.domManager.eventListenerBreakpoints.filter((eventBreakpoint) => eventBreakpoint.eventListener.onWindow);
+            for (let eventBreakpoint of eventBreakpointsOnWindow)
+                WI.domManager.removeBreakpointForEventListener(eventBreakpoint.eventListener);
+            return true;
+        }
+
         console.assert(treeElement instanceof WI.ResourceTreeElement || treeElement instanceof WI.ScriptTreeElement);
         if (!(treeElement instanceof WI.ResourceTreeElement) && !(treeElement instanceof WI.ScriptTreeElement))
             return false;
@@ -955,6 +978,9 @@ WI.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WI.NavigationSideba
             || treeElement instanceof WI.URLBreakpointTreeElement)
             return;
 
+        if (treeElement.representedObject === DebuggerSidebarPanel.__windowEventTargetRepresentedObject)
+            return;
+
         const options = {
             ignoreNetworkTab: true,
             ignoreSearchTab: true,
@@ -1231,8 +1257,13 @@ WI.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WI.NavigationSideba
             if (eventListener) {
                 console.assert(eventListener.eventListenerId === pauseData.eventListenerId);
 
-                let ownerElementRow = new WI.DetailsSectionSimpleRow(WI.UIString("Element"), WI.linkifyNodeReference(eventListener.node));
-                rows.push(ownerElementRow);
+                let value = null;
+                if (eventListener.onWindow)
+                    value = WI.unlocalizedString("window");
+                else if (eventListener.node)
+                    value = WI.linkifyNodeReference(eventListener.node);
+                if (value)
+                    rows.push(new WI.DetailsSectionSimpleRow(WI.UIString("Target"), value));
             }
 
             this._pauseReasonGroup.rows = rows;
index 894fed1..953c3e7 100644 (file)
@@ -34,8 +34,8 @@ WI.EventListenerSectionGroup = class EventListenerSectionGroup extends WI.Detail
         var rows = [];
         if (!options.hideType)
             rows.push(new WI.DetailsSectionSimpleRow(WI.UIString("Event"), this._eventListener.type));
-        if (!options.hideNode)
-            rows.push(new WI.DetailsSectionSimpleRow(WI.UIString("Node"), this._nodeTextOrLink()));
+        if (!options.hideTarget)
+            rows.push(new WI.DetailsSectionSimpleRow(WI.UIString("Target"), this._targetTextOrLink()));
         rows.push(new WI.DetailsSectionSimpleRow(WI.UIString("Function"), this._functionTextOrLink()));
 
         if (this._eventListener.useCapture)
@@ -65,17 +65,17 @@ WI.EventListenerSectionGroup = class EventListenerSectionGroup extends WI.Detail
 
     // Private
 
-    _nodeTextOrLink()
+    _targetTextOrLink()
     {
-        var node = this._eventListener.node;
-        console.assert(node);
-        if (!node)
-            return "";
+        if (this._eventListener.onWindow)
+            return WI.unlocalizedString("window");
 
-        if (node.nodeType() === Node.DOCUMENT_NODE)
-            return "document";
+        let node = this._eventListener.node;
+        if (node)
+            return WI.linkifyNodeReference(node);
 
-        return WI.linkifyNodeReference(node);
+        console.assert();
+        return "";
     }
 
     _functionTextOrLink()
index 6a4449f..ff8d7ce 100644 (file)
     }
 }
 
+.sidebar > .panel.navigation.sources > .content > .breakpoints .tree-outline .item.event-target-window .icon {
+    content: url(../Images/TypeObject.svg);
+}
+
 @media (prefers-dark-interface) {
     .sidebar > .panel.navigation.sources > .content > .warning-banner {
         color: var(--yellow-highlight-text-color);
index 0bba28a..011ea52 100644 (file)
@@ -142,6 +142,14 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
         this._breakpointsTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._handleTreeSelectionDidChange, this);
         this._breakpointsTreeOutline.ondelete = (treeElement) => {
             console.assert(treeElement.selected);
+
+            if (treeElement.representedObject === SourcesNavigationSidebarPanel.__windowEventTargetRepresentedObject) {
+                let eventBreakpointsOnWindow = WI.domManager.eventListenerBreakpoints.filter((eventBreakpoint) => eventBreakpoint.eventListener.onWindow);
+                for (let eventBreakpoint of eventBreakpointsOnWindow)
+                    WI.domManager.removeBreakpointForEventListener(eventBreakpoint.eventListener);
+                return true;
+            }
+
             console.assert(treeElement instanceof WI.ResourceTreeElement || treeElement instanceof WI.ScriptTreeElement);
             if (!(treeElement instanceof WI.ResourceTreeElement) && !(treeElement instanceof WI.ScriptTreeElement))
                 return false;
@@ -901,8 +909,23 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
         } else if (breakpoint instanceof WI.EventBreakpoint) {
             constructor = WI.EventBreakpointTreeElement;
 
-            if (breakpoint.eventListener)
-                parentTreeElement = getDOMNodeTreeElement(breakpoint.eventListener.node);
+            if (breakpoint.eventListener) {
+                let eventTargetTreeElement = null;
+                if (breakpoint.eventListener.onWindow) {
+                    if (!SourcesNavigationSidebarPanel.__windowEventTargetRepresentedObject)
+                        SourcesNavigationSidebarPanel.__windowEventTargetRepresentedObject = {__window: true};
+
+                    eventTargetTreeElement = this._breakpointsTreeOutline.findTreeElement(SourcesNavigationSidebarPanel.__windowEventTargetRepresentedObject);
+                    if (!eventTargetTreeElement) {
+                        const subtitle = null;
+                        eventTargetTreeElement = new WI.GeneralTreeElement(["event-target-window"], WI.unlocalizedString("window"), subtitle, SourcesNavigationSidebarPanel.__windowEventTargetRepresentedObject);
+                        this._insertDebuggerTreeElement(eventTargetTreeElement, parentTreeElement);
+                    }
+                } else if (breakpoint.eventListener.node)
+                    eventTargetTreeElement = getDOMNodeTreeElement(breakpoint.eventListener.node);
+                if (eventTargetTreeElement)
+                    parentTreeElement = eventTargetTreeElement;
+            }
         } else if (breakpoint instanceof WI.URLBreakpoint) {
             constructor = WI.URLBreakpointTreeElement;
 
@@ -1255,8 +1278,13 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
             if (eventListener) {
                 console.assert(eventListener.eventListenerId === pauseData.eventListenerId);
 
-                let ownerElementRow = new WI.DetailsSectionSimpleRow(WI.UIString("Element"), WI.linkifyNodeReference(eventListener.node));
-                rows.push(ownerElementRow);
+                let value = null;
+                if (eventListener.onWindow)
+                    value = WI.unlocalizedString("window");
+                else if (eventListener.node)
+                    value = WI.linkifyNodeReference(eventListener.node);
+                if (value)
+                    rows.push(new WI.DetailsSectionSimpleRow(WI.UIString("Target"), value));
             }
 
             this._pauseReasonGroup.rows = rows;
@@ -1388,6 +1416,9 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
             || treeElement instanceof WI.URLBreakpointTreeElement)
             return;
 
+        if (treeElement.representedObject === SourcesNavigationSidebarPanel.__windowEventTargetRepresentedObject)
+            return;
+
         if (treeElement instanceof WI.FolderTreeElement
             || treeElement instanceof WI.ResourceTreeElement
             || treeElement instanceof WI.ScriptTreeElement