Web Inspector: DOM: don't send the entire function string with each event listener
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Feb 2019 00:31:56 +0000 (00:31 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Feb 2019 00:31:56 +0000 (00:31 +0000)
https://bugs.webkit.org/show_bug.cgi?id=194293
<rdar://problem/47822809>

Reviewed by Joseph Pecoraro.

Source/JavaScriptCore:

* inspector/protocol/DOM.json:

* runtime/JSFunction.h:
Export `calculatedDisplayName`.

Source/WebCore:

Test: inspector/dom/getEventListenersForNode.html

* inspector/agents/InspectorDOMAgent.cpp:
(WebCore::InspectorDOMAgent::buildObjectForEventListener):

Source/WebInspectorUI:

* UserInterface/Views/EventListenerSectionGroup.js:
(WI.EventListenerSectionGroup.prototype._functionTextOrLink):

LayoutTests:

* inspector/dom/getEventListenersForNode.html:
* inspector/dom/getEventListenersForNode-expected.txt:

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

LayoutTests/ChangeLog
LayoutTests/inspector/dom/getEventListenersForNode-expected.txt
LayoutTests/inspector/dom/getEventListenersForNode.html
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/protocol/DOM.json
Source/JavaScriptCore/runtime/JSFunction.h
Source/WebCore/ChangeLog
Source/WebCore/inspector/agents/InspectorDOMAgent.cpp
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/Views/EventListenerSectionGroup.js

index d0fda69..3eade05 100644 (file)
@@ -1,3 +1,14 @@
+2019-02-06  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: DOM: don't send the entire function string with each event listener
+        https://bugs.webkit.org/show_bug.cgi?id=194293
+        <rdar://problem/47822809>
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/dom/getEventListenersForNode.html:
+        * inspector/dom/getEventListenersForNode-expected.txt:
+
 2019-02-06  Andy Estes  <aestes@apple.com>
 
         [Payment Request] It should be possible to require a phonetic name for shipping contacts
index 26b1225..91194eb 100644 (file)
@@ -3,34 +3,74 @@ Testing DOMAgent.getEventListenersForNode.
 
 == Running test suite: DOM.getEventListenersForNode
 -- Running test case: DOM.getEventListenersForNode.Basic
-Event: alpha
+Event: A
 Node: body
 Capture: true
 Attribute: false
+Handler Name: bodyA
 PASS: The Event Listener has a source location.
-PASS: The Event Listener has a function.
 
-Event: beta
+Event: B
+Node: body
+Capture: true
+Attribute: false
+PASS: The Event Listener has a source location.
+
+Event: E
+Node: div#x
+Capture: false
+Attribute: false
+Handler Name: ObjectEventHandler
+PASS: The Event Listener has a source location.
+
+Event: D
+Node: div#x
+Capture: false
+Attribute: false
+Handler Name: handleEvent
+PASS: The Event Listener has a source location.
+
+Event: C
 Node: div#x
 Capture: false
 Attribute: false
+PASS: The Event Listener has a source location.
+
+Event: B
+Node: div#x
+Capture: false
+Attribute: false
+Handler Name: xB
 Once: true
 PASS: The Event Listener has a source location.
-PASS: The Event Listener has a function.
 
-Event: alpha
+Event: A
 Node: div#x
 Capture: false
 Attribute: false
+Handler Name: xA
+PASS: The Event Listener has a source location.
+
+Event: click
+Node: div#x
+Capture: false
+Attribute: true
+Handler Name: onclick
+PASS: The Event Listener has a source location.
+
+Event: B
+Node: #document
+Capture: false
+Attribute: false
+Passive: true
 PASS: The Event Listener has a source location.
-PASS: The Event Listener has a function.
 
-Event: alpha
+Event: A
 Node: #document
 Capture: false
 Attribute: false
+Handler Name: documentA
 Passive: true
 PASS: The Event Listener has a source location.
-PASS: The Event Listener has a function.
 
 
index 697e532..0d3e667 100644 (file)
@@ -27,13 +27,14 @@ function test() {
                     InspectorTest.log(`Capture: ${eventListener.useCapture}`);
                     InspectorTest.log(`Attribute: ${eventListener.isAttribute}`);
 
+                    if (eventListener.handlerName)
+                        InspectorTest.log(`Handler Name: ${eventListener.handlerName}`);
                     if (eventListener.passive)
                         InspectorTest.log(`Passive: ${eventListener.passive}`);
                     if (eventListener.once)
                         InspectorTest.log(`Once: ${eventListener.once}`);
 
                     InspectorTest.expectThat(eventListener.location, "The Event Listener has a source location.");
-                    InspectorTest.expectThat(eventListener.handlerBody, "The Event Listener has a function.");
 
                     InspectorTest.log("");
                 }
@@ -62,15 +63,24 @@ function test() {
 <body onload="runTest()">
     <p>Testing DOMAgent.getEventListenersForNode.</p>
 
-    <div id="x"></div>
+    <div id="x" onclick="(function xClick(event) { })()"></div>
     <script>
+        class ObjectEventHandler {
+            handleEvent(event) { }
+        }
+
         let element = document.getElementById("x");
-        element.addEventListener("alpha", (event) => {});
-        element.addEventListener("beta", (event) => {}, {once: true});
+        element.addEventListener("A", function xA(event) { });
+        element.addEventListener("B", function xB(event) { }, {once: true});
+        element.addEventListener("C", (event) => { });
+        element.addEventListener("D", { handleEvent(event) { } });
+        element.addEventListener("E", new ObjectEventHandler);
 
-        document.body.addEventListener("alpha", (event) => {}, true);
+        document.body.addEventListener("A", function bodyA(event) { }, true);
+        document.body.addEventListener("B", (event) => {}, true);
 
-        document.addEventListener("alpha", (event) => {}, {passive: true});
+        document.addEventListener("A", function documentA(event) { }, {passive: true});
+        document.addEventListener("B", (event) => {}, {passive: true});
     </script>
 </body>
 </html>
index 74bab6f..9a9cc44 100644 (file)
@@ -1,3 +1,16 @@
+2019-02-06  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: DOM: don't send the entire function string with each event listener
+        https://bugs.webkit.org/show_bug.cgi?id=194293
+        <rdar://problem/47822809>
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/protocol/DOM.json:
+
+        * runtime/JSFunction.h:
+        Export `calculatedDisplayName`.
+
 2019-02-06  Yusuke Suzuki  <ysuzuki@apple.com>
 
         [JSC] PrivateName to PublicName hash table is wasteful
index 781609d..013c2bb 100644 (file)
                 { "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": "handlerBody", "type": "string", "description": "Event handler function body." },
                 { "name": "location", "$ref": "Debugger.Location", "optional": true, "description": "Handler code location." },
-                { "name": "sourceName", "type": "string", "optional": true, "description": "Source script URL." },
-                { "name": "handler", "$ref": "Runtime.RemoteObject", "optional": true, "description": "Event handler function value." },
+                { "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 },
index 3bf0676..242c8d6 100644 (file)
@@ -88,7 +88,7 @@ public:
 
     JS_EXPORT_PRIVATE String name(VM&);
     JS_EXPORT_PRIVATE String displayName(VM&);
-    const String calculatedDisplayName(VM&);
+    JS_EXPORT_PRIVATE const String calculatedDisplayName(VM&);
 
     ExecutableBase* executable() const { return m_executable.get(); }
 
index 1770e4b..b119adf 100644 (file)
@@ -1,3 +1,16 @@
+2019-02-06  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: DOM: don't send the entire function string with each event listener
+        https://bugs.webkit.org/show_bug.cgi?id=194293
+        <rdar://problem/47822809>
+
+        Reviewed by Joseph Pecoraro.
+
+        Test: inspector/dom/getEventListenersForNode.html
+
+        * inspector/agents/InspectorDOMAgent.cpp:
+        (WebCore::InspectorDOMAgent::buildObjectForEventListener):
+
 2019-02-06  Andy Estes  <aestes@apple.com>
 
         [Payment Request] It should be possible to require a phonetic name for shipping contacts
index 8797661..3536d78 100644 (file)
@@ -1663,28 +1663,48 @@ Ref<Inspector::Protocol::DOM::EventListener> InspectorDOMAgent::buildObjectForEv
 {
     Ref<EventListener> eventListener = registeredEventListener.callback();
 
-    JSC::ExecState* state = nullptr;
-    JSC::JSObject* handler = nullptr;
-    String body;
+    JSC::ExecState* exec = nullptr;
+    JSC::JSObject* handlerObject = nullptr;
+    JSC::JSFunction* handlerFunction = nullptr;
+    String handlerName;
     int lineNumber = 0;
     int columnNumber = 0;
     String scriptID;
-    String sourceName;
     if (is<JSEventListener>(eventListener.get())) {
         auto& scriptListener = downcast<JSEventListener>(eventListener.get());
+
         JSC::JSLockHolder lock(scriptListener.isolatedWorld().vm());
-        state = execStateFromNode(scriptListener.isolatedWorld(), &node->document());
-        handler = scriptListener.jsFunction(node->document());
-        if (handler && state) {
-            body = handler->toString(state)->value(state);
-            if (auto function = JSC::jsDynamicCast<JSC::JSFunction*>(state->vm(), handler)) {
-                if (!function->isHostOrBuiltinFunction()) {
-                    if (auto executable = function->jsExecutable()) {
-                        lineNumber = executable->firstLine() - 1;
-                        columnNumber = executable->startColumn() - 1;
-                        scriptID = executable->sourceID() == JSC::SourceProvider::nullID ? emptyString() : String::number(executable->sourceID());
-                        sourceName = executable->sourceURL();
-                    }
+
+        exec = execStateFromNode(scriptListener.isolatedWorld(), &node->document());
+        handlerObject = scriptListener.jsFunction(node->document());
+        if (handlerObject && exec) {
+            handlerFunction = JSC::jsDynamicCast<JSC::JSFunction*>(exec->vm(), handlerObject);
+
+            if (!handlerFunction) {
+                auto scope = DECLARE_CATCH_SCOPE(exec->vm());
+
+                // If the handler is not actually a function, see if it implements the EventListener interface and use that.
+                auto handleEventValue = handlerObject->get(exec, JSC::Identifier::fromString(exec, "handleEvent"));
+
+                if (UNLIKELY(scope.exception()))
+                    scope.clearException();
+
+                if (handleEventValue)
+                    handlerFunction = JSC::jsDynamicCast<JSC::JSFunction*>(exec->vm(), handleEventValue);
+            }
+
+            if (handlerFunction && !handlerFunction->isHostOrBuiltinFunction()) {
+                // If the listener implements the EventListener interface, use the class name instead of
+                // "handleEvent", unless it is a plain object.
+                if (handlerFunction != handlerObject)
+                    handlerName = JSC::JSObject::calculatedClassName(handlerObject);
+                if (handlerName.isEmpty() || handlerName == "Object"_s)
+                    handlerName = handlerFunction->calculatedDisplayName(exec->vm());
+
+                if (auto executable = handlerFunction->jsExecutable()) {
+                    lineNumber = executable->firstLine() - 1;
+                    columnNumber = executable->startColumn() - 1;
+                    scriptID = executable->sourceID() == JSC::SourceProvider::nullID ? emptyString() : String::number(executable->sourceID());
                 }
             }
         }
@@ -1696,12 +1716,11 @@ Ref<Inspector::Protocol::DOM::EventListener> InspectorDOMAgent::buildObjectForEv
         .setUseCapture(registeredEventListener.useCapture())
         .setIsAttribute(eventListener->isAttribute())
         .setNodeId(pushNodePathToFrontend(node))
-        .setHandlerBody(body)
         .release();
-    if (objectGroupId && handler && state) {
-        InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(state);
+    if (objectGroupId && handlerObject && exec) {
+        InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(exec);
         if (!injectedScript.hasNoValue())
-            value->setHandler(injectedScript.wrapObject(handler, *objectGroupId));
+            value->setHandlerObject(injectedScript.wrapObject(handlerObject, *objectGroupId));
     }
     if (!scriptID.isNull()) {
         auto location = Inspector::Protocol::Debugger::Location::create()
@@ -1710,9 +1729,9 @@ Ref<Inspector::Protocol::DOM::EventListener> InspectorDOMAgent::buildObjectForEv
             .release();
         location->setColumnNumber(columnNumber);
         value->setLocation(WTFMove(location));
-        if (!sourceName.isEmpty())
-            value->setSourceName(sourceName);
     }
+    if (!handlerName.isEmpty())
+        value->setHandlerName(handlerName);
     if (registeredEventListener.isPassive())
         value->setPassive(true);
     if (registeredEventListener.isOnce())
index 89bb7f2..278ff91 100644 (file)
@@ -1,3 +1,14 @@
+2019-02-06  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: DOM: don't send the entire function string with each event listener
+        https://bugs.webkit.org/show_bug.cgi?id=194293
+        <rdar://problem/47822809>
+
+        Reviewed by Joseph Pecoraro.
+
+        * UserInterface/Views/EventListenerSectionGroup.js:
+        (WI.EventListenerSectionGroup.prototype._functionTextOrLink):
+
 2019-02-06  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: "Worker not found" uncaught protocol errors
index 181d059..894fed1 100644 (file)
@@ -80,13 +80,19 @@ WI.EventListenerSectionGroup = class EventListenerSectionGroup extends WI.Detail
 
     _functionTextOrLink()
     {
-        var match = this._eventListener.handlerBody.match(/function ([^\(]+?)\(/);
-        if (match) {
-            var anonymous = false;
-            var functionName = match[1];
-        } else {
-            var anonymous = true;
-            var functionName = WI.UIString("(anonymous function)");
+        let anonymous = false;
+        let functionName = this._eventListener.handlerName;
+
+        // COMPATIBILITY (iOS 12.2): DOM.EventListener.handlerBody was replaced by DOM.EventListener.handlerName.
+        if (!functionName && this._eventListener.handlerBody) {
+            let match = this._eventListener.handlerBody.match(/function ([^\(]+?)\(/);
+            if (match)
+                functionName = match[1];
+        }
+
+        if (!functionName) {
+            anonymous = true;
+            functionName = WI.UIString("(anonymous function)");
         }
 
         if (!this._eventListener.location)