Web Inspector: show EventTarget listeners as an internal property
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 14 May 2020 02:36:41 +0000 (02:36 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 14 May 2020 02:36:41 +0000 (02:36 +0000)
https://bugs.webkit.org/show_bug.cgi?id=211766

Reviewed by Timothy Hatcher.

Source/WebCore:

Test: inspector/runtime/getProperties-internalProperties.html

Add a `listeners` internal property for `EventTarget` objects with the value
```
    listeners: {
        <event>: [
            {
                callback: <function>
                capture: <boolean>
                passive: <boolean>
                once: <boolean>
            }
            ...
        ]
        ...
    }
```
so long as at least one `JSEventListener` has been added prior to that point.

* inspector/WebInjectedScriptHost.cpp:
(WebCore::objectForEventTargetListeners): Added.
(WebCore::WebInjectedScriptHost::getInternalProperties):
Drive-by: only add the `name` internal property if the `Worker` actually has a name.
LayoutTests:

* inspector/runtime/getProperties-internalProperties.html: Added.
* inspector/runtime/getProperties-internalProperties-expected.txt: Added.

* inspector/debugger/tail-deleted-frames/tail-deleted-frames-this-value-expected.txt:
The `this` value of Global Code is the `window`, which is an `EventTarget`, so it's first
property should now be the `listeners` internal property.

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

LayoutTests/ChangeLog
LayoutTests/inspector/debugger/tail-deleted-frames/tail-deleted-frames-this-value-expected.txt
LayoutTests/inspector/runtime/getProperties-internalProperties-expected.txt [new file with mode: 0644]
LayoutTests/inspector/runtime/getProperties-internalProperties.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/inspector/WebInjectedScriptHost.cpp

index 9750aeb..5173340 100644 (file)
@@ -1,5 +1,19 @@
 2020-05-13  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: show EventTarget listeners as an internal property
+        https://bugs.webkit.org/show_bug.cgi?id=211766
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/runtime/getProperties-internalProperties.html: Added.
+        * inspector/runtime/getProperties-internalProperties-expected.txt: Added.
+
+        * inspector/debugger/tail-deleted-frames/tail-deleted-frames-this-value-expected.txt:
+        The `this` value of Global Code is the `window`, which is an `EventTarget`, so it's first
+        property should now be the `listeners` internal property.
+
+2020-05-13  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: rename CSS.StyleSheetOrigin.Regular to CSS.StyleSheetOrigin.Author to match the spec
         https://bugs.webkit.org/show_bug.cgi?id=211827
 
diff --git a/LayoutTests/inspector/runtime/getProperties-internalProperties-expected.txt b/LayoutTests/inspector/runtime/getProperties-internalProperties-expected.txt
new file mode 100644 (file)
index 0000000..31926cf
--- /dev/null
@@ -0,0 +1,27 @@
+Tests for the Runtime.getProperties command.
+
+
+== Running test suite: Runtime.getProperties.internalProperties
+-- Running test case: Runtime.getProperties.internalProperties.EventTarget
+Evaluating expression...
+Getting internal properties...
+
+  listeners: {
+    eventBubbleActiveMulti: [
+      0: {
+        callback: "function callbackBubbleActiveMulti() {}" (function)
+        capture: false (boolean)
+        passive: false (boolean)
+        once: false (boolean)
+      }
+    ]
+    eventCapturePassiveOnce: [
+      0: {
+        callback: "function callbackCapturePassiveOnce() {}" (function)
+        capture: true (boolean)
+        passive: true (boolean)
+        once: true (boolean)
+      }
+    ]
+  }
+
diff --git a/LayoutTests/inspector/runtime/getProperties-internalProperties.html b/LayoutTests/inspector/runtime/getProperties-internalProperties.html
new file mode 100644 (file)
index 0000000..e380e23
--- /dev/null
@@ -0,0 +1,89 @@
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/protocol-test.js"></script>
+<script src="resources/property-descriptor-utilities.js"></script>
+<script>
+if (window.internals)
+    window.internals.settings.setUnhandledPromiseRejectionToConsoleEnabled(false);
+
+function test()
+{
+    let suite = ProtocolTest.createAsyncSuite("Runtime.getProperties.internalProperties");
+
+    function addTestCase({name, expression}) {
+        suite.addTestCase({
+            name,
+            async test() {
+                ProtocolTest.log(`Evaluating expression...`);
+                let evaluateResponse = await InspectorProtocol.awaitCommand({
+                    method: "Runtime.evaluate",
+                    params: {expression},
+                });
+                InspectorProtocol.checkForError(evaluateResponse);
+                ProtocolTest.assert(!evaluateResponse.wasThrown);
+
+                ProtocolTest.log("Getting internal properties...");
+                let {internalProperties} = await InspectorProtocol.awaitCommand({
+                    method: "Runtime.getProperties",
+                    params: {
+                        objectId: evaluateResponse.result.objectId,
+                    },
+                });
+
+                ProtocolTest.newline();
+
+                async function dump({name, value}, indentSize) {
+                    if (name === "__proto__")
+                        return;
+
+                    let indentLog = " ".repeat(indentSize);
+                    let nameLog = name + ": ";
+
+                    if (value.type !== "object") {
+                        ProtocolTest.log(indentLog + nameLog + ProtocolTest.PropertyDescriptorUtilities.stringifyRemoteObject(value));
+                        return;
+                    }
+
+                    ProtocolTest.log(indentLog + nameLog + (value.subtype === "array" ? "[" : "{"));
+
+                    let {properties} = await InspectorProtocol.awaitCommand({
+                        method: "Runtime.getProperties",
+                        params: {
+                            objectId: value.objectId,
+                            ownProperties: true,
+                        },
+                    });
+                    for (let property of properties)
+                        await dump(property, indentSize + 2);
+
+                    ProtocolTest.log(indentLog + (value.subtype === "array" ? "]" : "}"));
+                }
+
+                for (let internalProperty of internalProperties)
+                    await dump(internalProperty, 2);
+            },
+        });
+    }
+
+    addTestCase({
+        name: "Runtime.getProperties.internalProperties.EventTarget",
+        expression: `(function() {
+let x = new EventTarget;
+x.addEventListener("eventBubbleActiveMulti", function callbackBubbleActiveMulti() {});
+x.addEventListener("eventCapturePassiveOnce", function callbackCapturePassiveOnce() {}, {
+    capture: true,
+    passive: true,
+    once: true,
+});
+return x;
+        })()`,
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onLoad="runTest()">
+<p>Tests for the Runtime.getProperties command.</p>
+</body>
+</html>
index 7eee8d7..1df35fc 100644 (file)
@@ -1,5 +1,36 @@
 2020-05-13  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: show EventTarget listeners as an internal property
+        https://bugs.webkit.org/show_bug.cgi?id=211766
+
+        Reviewed by Timothy Hatcher.
+
+        Test: inspector/runtime/getProperties-internalProperties.html
+
+        Add a `listeners` internal property for `EventTarget` objects with the value
+        ```
+            listeners: {
+                <event>: [
+                    {
+                        callback: <function>
+                        capture: <boolean>
+                        passive: <boolean>
+                        once: <boolean>
+                    }
+                    ...
+                ]
+                ...
+            }
+        ```
+        so long as at least one `JSEventListener` has been added prior to that point.
+
+        * inspector/WebInjectedScriptHost.cpp:
+        (WebCore::objectForEventTargetListeners): Added.
+        (WebCore::WebInjectedScriptHost::getInternalProperties):
+        Drive-by: only add the `name` internal property if the `Worker` actually has a name.
+
+2020-05-13  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: rename CSS.StyleSheetOrigin.Regular to CSS.StyleSheetOrigin.Author to match the spec
         https://bugs.webkit.org/show_bug.cgi?id=211827
 
index 09901d0..acb5fac 100644 (file)
@@ -28,6 +28,8 @@
 
 #include "DOMException.h"
 #include "JSDOMException.h"
+#include "JSEventListener.h"
+#include "JSEventTarget.h"
 #include "JSHTMLAllCollection.h"
 #include "JSHTMLCollection.h"
 #include "JSNode.h"
@@ -161,6 +163,48 @@ static JSString* jsStringForPaymentRequestState(VM& vm, PaymentRequest::State st
 }
 #endif
 
+static JSObject* objectForEventTargetListeners(VM& vm, JSGlobalObject* exec, EventTarget* eventTarget)
+{
+    auto* scriptExecutionContext = eventTarget->scriptExecutionContext();
+    if (!scriptExecutionContext)
+        return nullptr;
+
+    JSObject* listeners = nullptr;
+
+    for (auto& eventType : eventTarget->eventTypes()) {
+        unsigned listenersForEventIndex = 0;
+        auto* listenersForEvent = constructEmptyArray(exec, nullptr);
+
+        for (auto& eventListener : eventTarget->eventListeners(eventType)) {
+            if (!is<JSEventListener>(eventListener->callback()))
+                continue;
+
+            auto& jsListener = downcast<JSEventListener>(eventListener->callback());
+            if (&jsListener.isolatedWorld() != &currentWorld(*exec))
+                continue;
+
+            auto* jsFunction = jsListener.ensureJSFunction(*scriptExecutionContext);
+            if (!jsFunction)
+                continue;
+
+            auto* propertiesForListener = constructEmptyObject(exec);
+            propertiesForListener->putDirect(vm, Identifier::fromString(vm, "callback"), jsFunction);
+            propertiesForListener->putDirect(vm, Identifier::fromString(vm, "capture"), jsBoolean(eventListener->useCapture()));
+            propertiesForListener->putDirect(vm, Identifier::fromString(vm, "passive"), jsBoolean(eventListener->isPassive()));
+            propertiesForListener->putDirect(vm, Identifier::fromString(vm, "once"), jsBoolean(eventListener->isOnce()));
+            listenersForEvent->putDirectIndex(exec, listenersForEventIndex++, propertiesForListener);
+        }
+
+        if (listenersForEventIndex) {
+            if (!listeners)
+                listeners = constructEmptyObject(exec);
+            listeners->putDirect(vm, Identifier::fromString(vm, eventType), listenersForEvent);
+        }
+    }
+
+    return listeners;
+}
+
 JSValue WebInjectedScriptHost::getInternalProperties(VM& vm, JSGlobalObject* exec, JSC::JSValue value)
 {
     auto scope = DECLARE_THROW_SCOPE(vm);
@@ -168,8 +212,16 @@ JSValue WebInjectedScriptHost::getInternalProperties(VM& vm, JSGlobalObject* exe
     if (auto* worker = JSWorker::toWrapped(vm, value)) {
         unsigned index = 0;
         auto* array = constructEmptyArray(exec, nullptr);
-        array->putDirectIndex(exec, index++, constructInternalProperty(vm, exec, "name"_s, jsString(vm, worker->name())));
+
+        String name = worker->name();
+        if (!name.isEmpty())
+            array->putDirectIndex(exec, index++, constructInternalProperty(vm, exec, "name"_s, jsString(vm, name)));
+
         array->putDirectIndex(exec, index++, constructInternalProperty(vm, exec, "terminated"_s, jsBoolean(worker->wasTerminated())));
+
+        if (auto* listeners = objectForEventTargetListeners(vm, exec, worker))
+            array->putDirectIndex(exec, index++, constructInternalProperty(vm, exec, "listeners"_s, listeners));
+
         RETURN_IF_EXCEPTION(scope, { });
         return array;
     }
@@ -178,14 +230,30 @@ JSValue WebInjectedScriptHost::getInternalProperties(VM& vm, JSGlobalObject* exe
     if (PaymentRequest* paymentRequest = JSPaymentRequest::toWrapped(vm, value)) {
         unsigned index = 0;
         auto* array = constructEmptyArray(exec, nullptr);
+
         array->putDirectIndex(exec, index++, constructInternalProperty(vm, exec, "options"_s, objectForPaymentOptions(vm, exec, paymentRequest->paymentOptions())));
         array->putDirectIndex(exec, index++, constructInternalProperty(vm, exec, "details"_s, objectForPaymentDetails(vm, exec, paymentRequest->paymentDetails())));
         array->putDirectIndex(exec, index++, constructInternalProperty(vm, exec, "state"_s, jsStringForPaymentRequestState(vm, paymentRequest->state())));
+
+        if (auto* listeners = objectForEventTargetListeners(vm, exec, paymentRequest))
+            array->putDirectIndex(exec, index++, constructInternalProperty(vm, exec, "listeners"_s, listeners));
+
         RETURN_IF_EXCEPTION(scope, { });
         return array;
     }
 #endif
 
+    if (auto* eventTarget = JSEventTarget::toWrapped(vm, value)) {
+        unsigned index = 0;
+        auto* array = constructEmptyArray(exec, nullptr);
+
+        if (auto* listeners = objectForEventTargetListeners(vm, exec, eventTarget))
+            array->putDirectIndex(exec, index++, constructInternalProperty(vm, exec, "listeners"_s, listeners));
+
+        RETURN_IF_EXCEPTION(scope, { });
+        return array;
+    }
+
     return { };
 }