Web Inspector: display low-power enter/exit events in Timelines and Network node...
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 1 Nov 2018 04:12:59 +0000 (04:12 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 1 Nov 2018 04:12:59 +0000 (04:12 +0000)
https://bugs.webkit.org/show_bug.cgi?id=190641
<rdar://problem/45319049>

Reviewed by Joseph Pecoraro.

Source/JavaScriptCore:

* inspector/protocol/DOM.json:
Add `videoLowPowerChanged` event that is fired when `InspectorDOMAgent` is able to determine
whether a video element's low power state has changed.

Source/WebCore:

No new tests, as low power mode is indeterminate. Should not affect functionality.

* inspector/agents/InspectorDOMAgent.h:
* inspector/agents/InspectorDOMAgent.cpp:
(WebCore::InspectorDOMAgent::InspectorDOMAgent):
(WebCore::InspectorDOMAgent::addEventListenersToNode):
(WebCore::InspectorDOMAgent::mediaMetricsTimerFired): Added.

Source/WebInspectorUI:

* UserInterface/Protocol/DOMObserver.js:
(WI.DOMObserver.prototype.videoLowPowerChanged): Added.

* UserInterface/Controllers/DOMManager.js:
(WI.DOMManager.prototype.videoLowPowerChanged): Added.

* UserInterface/Models/DOMNode.js:
(WI.DOMNode):
(WI.DOMNode.prototype.get lowPowerRanges): Added.
(WI.DOMNode.prototype.videoLowPowerChanged): Added.
(WI.DOMNode.prototype.canEnterLowPowerMode): Added.

* UserInterface/Views/NetworkTableContentView.js:
(WI.NetworkTableContentView.prototype._populateDomainCell):
(WI.NetworkTableContentView.prototype._tryLinkResourceToDOMNode):
(WI.NetworkTableContentView.prototype._handleNodeLowPowerChanged): Added.
* UserInterface/Views/NetworkTableContentView.css:
(.network-table :not(.header) .cell.waterfall .waterfall-container > .area):
(.network-table :not(.header) .cell.waterfall .waterfall-container > .area.dom-fullscreen): Added.
(.network-table :not(.header) .cell.waterfall .waterfall-container > .area.low-power): Added.
(.network-table :not(.header) .cell.waterfall .waterfall-container > .dom-fullscreen): Deleted.

* UserInterface/Views/DOMNodeEventsContentView.js:
(WI.DOMNodeEventsContentView):
(WI.DOMNodeEventsContentView.prototype.initialLayout):
(WI.DOMNodeEventsContentView.prototype.closed): Deleted.
(WI.DOMNodeEventsContentView.prototype._handleDOMNodeDidFireEvent): Deleted.

* UserInterface/Views/DOMEventsBreakdownView.js:
(WI.DOMEventsBreakdownView):
(WI.DOMEventsBreakdownView.prototype.initialLayout):
(WI.DOMEventsBreakdownView.prototype.layout): Added.
(WI.DOMEventsBreakdownView.prototype._handleDOMNodeDidFireEvent): Added.
(WI.DOMEventsBreakdownView.prototype._handleDOMNodeLowPowerChanged): Added.
(WI.DOMEventsBreakdownView.prototype.addEvent): Deleted.
(WI.DOMEventsBreakdownView.prototype._populateTable.percentOfTotalTime): Deleted.
(WI.DOMEventsBreakdownView.prototype._populateTable): Deleted.
* UserInterface/Views/DOMEventsBreakdownView.css:
(.dom-events-breakdown .graph > .area): Added.
(.dom-events-breakdown .graph > .area.fullscreen):
(.dom-events-breakdown .graph > .area.low-power): Added.

* Localizations/en.lproj/localizedStrings.js:

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

15 files changed:
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/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/DOMNodeEventsContentView.js
Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.css
Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js

index b87fe99..87fd70e 100644 (file)
@@ -1,3 +1,15 @@
+2018-10-31  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: display low-power enter/exit events in Timelines and Network node waterfalls
+        https://bugs.webkit.org/show_bug.cgi?id=190641
+        <rdar://problem/45319049>
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/protocol/DOM.json:
+        Add `videoLowPowerChanged` event that is fired when `InspectorDOMAgent` is able to determine
+        whether a video element's low power state has changed.
+
 2018-10-31  Tadeu Zagallo  <tzagallo@apple.com>
 
         Adjust inlining threshold for new bytecode format
index c879186..0597d5f 100644 (file)
                 { "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." }
             ]
+        },
+        {
+            "name": "videoLowPowerChanged",
+            "description": "Called when a video element enters/exits low power mode.",
+            "parameters": [
+                { "name": "nodeId", "$ref": "NodeId" },
+                { "name": "timestamp", "$ref": "Network.Timestamp", "description": "Time when the video element entered/exited low power mode" },
+                { "name": "isLowPower", "type": "boolean" }
+            ]
         }
     ]
 }
index 7507550..069bc15 100644 (file)
@@ -1,3 +1,19 @@
+2018-10-31  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: display low-power enter/exit events in Timelines and Network node waterfalls
+        https://bugs.webkit.org/show_bug.cgi?id=190641
+        <rdar://problem/45319049>
+
+        Reviewed by Joseph Pecoraro.
+
+        No new tests, as low power mode is indeterminate. Should not affect functionality.
+
+        * inspector/agents/InspectorDOMAgent.h:
+        * inspector/agents/InspectorDOMAgent.cpp:
+        (WebCore::InspectorDOMAgent::InspectorDOMAgent):
+        (WebCore::InspectorDOMAgent::addEventListenersToNode):
+        (WebCore::InspectorDOMAgent::mediaMetricsTimerFired): Added.
+
 2018-10-31  Alicia Boya García  <aboya@igalia.com>
 
         [MSE] Use tolerance when growing the coded frame group
index a768580..a666311 100644 (file)
@@ -67,6 +67,7 @@
 #include "HTMLScriptElement.h"
 #include "HTMLStyleElement.h"
 #include "HTMLTemplateElement.h"
+#include "HTMLVideoElement.h"
 #include "HitTestResult.h"
 #include "InspectorClient.h"
 #include "InspectorController.h"
@@ -96,6 +97,7 @@
 #include "Text.h"
 #include "TextNodeTraversal.h"
 #include "Timer.h"
+#include "VideoPlaybackQuality.h"
 #include "WebInjectedScriptManager.h"
 #include "XPathResult.h"
 #include "markup.h"
@@ -282,6 +284,7 @@ InspectorDOMAgent::InspectorDOMAgent(WebAgentContext& context, InspectorPageAgen
     , m_backendDispatcher(Inspector::DOMBackendDispatcher::create(context.backendDispatcher, this))
     , m_pageAgent(pageAgent)
     , m_overlay(overlay)
+    , m_mediaMetricsTimer(*this, &InspectorDOMAgent::mediaMetricsTimerFired)
 {
 }
 
@@ -2178,6 +2181,9 @@ void InspectorDOMAgent::addEventListenersToNode(Node& node)
         createEventListener(eventNames().timeupdateEvent);
         createEventListener(eventNames().volumechangeEvent);
         createEventListener(eventNames().waitingEvent);
+
+        if (!m_mediaMetricsTimer.isActive())
+            m_mediaMetricsTimer.start(0_s, 1_s / 15.);
     }
 }
 
@@ -2447,6 +2453,49 @@ int InspectorDOMAgent::idForEventListener(EventTarget& target, const AtomicStrin
     return 0;
 }
 
+void InspectorDOMAgent::mediaMetricsTimerFired()
+{
+    // FIXME: remove metrics information for any media element when it's destroyed
+
+    if (HTMLMediaElement::allMediaElements().isEmpty()) {
+        if (m_mediaMetricsTimer.isActive())
+            m_mediaMetricsTimer.stop();
+        m_mediaMetrics.clear();
+        return;
+    }
+
+    for (auto* mediaElement : HTMLMediaElement::allMediaElements()) {
+        if (!is<HTMLVideoElement>(mediaElement) || !mediaElement->isPlaying())
+            continue;
+
+        auto videoPlaybackQuality = mediaElement->getVideoPlaybackQuality();
+        unsigned displayCompositedVideoFrames = videoPlaybackQuality->displayCompositedVideoFrames();
+
+        auto iterator = m_mediaMetrics.find(mediaElement);
+        if (iterator == m_mediaMetrics.end()) {
+            m_mediaMetrics.set(mediaElement, MediaMetrics(displayCompositedVideoFrames));
+            continue;
+        }
+
+        bool isLowPower = (displayCompositedVideoFrames - iterator->value.displayCompositedFrames) > 0;
+        if (iterator->value.isLowPower != isLowPower) {
+            iterator->value.isLowPower = isLowPower;
+
+            int nodeId = pushNodePathToFrontend(mediaElement);
+            if (nodeId) {
+                auto timestamp = m_environment.executionStopwatch()->elapsedTime().seconds();
+                m_frontendDispatcher->videoLowPowerChanged(nodeId, timestamp, iterator->value.isLowPower);
+            }
+        }
+
+        iterator->value.displayCompositedFrames = displayCompositedVideoFrames;
+    }
+
+    m_mediaMetrics.removeIf([&] (auto& entry) {
+        return !HTMLMediaElement::allMediaElements().contains(entry.key);
+    });
+}
+
 Node* InspectorDOMAgent::nodeForPath(const String& path)
 {
     // The path is of form "1,HTML,2,BODY,1,DIV"
index cc5acbd..9bb63d0 100644 (file)
@@ -31,6 +31,7 @@
 
 #include "EventTarget.h"
 #include "InspectorWebAgentBase.h"
+#include "Timer.h"
 #include <JavaScriptCore/InspectorBackendDispatchers.h>
 #include <JavaScriptCore/InspectorFrontendDispatchers.h>
 #include <wtf/HashMap.h>
@@ -63,6 +64,7 @@ class Frame;
 class InspectorHistory;
 class InspectorOverlay;
 class InspectorPageAgent;
+class HTMLMediaElement;
 class HitTestResult;
 class Node;
 class PseudoElement;
@@ -223,6 +225,8 @@ public:
     int idForEventListener(EventTarget&, const AtomicString& eventType, EventListener&, bool capture);
 
 private:
+    void mediaMetricsTimerFired();
+
     void highlightMousedOverNode();
     void setSearchingForNode(ErrorString&, bool enabled, const JSON::Object* highlightConfig);
     std::unique_ptr<HighlightConfig> highlightConfigFromInspectorObject(ErrorString&, const JSON::Object* highlightInspectorObject);
@@ -284,6 +288,22 @@ private:
     bool m_suppressAttributeModifiedEvent { false };
     bool m_documentRequested { false };
 
+    Timer m_mediaMetricsTimer;
+    struct MediaMetrics {
+        unsigned displayCompositedFrames { 0 };
+        bool isLowPower { false };
+
+        MediaMetrics() { }
+
+        MediaMetrics(unsigned displayCompositedFrames)
+            : displayCompositedFrames(displayCompositedFrames)
+        {
+        }
+    };
+
+    // The pointer key for this map should not be used for anything other than matching.
+    HashMap<HTMLMediaElement*, MediaMetrics> m_mediaMetrics;
+
     struct InspectorEventListener {
         int identifier { 1 };
         RefPtr<EventTarget> eventTarget;
index e95e00f..47d9ffb 100644 (file)
@@ -1,5 +1,57 @@
 2018-10-31  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: display low-power enter/exit events in Timelines and Network node waterfalls
+        https://bugs.webkit.org/show_bug.cgi?id=190641
+        <rdar://problem/45319049>
+
+        Reviewed by Joseph Pecoraro.
+
+        * UserInterface/Protocol/DOMObserver.js:
+        (WI.DOMObserver.prototype.videoLowPowerChanged): Added.
+
+        * UserInterface/Controllers/DOMManager.js:
+        (WI.DOMManager.prototype.videoLowPowerChanged): Added.
+
+        * UserInterface/Models/DOMNode.js:
+        (WI.DOMNode):
+        (WI.DOMNode.prototype.get lowPowerRanges): Added.
+        (WI.DOMNode.prototype.videoLowPowerChanged): Added.
+        (WI.DOMNode.prototype.canEnterLowPowerMode): Added.
+
+        * UserInterface/Views/NetworkTableContentView.js:
+        (WI.NetworkTableContentView.prototype._populateDomainCell):
+        (WI.NetworkTableContentView.prototype._tryLinkResourceToDOMNode):
+        (WI.NetworkTableContentView.prototype._handleNodeLowPowerChanged): Added.
+        * UserInterface/Views/NetworkTableContentView.css:
+        (.network-table :not(.header) .cell.waterfall .waterfall-container > .area):
+        (.network-table :not(.header) .cell.waterfall .waterfall-container > .area.dom-fullscreen): Added.
+        (.network-table :not(.header) .cell.waterfall .waterfall-container > .area.low-power): Added.
+        (.network-table :not(.header) .cell.waterfall .waterfall-container > .dom-fullscreen): Deleted.
+
+        * UserInterface/Views/DOMNodeEventsContentView.js:
+        (WI.DOMNodeEventsContentView):
+        (WI.DOMNodeEventsContentView.prototype.initialLayout):
+        (WI.DOMNodeEventsContentView.prototype.closed): Deleted.
+        (WI.DOMNodeEventsContentView.prototype._handleDOMNodeDidFireEvent): Deleted.
+
+        * UserInterface/Views/DOMEventsBreakdownView.js:
+        (WI.DOMEventsBreakdownView):
+        (WI.DOMEventsBreakdownView.prototype.initialLayout):
+        (WI.DOMEventsBreakdownView.prototype.layout): Added.
+        (WI.DOMEventsBreakdownView.prototype._handleDOMNodeDidFireEvent): Added.
+        (WI.DOMEventsBreakdownView.prototype._handleDOMNodeLowPowerChanged): Added.
+        (WI.DOMEventsBreakdownView.prototype.addEvent): Deleted.
+        (WI.DOMEventsBreakdownView.prototype._populateTable.percentOfTotalTime): Deleted.
+        (WI.DOMEventsBreakdownView.prototype._populateTable): Deleted.
+        * UserInterface/Views/DOMEventsBreakdownView.css:
+        (.dom-events-breakdown .graph > .area): Added.
+        (.dom-events-breakdown .graph > .area.fullscreen):
+        (.dom-events-breakdown .graph > .area.low-power): Added.
+
+        * Localizations/en.lproj/localizedStrings.js:
+
+2018-10-31  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: Audit: save imported audits across WebInspector sessions
         https://bugs.webkit.org/show_bug.cgi?id=190858
         <rdar://problem/45527625>
index 8bd0720..53a35dc 100644 (file)
@@ -422,6 +422,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"] = "Fullscreen";
 localizedStrings["Fullscreen from “%s“"] = "Fullscreen from “%s“";
 localizedStrings["Function"] = "Function";
 localizedStrings["Function Name Variable"] = "Function Name Variable";
@@ -535,6 +536,7 @@ localizedStrings["Log WebSocket"] = "Log WebSocket";
 localizedStrings["Log: "] = "Log: ";
 localizedStrings["Logs"] = "Logs";
 localizedStrings["Low"] = "Low";
+localizedStrings["Low Power Mode"] = "Low Power Mode";
 localizedStrings["Lowest: %s"] = "Lowest: %s";
 localizedStrings["MIME Type"] = "MIME Type";
 localizedStrings["MIME Type:"] = "MIME Type:";
index 5081a6b..8ecdf77 100644 (file)
@@ -147,6 +147,17 @@ WI.DOMManager = class DOMManager extends WI.Object
         node.didFireEvent(eventName, timestamp, data);
     }
 
+    videoLowPowerChanged(nodeId, timestamp, isLowPower)
+    {
+        // Called from WI.DOMObserver.
+
+        let node = this._idToDOMNode[nodeId];
+        if (!node)
+            return;
+
+        node.videoLowPowerChanged(timestamp, isLowPower);
+    }
+
     // Private
 
     _wrapClientCallback(callback)
index 02d938a..8583b37 100644 (file)
@@ -139,6 +139,7 @@ WI.DOMNode = class DOMNode extends WI.Object
         }
 
         this._domEvents = [];
+        this._lowPowerRanges = [];
 
         if (this._shouldListenForEventListeners())
             WI.DOMNode.addEventListener(WI.DOMNode.Event.DidFireEvent, this._handleDOMNodeDidFireEvent, this);
@@ -158,6 +159,7 @@ WI.DOMNode = class DOMNode extends WI.Object
     // Public
 
     get domEvents() { return this._domEvents; }
+    get lowPowerRanges() { return this._lowPowerRanges; }
 
     get frameIdentifier()
     {
@@ -726,6 +728,34 @@ WI.DOMNode = class DOMNode extends WI.Object
         });
     }
 
+    videoLowPowerChanged(timestamp, isLowPower)
+    {
+        // Called from WI.DOMManager.
+
+        console.assert(this.canEnterLowPowerMode());
+
+        let lastValue = this._lowPowerRanges.lastValue;
+
+        if (isLowPower) {
+            console.assert(!lastValue || lastValue.endTimestamp);
+            if (!lastValue || lastValue.endTimestamp)
+                this._lowPowerRanges.push({startTimestamp: timestamp});
+        } else {
+            console.assert(!lastValue || lastValue.startTimestamp);
+            if (!lastValue)
+                this._lowPowerRanges.push({endTimestamp: timestamp});
+            else if (lastValue.startTimestamp)
+                lastValue.endTimestamp = timestamp;
+        }
+
+        this.dispatchEventToListeners(WI.DOMNode.Event.LowPowerChanged, {isLowPower, timestamp});
+    }
+
+    canEnterLowPowerMode()
+    {
+        return this.localName() === "video" || this.nodeName().toLowerCase() === "video";
+    }
+
     _handleDOMNodeDidFireEvent(event)
     {
         if (event.target === this || !event.target.isAncestor(this))
@@ -898,6 +928,7 @@ WI.DOMNode.Event = {
     AttributeRemoved: "dom-node-attribute-removed",
     EventListenersChanged: "dom-node-event-listeners-changed",
     DidFireEvent: "dom-node-did-fire-event",
+    LowPowerChanged: "dom-node-video-low-power-changed",
 };
 
 WI.DOMNode.PseudoElementType = {
index 1d94b92..eeddb28 100644 (file)
@@ -116,4 +116,9 @@ WI.DOMObserver = class DOMObserver
     {
         WI.domManager.didFireEvent(nodeId, eventName, timestamp, data);
     }
+
+    videoLowPowerChanged(nodeId, timestamp, isLowPower)
+    {
+        WI.domManager.videoLowPowerChanged(nodeId, timestamp, isLowPower);
+    }
 };
index 91b8ec2..bc88db4 100644 (file)
     border-radius: 50%;
 }
 
-.dom-events-breakdown .graph > .area.fullscreen {
+.dom-events-breakdown .graph > .area {
     top: 0;
     height: 100%;
-    background-color: var(--panel-background-color);
+}
+
+.dom-events-breakdown .graph > .area.fullscreen {
+    background-color: hsla(0, 0%, 75%, 0.25);
+}
+
+.dom-events-breakdown .graph > .area.low-power {
+    background-color: hsla(83, 100%, 48%, 0.4);
 }
 
 .dom-events-breakdown .time {
index 208f479..1d642e6 100644 (file)
 
 WI.DOMEventsBreakdownView = class DOMEventsBreakdownView extends WI.View
 {
-    constructor(domEvents, {includeGraph, startTimestamp} = {})
+    constructor(domNodeOrEvents, {includeGraph, startTimestamp} = {})
     {
+        console.assert(domNodeOrEvents instanceof WI.DOMNode || Array.isArray(domNodeOrEvents));
+
         super();
 
-        this._domEvents = domEvents;
+        if (domNodeOrEvents instanceof WI.DOMNode) {
+            this._domNode = domNodeOrEvents;
+            this._domNode.addEventListener(WI.DOMNode.Event.DidFireEvent, this._handleDOMNodeDidFireEvent, this);
+            if (this._domNode.canEnterLowPowerMode())
+                this._domNode.addEventListener(WI.DOMNode.Event.LowPowerChanged, this._handleDOMNodeLowPowerChanged, this);
+
+            this._domEvents = null;
+        } else {
+            this._domNode = null;
+            this._domEvents = domNodeOrEvents;
+            this._lowPowerRanges = [];
+        }
+
         this._includeGraph = includeGraph || false;
         this._startTimestamp = startTimestamp || 0;
 
@@ -38,15 +52,6 @@ WI.DOMEventsBreakdownView = class DOMEventsBreakdownView extends WI.View
         this.element.classList.add("dom-events-breakdown");
     }
 
-    // Public
-
-    addEvent(domEvent)
-    {
-        this._domEvents.push(domEvent);
-
-        this.soon._populateTable();
-    }
-
     // Protected
 
     initialLayout()
@@ -74,18 +79,19 @@ WI.DOMEventsBreakdownView = class DOMEventsBreakdownView extends WI.View
         originatorHeadCell.textContent = WI.UIString("Originator");
 
         this._tableBodyElement = tableElement.appendChild(document.createElement("tbody"));
-
-        this._populateTable();
     }
 
-    // Private
-
-    _populateTable()
+    layout()
     {
+        if (this.layoutReason !== WI.View.LayoutReason.Dirty)
+            return;
+
         this._tableBodyElement.removeChildren();
 
-        let startTimestamp = this._domEvents[0].timestamp;
-        let endTimestamp = this._domEvents.lastValue.timestamp;
+        console.assert(this._domEvents || (this._domNode && this._domNode.domEvents));
+        let domEvents = this._domEvents || this._domNode.domEvents;
+        let startTimestamp = domEvents[0].timestamp;
+        let endTimestamp = domEvents.lastValue.timestamp;
         let totalTime = endTimestamp - startTimestamp;
         let styleAttribute = WI.resolvedLayoutDirection() === WI.LayoutDirection.LTR ? "left" : "right";
 
@@ -94,7 +100,7 @@ WI.DOMEventsBreakdownView = class DOMEventsBreakdownView extends WI.View
         }
 
         let fullscreenRanges = [];
-        let fullscreenDOMEvents = WI.DOMNode.getFullscreenDOMEvents(this._domEvents);
+        let fullscreenDOMEvents = WI.DOMNode.getFullscreenDOMEvents(domEvents);
         for (let fullscreenDOMEvent of fullscreenDOMEvents) {
             let {enabled} = fullscreenDOMEvent.data;
             if (enabled || !fullscreenRanges.length) {
@@ -103,9 +109,13 @@ WI.DOMEventsBreakdownView = class DOMEventsBreakdownView extends WI.View
                 });
             }
             fullscreenRanges.lastValue.endTimestamp = (enabled && fullscreenDOMEvent === fullscreenDOMEvents.lastValue) ? endTimestamp : fullscreenDOMEvent.timestamp;
+            if (fullscreenDOMEvent.originator)
+                fullscreenRanges.lastValue.originator = fullscreenDOMEvent.originator;
         }
 
-        for (let domEvent of this._domEvents) {
+        let lowPowerRanges = this._domNode ? this._domNode.lowPowerRanges : [];
+
+        for (let domEvent of domEvents) {
             let rowElement = this._tableBodyElement.appendChild(document.createElement("tr"));
 
             let nameCell = rowElement.appendChild(document.createElement("td"));
@@ -122,6 +132,20 @@ WI.DOMEventsBreakdownView = class DOMEventsBreakdownView extends WI.View
                     fullscreenArea.classList.add("area", "fullscreen");
                     fullscreenArea.style.setProperty(styleAttribute, percentOfTotalTime(fullscreenRange.startTimestamp - startTimestamp) + "%");
                     fullscreenArea.style.setProperty("width", percentOfTotalTime(fullscreenRange.endTimestamp - fullscreenRange.startTimestamp) + "%");
+
+                    if (fullscreenRange.originator)
+                        fullscreenArea.title = WI.UIString("Fullscreen from “%s“").format(fullscreenRange.originator.displayName);
+                    else
+                        fullscreenArea.title = WI.UIString("Fullscreen");
+                }
+
+                let lowPowerRange = lowPowerRanges.find((range) => domEvent.timestamp >= range.startTimestamp && domEvent.timestamp <= range.endTimestamp);
+                if (lowPowerRange) {
+                    let lowPowerArea = graphCell.appendChild(document.createElement("div"));
+                    lowPowerArea.classList.add("area", "low-power");
+                    lowPowerArea.title = WI.UIString("Low Power Mode");
+                    lowPowerArea.style.setProperty(styleAttribute, percentOfTotalTime(lowPowerRange.startTimestamp - startTimestamp) + "%");
+                    lowPowerArea.style.setProperty("width", percentOfTotalTime(lowPowerRange.endTimestamp - lowPowerRange.startTimestamp) + "%");
                 }
 
                 let graphPoint = graphCell.appendChild(document.createElement("div"));
@@ -145,4 +169,16 @@ WI.DOMEventsBreakdownView = class DOMEventsBreakdownView extends WI.View
             }
         }
     }
+
+    // Private
+
+    _handleDOMNodeDidFireEvent(event)
+    {
+        this.needsLayout();
+    }
+
+    _handleDOMNodeLowPowerChanged(event)
+    {
+        this.needsLayout();
+    }
 };
index 336a276..1950e85 100644 (file)
@@ -46,29 +46,10 @@ WI.DOMNodeEventsContentView = class DOMNodeEventsContentView extends WI.ContentV
     {
         super.initialLayout();
 
-        this._breakdownView = new WI.DOMEventsBreakdownView(this._domNode.domEvents.slice(), {
+        this._breakdownView = new WI.DOMEventsBreakdownView(this._domNode, {
             includeGraph: true,
             startTimestamp: this._startTimestamp,
         });
         this.addSubview(this._breakdownView);
-
-        this._domNode.addEventListener(WI.DOMNode.Event.DidFireEvent, this._handleDOMNodeDidFireEvent, this);
-    }
-
-    closed()
-    {
-        this._domNode.removeEventListener(null, null, this);
-
-        super.closed();
-    }
-
-    // Private
-
-    _handleDOMNodeDidFireEvent(event)
-    {
-        let {domEvent} = event.data;
-
-        if (this._breakdownView)
-            this._breakdownView.addEvent(domEvent);
     }
 };
index 565233f..84e9761 100644 (file)
@@ -175,6 +175,23 @@ body[dir=rtl] .network-table .cell.name > .status {
     border-top-style: solid;
 }
 
+.network-table :not(.header) .cell.waterfall .waterfall-container > .area {
+    position: absolute;
+    top: var(--area-padding);
+    height: calc(100% - (var(--area-padding) * 2));
+
+    /* Half of the vertical space above any .dom-event node */
+    --area-padding: calc((50% - (var(--node-waterfall-dom-event-size) / 2)) / 2);
+}
+
+.network-table :not(.header) .cell.waterfall .waterfall-container > .area.dom-fullscreen {
+    background-color: hsla(0, 0%, 75%, 0.75);
+}
+
+.network-table :not(.header) .cell.waterfall .waterfall-container > .area.low-power {
+    background-color: var(--network-request-color);
+}
+
 .network-table .timeline-ruler {
     position: absolute;
     top: 0;
@@ -182,16 +199,6 @@ 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 83237f4..58bc0e7 100644 (file)
@@ -542,7 +542,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
                 lockIconElement.className = "lock";
             }
 
-            cell.append(domain);
+            cell.append(domain || emDash);
         }
 
         let uniqueSchemeValues = this._uniqueValuesForDOMNodeEntry(entry, (resourceEntry) => resourceEntry.scheme);
@@ -558,11 +558,6 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
             return;
         }
 
-        if (!entry.domain) {
-            cell.textContent = emDash;
-            return;
-        }
-
         createIconAndText(entry.scheme, entry.domain);
     }
 
@@ -686,16 +681,29 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
                 for (let i = 0; i < fullscreenDOMEvents.length; i += 2) {
                     let fullscreenElement = container.appendChild(document.createElement("div"));
-                    fullscreenElement.classList.add("dom-fullscreen");
+                    fullscreenElement.classList.add("area", "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);
+                    else
+                        fullscreenElement.title = WI.UIString("Fullscreen");
                 }
             }
 
+            for (let lowPowerRange of domNode.lowPowerRanges) {
+                let startTimestamp = lowPowerRange.startTimestamp || graphStartTime;
+                let endTimestamp = lowPowerRange.endTimestamp || this._waterfallEndTime;
+
+                let lowPowerElement = container.appendChild(document.createElement("div"));
+                lowPowerElement.classList.add("area", "low-power");
+                lowPowerElement.title = WI.UIString("Low Power Mode");
+                positionByStartOffset(lowPowerElement, startTimestamp);
+                setWidthForDuration(lowPowerElement, startTimestamp, endTimestamp);
+            }
+
             let playing = false;
 
             function createDOMEventLine(domEvents, startTimestamp, endTimestamp) {
@@ -1558,6 +1566,8 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
             this._domNodeEntries.set(resource.initiatorNode, nodeEntry);
 
             resource.initiatorNode.addEventListener(WI.DOMNode.Event.DidFireEvent, this._handleNodeDidFireEvent, this);
+            if (resource.initiatorNode.canEnterLowPowerMode())
+                resource.initiatorNode.addEventListener(WI.DOMNode.Event.LowPowerChanged, this._handleNodeLowPowerChanged, this);
         }
 
         if (!this._entriesSortComparator)
@@ -1593,6 +1603,19 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         this.needsLayout();
     }
 
+    _handleNodeLowPowerChanged(event)
+    {
+        let domNode = event.target;
+        let {timestamp} = event.data;
+
+        this._pendingUpdates.push(domNode);
+
+        if (timestamp > this._waterfallEndTime)
+            this._waterfallEndTime = timestamp + (this._waterfallTimelineRuler.secondsPerPixel * 10);
+
+        this.needsLayout();
+    }
+
     _hasTypeFilter()
     {
         return !!this._activeTypeFilters;