Web Inspector: display fullscreen enter/exit events in Timelines and Network node...
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 25 Oct 2018 22:59:29 +0000 (22:59 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 25 Oct 2018 22:59:29 +0000 (22:59 +0000)
https://bugs.webkit.org/show_bug.cgi?id=189874
<rdar://problem/44700000>

Reviewed by Joseph Pecoraro.

Source/JavaScriptCore:

* inspector/protocol/DOM.json:
Allow `data` to be passed to the frontend with `didFireEvent`.

Source/WebCore:

Updated existing test: http/tests/inspector/dom/didFireEvent.html

* inspector/agents/InspectorDOMAgent.h:
* inspector/agents/InspectorDOMAgent.cpp:
(WebCore::EventFiredCallback::handleEvent):
(WebCore::InspectorDOMAgent::didCreateFrontendAndBackend):
(WebCore::InspectorDOMAgent::addEventListenersToNode):
(WebCore::InspectorDOMAgent::discardBindings):
(WebCore::InspectorDOMAgent::eventDidResetAfterDispatch): Added.
Prevent the same event from being sent to the frontend more than once.

* dom/Event.cpp:
(WebCore::Event::resetAfterDispatch):

* dom/Document.cpp:
(WebCore::Document::Document):

* inspector/InspectorInstrumentation.h:
(WebCore::InspectorInstrumentation::eventDidResetAfterDispatch): Added.
* inspector/InspectorInstrumentation.cpp:
(WebCore::InspectorInstrumentation::eventDidResetAfterDispatchImpl): Added.

Source/WebInspectorUI:

* Localizations/en.lproj/localizedStrings.js:

* UserInterface/Protocol/DOMObserver.js:
(WI.DOMObserver.prototype.didFireEvent):
* UserInterface/Controllers/DOMManager.js:
(WI.DOMManager.prototype.didFireEvent):
Allow `data` to be passed to the frontend with `didFireEvent`.

* UserInterface/Models/DOMNode.js:
(WI.DOMNode):
(WI.DOMNode.getFullscreenDOMEvents): Added.
(WI.DOMNode.prototype.didFireEvent):
(WI.DOMNode.prototype._handleDOMNodeDidFireEvent): Added.
(WI.DOMNode.prototype._addDOMEvent):
(WI.DOMNode.prototype._shouldListenForEventListeners): Added.
If an event is fired on an ancestor of this node, also record that event in this node's
`domEvents`, including the `originator` node.

* UserInterface/Views/NetworkTableContentView.js:
(WI.NetworkTableContentView.prototype._populateWaterfallGraph):
* UserInterface/Views/NetworkTableContentView.css:
(.network-table :not(.header) .cell.waterfall .waterfall-container > .dom-fullscreen): Added.

* UserInterface/Views/DOMEventsBreakdownView.js:
(WI.DOMEventsBreakdownView.prototype.initialLayout):
(WI.DOMEventsBreakdownView.prototype._populateTable):
* UserInterface/Views/DOMEventsBreakdownView.css:
(.dom-events-breakdown .graph > .area.fullscreen): Added.
(.dom-events-breakdown .inherited > .name, .dom-events-breakdown .inherited > .graph > .point): Added.
(.dom-events-breakdown:not(.has-inherited) .originator): Added.

LayoutTests:

* http/tests/inspector/dom/didFireEvent-expected.txt:
* http/tests/inspector/dom/didFireEvent.html:

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

21 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/inspector/dom/didFireEvent-expected.txt
LayoutTests/http/tests/inspector/dom/didFireEvent.html
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/protocol/DOM.json
Source/WebCore/ChangeLog
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Event.cpp
Source/WebCore/inspector/InspectorInstrumentation.cpp
Source/WebCore/inspector/InspectorInstrumentation.h
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/Controllers/DOMManager.js
Source/WebInspectorUI/UserInterface/Models/DOMNode.js
Source/WebInspectorUI/UserInterface/Protocol/DOMObserver.js
Source/WebInspectorUI/UserInterface/Views/DOMEventsBreakdownView.css
Source/WebInspectorUI/UserInterface/Views/DOMEventsBreakdownView.js
Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.css
Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js

index 42ad3a1..6d3edee 100644 (file)
@@ -1,3 +1,14 @@
+2018-10-25  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: display fullscreen enter/exit events in Timelines and Network node waterfalls
+        https://bugs.webkit.org/show_bug.cgi?id=189874
+        <rdar://problem/44700000>
+
+        Reviewed by Joseph Pecoraro.
+
+        * http/tests/inspector/dom/didFireEvent-expected.txt:
+        * http/tests/inspector/dom/didFireEvent.html:
+
 2018-10-25  Alexey Proskuryakov  <ap@apple.com>
 
         https://bugs.webkit.org/show_bug.cgi?id=175597
index f279165..78b18ec 100644 (file)
@@ -3,8 +3,38 @@ Tests that listeners registered by InspectorDOMAgent::addEventListenersToNode ar
 
 
 == Running test suite: DOM.didFireEvent
--- Running test case: DOM.didFireEvent
+-- Running test case: DOM.didFireEvent.Basic
 Adding video source "resources/white.mp4"...
-PASS: Should recieve a "loadstart" event.
+PASS: Should receive a "loadstart" event.
 PASS: Event timestamp should be greater than 0.
 
+-- Running test case: DOM.didFireEvent.Fullscreen
+Entering fullscreen on #video...
+PASS: Should receive a "webkitfullscreenchange" event.
+PASS: Event timestamp should be greater than 0.
+PASS: Event should have data.
+PASS: Fullscreen should be true.
+Target: video#video
+Exiting fullscreen...
+PASS: Should receive a "webkitfullscreenchange" event.
+PASS: Event timestamp should be greater than 0.
+PASS: Event should have data.
+PASS: Fullscreen should be false.
+Target: video#video
+
+-- Running test case: DOM.didFireEvent.Inherited
+Entering fullscreen on #container...
+PASS: Should receive a "webkitfullscreenchange" event.
+PASS: Event timestamp should be greater than 0.
+PASS: Event should have data.
+PASS: Fullscreen should be true.
+Target: video#video
+Originator: div#container
+Exiting fullscreen...
+PASS: Should receive a "webkitfullscreenchange" event.
+PASS: Event timestamp should be greater than 0.
+PASS: Event should have data.
+PASS: Fullscreen should be false.
+Target: video#video
+Originator: div#container
+
index ab6b4ec..fb4936e 100644 (file)
@@ -11,14 +11,80 @@ function loadSource(url, type) {
     document.getElementById("video").appendChild(sourceElement);
 }
 
+function enterFullscreen(element) {
+    document.addEventListener("keydown", (event) => {
+        document.addEventListener("webkitfullscreenchange", (event) => {
+            console.assert(document.webkitFullscreenElement === element);
+
+            TestPage.dispatchEventToFrontend("TestPage-enteredFullscreen");
+        }, {once: true});
+
+        element.webkitRequestFullscreen();
+    }, {once: true});
+
+    if (window.testRunner) {
+        // DumpRenderTree changes the firstResponder to the WebInspector window when it opens.
+        // This refocuses the test page, ensuring it gets the event.
+        if (window.testRunner.setMainFrameIsFirstResponder)
+            window.testRunner.setMainFrameIsFirstResponder(true);
+
+        eventSender.keyDown(" ");
+    }
+}
+
+function exitFullscreen() {
+    document.addEventListener("webkitfullscreenchange", (event) => {
+        console.assert(!document.webkitFullscreenElement);
+
+        TestPage.dispatchEventToFrontend("TestPage-exitedFullscreen");
+    }, {once: true});
+
+    document.webkitExitFullscreen();
+}
+
 function test()
 {
+    InspectorTest.debug();
+
     let suite = InspectorTest.createAsyncSuite("DOM.didFireEvent");
 
     let videoNode = null;
 
+    function fullscreenTest(fullscreenElementId, resolve, reject) {
+        InspectorTest.awaitEvent("TestPage-exitedFullscreen")
+        .then(resolve, reject);
+
+        InspectorTest.awaitEvent("TestPage-enteredFullscreen")
+        .then((event) => {
+            InspectorTest.log("Exiting fullscreen...");
+            InspectorTest.evaluateInPage(`exitFullscreen()`).catch(reject);
+        });
+
+        let enabled = false;
+        let listener = videoNode.addEventListener(WI.DOMNode.Event.DidFireEvent, (event) => {
+            let {domEvent} = event.data;
+            if (domEvent.eventName !== "webkitfullscreenchange")
+                return;
+
+            InspectorTest.pass(`Should receive a "webkitfullscreenchange" event.`);
+            InspectorTest.expectGreaterThan(domEvent.timestamp, 0, "Event timestamp should be greater than 0.");
+            InspectorTest.expectThat(domEvent.data, "Event should have data.");
+            InspectorTest.expectNotEqual(domEvent.data.enabled, enabled, `Fullscreen should be ${!enabled}.`);
+            InspectorTest.log("Target: " + event.target.displayName);
+            if (domEvent.originator)
+                InspectorTest.log("Originator: " + domEvent.originator.displayName);
+
+            enabled = domEvent.data.enabled;
+            if (!enabled)
+                videoNode.removeEventListener(WI.DOMNode.Event.DidFireEvent, listener);
+        });
+
+        InspectorTest.log(`Entering fullscreen on #${fullscreenElementId}...`);
+        InspectorTest.evaluateInPage(`enterFullscreen(document.getElementById("${fullscreenElementId}"))`).catch(reject);
+    }
+
     suite.addTestCase({
-        name: "DOM.didFireEvent",
+        name: "DOM.didFireEvent.Basic",
         description: "Check that HTMLMediaElement events work.",
         test(resolve, reject) {
             const file = "white.mp4";
@@ -28,7 +94,7 @@ function test()
                 if (domEvent.eventName !== "loadstart")
                     return;
 
-                InspectorTest.pass(`Should recieve a "loadstart" event.`)
+                InspectorTest.pass(`Should receive a "loadstart" event.`)
                 InspectorTest.expectGreaterThan(domEvent.timestamp, 0, "Event timestamp should be greater than 0.");
 
                 videoNode.removeEventListener(WI.DOMNode.Event.DidFireEvent, listener);
@@ -40,6 +106,22 @@ function test()
         }
     });
 
+    suite.addTestCase({
+        name: "DOM.didFireEvent.Fullscreen",
+        description: "Check that fullscreen events work.",
+        test(resolve, reject) {
+            fullscreenTest("video", resolve, reject);
+        }
+    });
+
+    suite.addTestCase({
+        name: "DOM.didFireEvent.Inherited",
+        description: "Check that inherited events work.",
+        test(resolve, reject) {
+            fullscreenTest("container", resolve, reject);
+        }
+    });
+
     WI.domManager.requestDocument((documentNode) => {
         WI.domManager.querySelector(documentNode.id, "#video", (videoNodeId) => {
             videoNode = WI.domManager.nodeForId(videoNodeId);
@@ -56,6 +138,8 @@ function test()
 </head>
 <body onload="runTest()">
     <p>Tests that listeners registered by InspectorDOMAgent::addEventListenersToNode are working.</p>
-    <video id="video" muted autoplay></video>
+    <div id="container">
+        <video id="video" muted autoplay></video>
+    </div>
 </body>
 </html>
index 2e03823..aadbbff 100644 (file)
@@ -1,3 +1,14 @@
+2018-10-25  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: display fullscreen enter/exit events in Timelines and Network node waterfalls
+        https://bugs.webkit.org/show_bug.cgi?id=189874
+        <rdar://problem/44700000>
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/protocol/DOM.json:
+        Allow `data` to be passed to the frontend with `didFireEvent`.
+
 2018-10-25  Ross Kirsling  <ross.kirsling@sony.com>
 
         Cleanup: inline constexpr is redundant as constexpr implies inline
index 19203c4..c879186 100644 (file)
             "parameters": [
                 { "name": "nodeId", "$ref": "NodeId" },
                 { "name": "eventName", "type": "string" },
-                { "name": "timestamp", "$ref": "Network.Timestamp", "description": "Time when the event was fired" }
+                { "name": "timestamp", "$ref": "Network.Timestamp", "description": "Time when the event was fired" },
+                { "name": "data", "type": "object", "optional": true, "description": "Holds ancillary information about the event or its target." }
             ]
         }
     ]
index 096a3da..cdd6cb7 100644 (file)
@@ -1,3 +1,33 @@
+2018-10-25  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: display fullscreen enter/exit events in Timelines and Network node waterfalls
+        https://bugs.webkit.org/show_bug.cgi?id=189874
+        <rdar://problem/44700000>
+
+        Reviewed by Joseph Pecoraro.
+
+        Updated existing test: http/tests/inspector/dom/didFireEvent.html
+
+        * inspector/agents/InspectorDOMAgent.h:
+        * inspector/agents/InspectorDOMAgent.cpp:
+        (WebCore::EventFiredCallback::handleEvent):
+        (WebCore::InspectorDOMAgent::didCreateFrontendAndBackend):
+        (WebCore::InspectorDOMAgent::addEventListenersToNode):
+        (WebCore::InspectorDOMAgent::discardBindings):
+        (WebCore::InspectorDOMAgent::eventDidResetAfterDispatch): Added.
+        Prevent the same event from being sent to the frontend more than once.
+
+        * dom/Event.cpp:
+        (WebCore::Event::resetAfterDispatch):
+
+        * dom/Document.cpp:
+        (WebCore::Document::Document):
+
+        * inspector/InspectorInstrumentation.h:
+        (WebCore::InspectorInstrumentation::eventDidResetAfterDispatch): Added.
+        * inspector/InspectorInstrumentation.cpp:
+        (WebCore::InspectorInstrumentation::eventDidResetAfterDispatchImpl): Added.
+
 2018-10-25  Michael Catanzaro  <mcatanzaro@igalia.com>
 
         Unreviewed, silence a -Wreturn-type warning
index 4654c0e..7b4808b 100644 (file)
@@ -560,6 +560,8 @@ Document::Document(Frame* frame, const URL& url, unsigned documentClasses, unsig
 
     for (auto& nodeListAndCollectionCount : m_nodeListAndCollectionCounts)
         nodeListAndCollectionCount = 0;
+
+    InspectorInstrumentation::addEventListenersToNode(*this);
 }
 
 #if ENABLE(FULLSCREEN_API)
index 53062df..90baa97 100644 (file)
@@ -171,6 +171,8 @@ void Event::resetAfterDispatch()
     m_eventPhase = NONE;
     m_propagationStopped = false;
     m_immediatePropagationStopped = false;
+
+    InspectorInstrumentation::eventDidResetAfterDispatch(*this);
 }
 
 } // namespace WebCore
index a09c44e..1eb72cf 100644 (file)
@@ -429,6 +429,12 @@ void InspectorInstrumentation::didDispatchEventOnWindowImpl(const InspectorInstr
         timelineAgent->didDispatchEvent();
 }
 
+void InspectorInstrumentation::eventDidResetAfterDispatchImpl(InstrumentingAgents& instrumentingAgents, const Event& event)
+{
+    if (auto* domAgent = instrumentingAgents.inspectorDOMAgent())
+        domAgent->eventDidResetAfterDispatch(event);
+}
+
 InspectorInstrumentationCookie InspectorInstrumentation::willEvaluateScriptImpl(InstrumentingAgents& instrumentingAgents, Frame& frame, const String& url, int lineNumber)
 {
     int timelineAgentId = 0;
index 510d08f..d91d0c2 100644 (file)
@@ -157,6 +157,7 @@ public:
     static void didHandleEvent(ScriptExecutionContext&);
     static InspectorInstrumentationCookie willDispatchEventOnWindow(Frame*, const Event&, DOMWindow&);
     static void didDispatchEventOnWindow(const InspectorInstrumentationCookie&);
+    static void eventDidResetAfterDispatch(const Event&);
     static InspectorInstrumentationCookie willEvaluateScript(Frame&, const String& url, int lineNumber);
     static void didEvaluateScript(const InspectorInstrumentationCookie&, Frame&);
     static InspectorInstrumentationCookie willFireTimer(ScriptExecutionContext&, int timerId, bool oneShot);
@@ -343,6 +344,7 @@ private:
     static void didDispatchEventImpl(const InspectorInstrumentationCookie&);
     static InspectorInstrumentationCookie willDispatchEventOnWindowImpl(InstrumentingAgents&, const Event&, DOMWindow&);
     static void didDispatchEventOnWindowImpl(const InspectorInstrumentationCookie&);
+    static void eventDidResetAfterDispatchImpl(InstrumentingAgents&, const Event&);
     static InspectorInstrumentationCookie willEvaluateScriptImpl(InstrumentingAgents&, Frame&, const String& url, int lineNumber);
     static void didEvaluateScriptImpl(const InspectorInstrumentationCookie&, Frame&);
     static InspectorInstrumentationCookie willFireTimerImpl(InstrumentingAgents&, int timerId, bool oneShot, ScriptExecutionContext&);
@@ -809,6 +811,18 @@ inline void InspectorInstrumentation::didDispatchEventOnWindow(const InspectorIn
         didDispatchEventOnWindowImpl(cookie);
 }
 
+inline void InspectorInstrumentation::eventDidResetAfterDispatch(const Event& event)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(void());
+
+    if (!is<Node>(event.target()))
+        return;
+
+    auto* node = downcast<Node>(event.target());
+    if (auto* instrumentingAgents = instrumentingAgentsForContext(node->scriptExecutionContext()))
+        return eventDidResetAfterDispatchImpl(*instrumentingAgents, event);
+}
+
 inline InspectorInstrumentationCookie InspectorInstrumentation::willEvaluateScript(Frame& frame, const String& url, int lineNumber)
 {
     FAST_RETURN_IF_NO_FRONTENDS(InspectorInstrumentationCookie());
index 10a97b8..1711db0 100644 (file)
@@ -234,7 +234,7 @@ public:
 
     void handleEvent(ScriptExecutionContext&, Event& event) final
     {
-        if (!is<Node>(event.target()))
+        if (!is<Node>(event.target()) || m_domAgent.m_dispatchedEvents.contains(&event))
             return;
 
         auto* node = downcast<Node>(event.target());
@@ -242,8 +242,15 @@ public:
         if (!nodeId)
             return;
 
+        m_domAgent.m_dispatchedEvents.add(&event);
+
+        RefPtr<JSON::Object> data = JSON::Object::create();
+
+        if (event.type() == eventNames().webkitfullscreenchangeEvent)
+            data->setBoolean("enabled"_s, !!node->document().webkitFullscreenElement());
+
         auto timestamp = m_domAgent.m_environment.executionStopwatch()->elapsedTime().seconds();
-        m_domAgent.m_frontendDispatcher->didFireEvent(nodeId, event.type(), timestamp);
+        m_domAgent.m_frontendDispatcher->didFireEvent(nodeId, event.type(), timestamp, data->size() ? WTFMove(data) : nullptr);
     }
 
 private:
@@ -290,6 +297,9 @@ void InspectorDOMAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*,
     m_instrumentingAgents.setInspectorDOMAgent(this);
     m_document = m_pageAgent->mainFrame().document();
 
+    if (m_document)
+        addEventListenersToNode(*m_document);
+
     for (auto* mediaElement : HTMLMediaElement::allMediaElements())
         addEventListenersToNode(*mediaElement);
 
@@ -523,6 +533,7 @@ void InspectorDOMAgent::discardBindings()
 {
     m_documentNodeToIdMap.clear();
     m_idToNode.clear();
+    m_dispatchedEvents.clear();
     m_eventListenerEntries.clear();
     releaseDanglingNodes();
     m_childrenRequested.clear();
@@ -2138,7 +2149,10 @@ void InspectorDOMAgent::addEventListenersToNode(Node& node)
         node.addEventListener(eventName, callback.copyRef(), false);
     };
 
-    if (is<HTMLMediaElement>(node)) {
+    if (is<Document>(node))
+        createEventListener(eventNames().webkitfullscreenchangeEvent);
+    else if (is<HTMLMediaElement>(node)) {
+        createEventListener(eventNames().webkitfullscreenchangeEvent);
         createEventListener(eventNames().abortEvent);
         createEventListener(eventNames().canplayEvent);
         createEventListener(eventNames().canplaythroughEvent);
@@ -2406,6 +2420,11 @@ bool InspectorDOMAgent::isEventListenerDisabled(EventTarget& target, const Atomi
     return false;
 }
 
+void InspectorDOMAgent::eventDidResetAfterDispatch(const Event& event)
+{
+    m_dispatchedEvents.remove(&event);
+}
+
 bool InspectorDOMAgent::hasBreakpointForEventListener(EventTarget& target, const AtomicString& eventType, EventListener& listener, bool capture)
 {
     for (auto& inspectorEventListener : m_eventListenerEntries.values()) {
index 6d500df..cc5acbd 100644 (file)
@@ -177,6 +177,7 @@ public:
     void didAddEventListener(EventTarget&);
     void willRemoveEventListener(EventTarget&, const AtomicString& eventType, EventListener&, bool capture);
     bool isEventListenerDisabled(EventTarget&, const AtomicString& eventType, EventListener&, bool capture);
+    void eventDidResetAfterDispatch(const Event&);
 
     // Callbacks that don't directly correspond to an instrumentation entry point.
     void setDocument(Document*);
@@ -319,6 +320,7 @@ private:
 
     friend class EventFiredCallback;
 
+    HashSet<const Event*> m_dispatchedEvents;
     HashMap<int, InspectorEventListener> m_eventListenerEntries;
     int m_lastEventListenerId { 1 };
 };
index 06a1bee..fe75aff 100644 (file)
@@ -1,5 +1,44 @@
 2018-10-25  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: display fullscreen enter/exit events in Timelines and Network node waterfalls
+        https://bugs.webkit.org/show_bug.cgi?id=189874
+        <rdar://problem/44700000>
+
+        Reviewed by Joseph Pecoraro.
+
+        * Localizations/en.lproj/localizedStrings.js:
+
+        * UserInterface/Protocol/DOMObserver.js:
+        (WI.DOMObserver.prototype.didFireEvent):
+        * UserInterface/Controllers/DOMManager.js:
+        (WI.DOMManager.prototype.didFireEvent):
+        Allow `data` to be passed to the frontend with `didFireEvent`.
+
+        * UserInterface/Models/DOMNode.js:
+        (WI.DOMNode):
+        (WI.DOMNode.getFullscreenDOMEvents): Added.
+        (WI.DOMNode.prototype.didFireEvent):
+        (WI.DOMNode.prototype._handleDOMNodeDidFireEvent): Added.
+        (WI.DOMNode.prototype._addDOMEvent):
+        (WI.DOMNode.prototype._shouldListenForEventListeners): Added.
+        If an event is fired on an ancestor of this node, also record that event in this node's
+        `domEvents`, including the `originator` node.
+
+        * UserInterface/Views/NetworkTableContentView.js:
+        (WI.NetworkTableContentView.prototype._populateWaterfallGraph):
+        * UserInterface/Views/NetworkTableContentView.css:
+        (.network-table :not(.header) .cell.waterfall .waterfall-container > .dom-fullscreen): Added.
+
+        * UserInterface/Views/DOMEventsBreakdownView.js:
+        (WI.DOMEventsBreakdownView.prototype.initialLayout):
+        (WI.DOMEventsBreakdownView.prototype._populateTable):
+        * UserInterface/Views/DOMEventsBreakdownView.css:
+        (.dom-events-breakdown .graph > .area.fullscreen): Added.
+        (.dom-events-breakdown .inherited > .name, .dom-events-breakdown .inherited > .graph > .point): Added.
+        (.dom-events-breakdown:not(.has-inherited) .originator): Added.
+
+2018-10-25  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: Network: more aggressively snap timing blocks together
         https://bugs.webkit.org/show_bug.cgi?id=190439
 
index 1e849d8..506b853 100644 (file)
@@ -404,6 +404,7 @@ localizedStrings["Frames"] = "Frames";
 localizedStrings["Frames %d \u2013 %d"] = "Frames %d \u2013 %d";
 localizedStrings["Full Garbage Collection"] = "Full Garbage Collection";
 localizedStrings["Full URL"] = "Full URL";
+localizedStrings["Fullscreen from “%s“"] = "Fullscreen from “%s“";
 localizedStrings["Function"] = "Function";
 localizedStrings["Function Name Variable"] = "Function Name Variable";
 localizedStrings["Garbage Collection"] = "Garbage Collection";
@@ -594,6 +595,7 @@ localizedStrings["Options"] = "Options";
 localizedStrings["Original"] = "Original";
 localizedStrings["Original formatting"] = "Original formatting";
 localizedStrings["Originally %s"] = "Originally %s";
+localizedStrings["Originator"] = "Originator";
 localizedStrings["Other"] = "Other";
 localizedStrings["Other Issue"] = "Other Issue";
 localizedStrings["Outgoing message"] = "Outgoing message";
index a3c58e2..26dc6e5 100644 (file)
@@ -122,7 +122,7 @@ WI.DOMManager = class DOMManager extends WI.Object
         node.dispatchEventToListeners(WI.DOMNode.Event.EventListenersChanged);
     }
 
-    didFireEvent(nodeId, eventName, timestamp)
+    didFireEvent(nodeId, eventName, timestamp, data)
     {
         // Called from WI.DOMObserver.
 
@@ -130,7 +130,7 @@ WI.DOMManager = class DOMManager extends WI.Object
         if (!node)
             return;
 
-        node.didFireEvent(eventName, timestamp);
+        node.didFireEvent(eventName, timestamp, data);
     }
 
     // Private
index 6bbdbac..7ead2de 100644 (file)
@@ -139,6 +139,20 @@ WI.DOMNode = class DOMNode extends WI.Object
         }
 
         this._domEvents = [];
+
+        if (this._shouldListenForEventListeners())
+            WI.DOMNode.addEventListener(WI.DOMNode.Event.DidFireEvent, this._handleDOMNodeDidFireEvent, this);
+    }
+
+    // Static
+
+    static getFullscreenDOMEvents(domEvents)
+    {
+        return domEvents.reduce((accumulator, current) => {
+            if (current.eventName === "webkitfullscreenchange" && current.data && (!accumulator.length || accumulator.lastValue.data.enabled !== current.data.enabled))
+                accumulator.push(current);
+            return accumulator;
+        }, []);
     }
 
     // Public
@@ -701,16 +715,28 @@ WI.DOMNode = class DOMNode extends WI.Object
         return !!this.ownerSVGElement;
     }
 
-    didFireEvent(eventName, timestamp)
+    didFireEvent(eventName, timestamp, data)
     {
         // Called from WI.DOMManager.
 
         this._addDOMEvent({
             eventName,
             timestamp: WI.timelineManager.computeElapsedTime(timestamp),
+            data,
         });
     }
 
+    _handleDOMNodeDidFireEvent(event)
+    {
+        if (event.target === this || !event.target.isAncestor(this))
+            return;
+
+        let domEvent = Object.shallowCopy(event.data.domEvent);
+        domEvent.originator = event.target;
+
+        this._addDOMEvent(domEvent);
+    }
+
     _addDOMEvent(domEvent)
     {
         this._domEvents.push(domEvent);
@@ -718,6 +744,12 @@ WI.DOMNode = class DOMNode extends WI.Object
         this.dispatchEventToListeners(WI.DOMNode.Event.DidFireEvent, {domEvent});
     }
 
+    _shouldListenForEventListeners()
+    {
+        let lowerCaseName = this.localName() || this.nodeName().toLowerCase();
+        return lowerCaseName === "video" || lowerCaseName === "audio";
+    }
+
     _setAttributesPayload(attrs)
     {
         this._attributes = [];
index b179231..1d94b92 100644 (file)
@@ -112,8 +112,8 @@ WI.DOMObserver = class DOMObserver
         WI.domManager.willRemoveEventListener(nodeId);
     }
 
-    didFireEvent(nodeId, eventName, timestamp)
+    didFireEvent(nodeId, eventName, timestamp, data)
     {
-        WI.domManager.didFireEvent(nodeId, eventName, timestamp);
+        WI.domManager.didFireEvent(nodeId, eventName, timestamp, data);
     }
 };
index 101ef5a..91b8ec2 100644 (file)
     border-radius: 50%;
 }
 
+.dom-events-breakdown .graph > .area.fullscreen {
+    top: 0;
+    height: 100%;
+    background-color: var(--panel-background-color);
+}
+
 .dom-events-breakdown .time {
     text-align: end;
 }
+
+.dom-events-breakdown .inherited > .name,
+.dom-events-breakdown .inherited > .graph > .point {
+    opacity: 0.5;
+}
+
+.dom-events-breakdown:not(.has-inherited) .originator {
+    display: none;
+}
index 513a95f..208f479 100644 (file)
@@ -69,6 +69,10 @@ WI.DOMEventsBreakdownView = class DOMEventsBreakdownView extends WI.View
         timeHeadCell.classList.add("time");
         timeHeadCell.textContent = WI.UIString("Time");
 
+        let originatorHeadCell = headRowElement.appendChild(document.createElement("th"));
+        originatorHeadCell.classList.add("originator");
+        originatorHeadCell.textContent = WI.UIString("Originator");
+
         this._tableBodyElement = tableElement.appendChild(document.createElement("tbody"));
 
         this._populateTable();
@@ -89,6 +93,18 @@ WI.DOMEventsBreakdownView = class DOMEventsBreakdownView extends WI.View
             return time / totalTime * 100;
         }
 
+        let fullscreenRanges = [];
+        let fullscreenDOMEvents = WI.DOMNode.getFullscreenDOMEvents(this._domEvents);
+        for (let fullscreenDOMEvent of fullscreenDOMEvents) {
+            let {enabled} = fullscreenDOMEvent.data;
+            if (enabled || !fullscreenRanges.length) {
+                fullscreenRanges.push({
+                    startTimestamp: enabled ? fullscreenDOMEvent.timestamp : startTimestamp,
+                });
+            }
+            fullscreenRanges.lastValue.endTimestamp = (enabled && fullscreenDOMEvent === fullscreenDOMEvents.lastValue) ? endTimestamp : fullscreenDOMEvent.timestamp;
+        }
+
         for (let domEvent of this._domEvents) {
             let rowElement = this._tableBodyElement.appendChild(document.createElement("tr"));
 
@@ -100,6 +116,14 @@ WI.DOMEventsBreakdownView = class DOMEventsBreakdownView extends WI.View
                 let graphCell = rowElement.appendChild(document.createElement("td"));
                 graphCell.classList.add("graph");
 
+                let fullscreenRange = fullscreenRanges.find((range) => domEvent.timestamp >= range.startTimestamp && domEvent.timestamp <= range.endTimestamp);
+                if (fullscreenRange) {
+                    let fullscreenArea = graphCell.appendChild(document.createElement("div"));
+                    fullscreenArea.classList.add("area", "fullscreen");
+                    fullscreenArea.style.setProperty(styleAttribute, percentOfTotalTime(fullscreenRange.startTimestamp - startTimestamp) + "%");
+                    fullscreenArea.style.setProperty("width", percentOfTotalTime(fullscreenRange.endTimestamp - fullscreenRange.startTimestamp) + "%");
+                }
+
                 let graphPoint = graphCell.appendChild(document.createElement("div"));
                 graphPoint.classList.add("point");
                 graphPoint.style.setProperty(styleAttribute, `calc(${percentOfTotalTime(domEvent.timestamp - startTimestamp)}% - (var(--point-size) / 2))`);
@@ -110,6 +134,15 @@ WI.DOMEventsBreakdownView = class DOMEventsBreakdownView extends WI.View
 
             const higherResolution = true;
             timeCell.textContent = Number.secondsToString(domEvent.timestamp - this._startTimestamp, higherResolution);
+
+            let originatorCell = rowElement.appendChild(document.createElement("td"));
+            originatorCell.classList.add("originator");
+            if (domEvent.originator) {
+                originatorCell.appendChild(WI.linkifyNodeReference(domEvent.originator));
+
+                rowElement.classList.add("inherited");
+                this.element.classList.add("has-inherited");
+            }
         }
     }
 };
index 9f5279c..7873fcc 100644 (file)
@@ -182,6 +182,16 @@ body[dir=rtl] .network-table .cell.name > .status {
     overflow: hidden;
 }
 
+.network-table :not(.header) .cell.waterfall .waterfall-container > .dom-fullscreen {
+    position: absolute;
+    top: var(--dom-fullscreen-vertical-padding);
+    height: calc(100% - (var(--dom-fullscreen-vertical-padding) * 2));
+    background-color: lightgrey;
+
+    /* Half of the vertical space above any .dom-event node */
+    --dom-fullscreen-vertical-padding: calc((50% - (var(--node-waterfall-dom-event-size) / 2)) / 2);
+}
+
 .network-table .timeline-ruler > .header {
     top: calc(var(--navigation-bar-height) - var(--timeline-ruler-height));
 }
index cf3269a..8ffa722 100644 (file)
@@ -660,17 +660,42 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         if (domNode) {
             const domEventElementSize = 8; // Keep this in sync with `--node-waterfall-dom-event-size`.
 
-            let groupedDOMEvents = domNode.domEvents.reduce((accumulator, current) => {
-                if (!accumulator.length || (current.timestamp - accumulator.lastValue.endTimestamp) >= (domEventElementSize * secondsPerPixel)) {
-                    accumulator.push({
-                        startTimestamp: current.timestamp,
+            let groupedDOMEvents = [];
+            for (let domEvent of domNode.domEvents) {
+                if (domEvent.originator)
+                    continue;
+
+                if (!groupedDOMEvents.length || (domEvent.timestamp - groupedDOMEvents.lastValue.endTimestamp) >= (domEventElementSize * secondsPerPixel)) {
+                    groupedDOMEvents.push({
+                        startTimestamp: domEvent.timestamp,
                         domEvents: [],
                     });
                 }
-                accumulator.lastValue.endTimestamp = current.timestamp;
-                accumulator.lastValue.domEvents.push(current);
-                return accumulator;
-            }, []);
+                groupedDOMEvents.lastValue.endTimestamp = domEvent.timestamp;
+                groupedDOMEvents.lastValue.domEvents.push(domEvent);
+            }
+
+            let fullscreenDOMEvents = WI.DOMNode.getFullscreenDOMEvents(domNode.domEvents);
+            if (fullscreenDOMEvents.length) {
+                if (!fullscreenDOMEvents[0].data.enabled)
+                    fullscreenDOMEvents.unshift({timestamp: graphStartTime});
+
+                if (fullscreenDOMEvents.lastValue.data.enabled)
+                    fullscreenDOMEvents.push({timestamp: this._waterfallEndTime});
+
+                console.assert((fullscreenDOMEvents.length % 2) === 0, "Every enter/exit of fullscreen should have a corresponding exit/enter.");
+
+                for (let i = 0; i < fullscreenDOMEvents.length; i += 2) {
+                    let fullscreenElement = container.appendChild(document.createElement("div"));
+                    fullscreenElement.classList.add("dom-fullscreen");
+                    positionByStartOffset(fullscreenElement, fullscreenDOMEvents[i].timestamp);
+                    setWidthForDuration(fullscreenElement, fullscreenDOMEvents[i].timestamp, fullscreenDOMEvents[i + 1].timestamp);
+
+                    let originator = fullscreenDOMEvents[i].originator || fullscreenDOMEvents[i + 1].originator;
+                    if (originator)
+                        fullscreenElement.title = WI.UIString("Fullscreen from “%s“").format(originator.displayName);
+                }
+            }
 
             let playing = false;