Changeset 242562 in webkit


Ignore:
Timestamp:
Mar 6, 2019, 12:38:43 PM (6 years ago)
Author:
Joseph Pecoraro
Message:

Web Inspector: CPU Usage Timeline - Statistics and Sources sections
https://bugs.webkit.org/show_bug.cgi?id=195202

Reviewed by Devin Rousso.

Source/WebInspectorUI:

  • Localizations/en.lproj/localizedStrings.js:

New strings.

  • UserInterface/Base/Utilities.js:

(Map.prototype.getOrInitialize):
Helper to get and if not found initialize with a value.

  • UserInterface/Views/CPUTimelineView.css:

(.timeline-view.cpu > .content > .overview > .chart > .container.stats):
(.timeline-view.cpu > .content > .overview > .chart > .container.stats > table):
(.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > th):
(.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > td.number):
(.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > td.label):
(.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .show-more):
(.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter):
(.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter:hover):
(.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .active):
(.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .active + .active):
(@media (prefers-color-scheme: dark)):
Colors for the statistics sections.

  • UserInterface/Views/CPUTimelineView.js:

(WI.CPUTimelineView):
(WI.CPUTimelineView.prototype.reset):
(WI.CPUTimelineView.prototype.clear):
(WI.CPUTimelineView.prototype._clearStatistics):
(WI.CPUTimelineView.prototype._clearSources):
Updates for additional sections.
Include a cache of the statisiticsData so we can relayout parts of the UI and
avoid an entire UI update.

(WI.CPUTimelineView.prototype.initialLayout):
(WI.CPUTimelineView.prototype._layoutBreakdownChart):
(WI.CPUTimelineView.prototype._layoutStatisticsAndSources):
(WI.CPUTimelineView.prototype._layoutStatisticsSection.createEllipsisElement):
(WI.CPUTimelineView.prototype._layoutStatisticsSection):
(WI.CPUTimelineView.prototype._layoutSourcesSection.firstNonNativeCallFrame):
(WI.CPUTimelineView.prototype._layoutSourcesSection.keyForSourceCodeLocation):
(WI.CPUTimelineView.prototype._layoutSourcesSection.labelForLocation):
(WI.CPUTimelineView.prototype._layoutSourcesSection.createEllipsisElement):
(WI.CPUTimelineView.prototype._layoutSourcesSection):
Extract layouts into helper methods to avoid an enormous layout method.

(WI.CPUTimelineView.prototype._computeSamplingData.incrementTypeCount):
(WI.CPUTimelineView.prototype._computeSamplingData):
Compute additional data when going through script events.

(WI.CPUTimelineView.prototype._resetSourcesFilters):
(WI.CPUTimelineView.prototype._addSourcesFilter):
(WI.CPUTimelineView.prototype._removeSourcesFilter):
(WI.CPUTimelineView.prototype._updateSourcesFilters):
Helpers for updating the source filters.

(WI.CPUTimelineView.prototype._createTableRow):
(WI.CPUTimelineView.prototype._insertTableRow):
Helpers for creating rows in the statistics / sources tables.

LayoutTests:

  • inspector/unit-tests/map-utilities-expected.txt: Added.
  • inspector/unit-tests/map-utilities.html: Added.
  • inspector/unit-tests/set-utilities-expected.txt:
  • inspector/unit-tests/set-utilities.html:
Location:
trunk
Files:
2 added
11 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r242551 r242562  
     12019-03-06  Joseph Pecoraro  <pecoraro@apple.com>
     2
     3        Web Inspector: CPU Usage Timeline - Statistics and Sources sections
     4        https://bugs.webkit.org/show_bug.cgi?id=195202
     5
     6        Reviewed by Devin Rousso.
     7
     8        * inspector/unit-tests/map-utilities-expected.txt: Added.
     9        * inspector/unit-tests/map-utilities.html: Added.
     10        * inspector/unit-tests/set-utilities-expected.txt:
     11        * inspector/unit-tests/set-utilities.html:
     12
    1132019-03-06  Wenson Hsieh  <wenson_hsieh@apple.com>
    214
  • trunk/LayoutTests/inspector/unit-tests/set-utilities-expected.txt

    r241652 r242562  
    11
    2 == Running test suite: SetUtilities
     2== Running test suite: Set
    33-- Running test case: Set.prototype.intersects
    44PASS: an empty set should not intersect another empty set.
  • trunk/LayoutTests/inspector/unit-tests/set-utilities.html

    r241652 r242562  
    66function test()
    77{
    8     let suite = InspectorTest.createSyncSuite("SetUtilities");
     8    let suite = InspectorTest.createSyncSuite("Set");
    99
    1010    suite.addTestCase({
     
    2929            testTrue([1, "a", object1], [1, 3, "a", "c", object1, object3], "a set should intersect another set with same and additional values.");
    3030            testTrue([1, 2, "a", "b", object1, object2], [1, 3, "a", "c", object1, object3], "a set should intersect another set with same and different values.");
    31 
    32             return true;
    3331        }
    3432    });
     
    5553            testTrue([1, "a", object1], [1, 3, "a", "c", object1, object3], "a set should be a subset of another set with same and additional values.");
    5654            testFalse([1, 2, "a", "b", object1, object2], [1, 3, "a", "c", object1, object3], "a set should not be a subset of another set with same and different values.");
    57 
    58             return true;
    5955        }
    6056    });
     
    8076            testFalse([1, "a", object1], [2, "b", object2], "a set should not be a equal to another set with different values.");
    8177            testFalse([1, 2, "a", "b", object1, object2], [1, 3, "a", "c", object1, object3], "a set should not be equal to another set with same and different values.");
    82 
    83             return true;
    8478        }
    8579    });
     
    122116                expectedDifference: [1],
    123117            });
    124 
    125             return true;
    126118        }
    127119    });
  • trunk/Source/WebInspectorUI/ChangeLog

    r242560 r242562  
     12019-03-06  Joseph Pecoraro  <pecoraro@apple.com>
     2
     3        Web Inspector: CPU Usage Timeline - Statistics and Sources sections
     4        https://bugs.webkit.org/show_bug.cgi?id=195202
     5
     6        Reviewed by Devin Rousso.
     7
     8        * Localizations/en.lproj/localizedStrings.js:
     9        New strings.
     10
     11        * UserInterface/Base/Utilities.js:
     12        (Map.prototype.getOrInitialize):
     13        Helper to get and if not found initialize with a value.
     14
     15        * UserInterface/Views/CPUTimelineView.css:
     16        (.timeline-view.cpu > .content > .overview > .chart > .container.stats):
     17        (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table):
     18        (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > th):
     19        (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > td.number):
     20        (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > td.label):
     21        (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .show-more):
     22        (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter):
     23        (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter:hover):
     24        (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .active):
     25        (.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .active + .active):
     26        (@media (prefers-color-scheme: dark)):
     27        Colors for the statistics sections.
     28
     29        * UserInterface/Views/CPUTimelineView.js:
     30        (WI.CPUTimelineView):
     31        (WI.CPUTimelineView.prototype.reset):
     32        (WI.CPUTimelineView.prototype.clear):
     33        (WI.CPUTimelineView.prototype._clearStatistics):
     34        (WI.CPUTimelineView.prototype._clearSources):
     35        Updates for additional sections.
     36        Include a cache of the statisiticsData so we can relayout parts of the UI and
     37        avoid an entire UI update.
     38
     39        (WI.CPUTimelineView.prototype.initialLayout):
     40        (WI.CPUTimelineView.prototype._layoutBreakdownChart):
     41        (WI.CPUTimelineView.prototype._layoutStatisticsAndSources):
     42        (WI.CPUTimelineView.prototype._layoutStatisticsSection.createEllipsisElement):
     43        (WI.CPUTimelineView.prototype._layoutStatisticsSection):
     44        (WI.CPUTimelineView.prototype._layoutSourcesSection.firstNonNativeCallFrame):
     45        (WI.CPUTimelineView.prototype._layoutSourcesSection.keyForSourceCodeLocation):
     46        (WI.CPUTimelineView.prototype._layoutSourcesSection.labelForLocation):
     47        (WI.CPUTimelineView.prototype._layoutSourcesSection.createEllipsisElement):
     48        (WI.CPUTimelineView.prototype._layoutSourcesSection):
     49        Extract layouts into helper methods to avoid an enormous layout method.
     50
     51        (WI.CPUTimelineView.prototype._computeSamplingData.incrementTypeCount):
     52        (WI.CPUTimelineView.prototype._computeSamplingData):
     53        Compute additional data when going through script events.
     54
     55        (WI.CPUTimelineView.prototype._resetSourcesFilters):
     56        (WI.CPUTimelineView.prototype._addSourcesFilter):
     57        (WI.CPUTimelineView.prototype._removeSourcesFilter):
     58        (WI.CPUTimelineView.prototype._updateSourcesFilters):
     59        Helpers for updating the source filters.
     60
     61        (WI.CPUTimelineView.prototype._createTableRow):
     62        (WI.CPUTimelineView.prototype._insertTableRow):
     63        Helpers for creating rows in the statistics / sources tables.
     64
    1652019-03-06  Joseph Pecoraro  <pecoraro@apple.com>
    266
  • trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js

    r242556 r242562  
    123123localizedStrings["Anonymous StyleSheet %d"] = "Anonymous StyleSheet %d";
    124124localizedStrings["Application Cache"] = "Application Cache";
     125/* Approximate count of events */
     126localizedStrings["Approximate Number"] = "~%s";
    125127localizedStrings["Area"] = "Area";
    126128localizedStrings["Assertion"] = "Assertion";
     
    428430localizedStrings["Event Breakpoint\u2026"] = "Event Breakpoint\u2026";
    429431localizedStrings["Event Dispatched"] = "Event Dispatched";
     432localizedStrings["Event Handlers:"] = "Event Handlers:";
    430433localizedStrings["Event Listeners"] = "Event Listeners";
    431434localizedStrings["Events"] = "Events";
     435localizedStrings["Events:"] = "Events:";
    432436localizedStrings["Example: \u201C%s\u201D"] = "Example: \u201C%s\u201D";
    433437localizedStrings["Exception with thrown value: %s"] = "Exception with thrown value: %s";
     
    458462localizedStrings["Filter"] = "Filter";
    459463localizedStrings["Filter Full URL"] = "Filter Full URL";
     464localizedStrings["Filter:"] = "Filter:";
    460465localizedStrings["Find Next (%s)"] = "Find Next (%s)";
    461466localizedStrings["Find Previous (%s)"] = "Find Previous (%s)";
     
    687692localizedStrings["Object Store"] = "Object Store";
    688693localizedStrings["Observer Callback"] = "Observer Callback";
     694localizedStrings["Observer Handlers:"] = "Observer Handlers:";
     695localizedStrings["Observers:"] = "Observers:";
    689696localizedStrings["Off"] = "Off";
    690697localizedStrings["Once"] = "Once";
     
    854861localizedStrings["Script"] = "Script";
    855862localizedStrings["Script Element %d"] = "Script Element %d";
     863localizedStrings["Script Entries:"] = "Script Entries:";
    856864localizedStrings["Script Evaluated"] = "Script Evaluated";
    857865localizedStrings["Scripts"] = "Scripts";
     
    957965localizedStrings["Start to Finish"] = "Start to Finish";
    958966localizedStrings["State"] = "State";
     967localizedStrings["Statistics"] = "Statistics";
    959968localizedStrings["Status"] = "Status";
    960969localizedStrings["Step"] = "Step";
     
    10341043localizedStrings["Timer Installed"] = "Timer Installed";
    10351044localizedStrings["Timer Removed"] = "Timer Removed";
     1045localizedStrings["Timers:"] = "Timers:";
    10361046localizedStrings["Timestamp \u2014 %s"] = "Timestamp \u2014 %s";
    10371047localizedStrings["Timing"] = "Timing";
     
    10641074localizedStrings["Undefined custom element"] = "Undefined custom element";
    10651075localizedStrings["Unique"] = "Unique";
     1076localizedStrings["Unknown Location"] = "Unknown Location";
    10661077localizedStrings["Unknown error"] = "Unknown error";
    10671078localizedStrings["Unknown node"] = "Unknown node";
  • trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js

    r242174 r242562  
    113113    value(key)
    114114    {
    115         var deletedValue = this.get(key);
     115        let deletedValue = this.get(key);
    116116        this.delete(key);
    117117        return deletedValue;
     118    }
     119});
     120
     121Object.defineProperty(Map.prototype, "getOrInitialize",
     122{
     123    value(key, initialValue)
     124    {
     125        console.assert(initialValue !== undefined, "getOrInitialize should not be used with undefined.");
     126
     127        let value = this.get(key);
     128        if (value)
     129            return value;
     130
     131        this.set(key, initialValue);
     132        return initialValue;
    118133    }
    119134});
  • trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecord.js

    r240457 r242562  
    109109
    110110        // Return the first non-native code call frame as the initiator.
    111         for (var i = 0; i < this._callFrames.length; ++i) {
    112             if (this._callFrames[i].nativeCode)
    113                 continue;
    114             return this._callFrames[i];
     111        for (let frame of this._callFrames) {
     112            if (!frame.nativeCode)
     113                return frame;
    115114        }
    116115
  • trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css

    r242300 r242562  
    4646    height: 15px;
    4747    -webkit-margin-start: 7px;
    48     color: white;
    4948    font-size: 12px;
    50     background-color: darkgray;
     49    color: var(--gray-foreground-color);
     50    background-color: var(--gray-background-color);
    5151    border-radius: 50%;
    5252}
     
    319319}
    320320
     321.timeline-view.cpu > .content > .overview > .chart > .container.stats {
     322    padding: 0 5px;
     323    white-space: nowrap;
     324    -webkit-user-select: text;
     325}
     326
     327.timeline-view.cpu > .content > .overview > .chart > .container.stats > table {
     328    overflow: hidden;
     329}
     330
     331.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > th {
     332    text-align: end;
     333}
     334
     335.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > td.number {
     336    min-width: 25px;
     337    padding: 0px 2px;
     338    text-align: end;
     339}
     340
     341.timeline-view.cpu > .content > .overview > .chart > .container.stats > table > tr > td.label {
     342    text-align: start;
     343}
     344
     345.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .show-more {
     346    cursor: pointer;
     347}
     348
     349.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .unknown {
     350    color: var(--link-text-color);
     351}
     352
     353.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter-clear {
     354    display: inline-block;
     355    width: 13px;
     356    height: 13px;
     357    font-size: 12px;
     358    color: var(--gray-foreground-color);
     359    background-color: var(--gray-background-color);
     360    border-radius: 50%;
     361    line-height: 12px;
     362    text-align: center;
     363    cursor: pointer;
     364}
     365
     366.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter {
     367    padding: 0 6px 1px;
     368    font-size: 10px;
     369    background-color: hsl(0, 0%, 85%);
     370    border: 1px solid transparent;
     371    border-radius: 3px;
     372    cursor: pointer;
     373}
     374
     375.timeline-view.cpu > .content > .overview > .chart > .container.stats > table :matches(.filter, .filter-clear):hover {
     376    opacity: 0.7;
     377}
     378
     379.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter.active {
     380    color: var(--selected-foreground-color);
     381    background-color: var(--selected-background-color);
     382}
     383
     384.timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter.active + .filter.active {
     385    -webkit-margin-start: 3px;
     386}
     387
    321388@media (prefers-color-scheme: dark) {
    322     .timeline-view.cpu > .content .subtitle > .info {
    323         background-color: gray;
    324     }
    325 
    326389    .timeline-view.cpu .gauge-chart:not(.empty) > svg > polygon.needle {
    327390        fill: hsla(0, 0%, var(--foreground-lightness), 0.85);
    328391        stroke: hsla(0, 0%, var(--foreground-lightness), 0.85);
    329392    }
    330 }
     393
     394    .timeline-view.cpu > .content > .overview > .chart > .container.stats > table .filter {
     395        background-color: hsl(0, 0%, 33%);
     396    }
     397}
  • trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js

    r242300 r242562  
    3636        this.element.classList.add("cpu");
    3737
     38        this._statisticsData = null;
     39        this._sectionLimit = CPUTimelineView.defaultSectionLimit;
     40
    3841        timeline.addEventListener(WI.Timeline.Event.RecordAdded, this._cpuTimelineRecordAdded, this);
    3942    }
     
    6467    static get highEnergyThreshold() { return 150; }
    6568
     69    static get defaultSectionLimit() { return 5; }
     70
    6671    // Public
    6772
     
    8388    {
    8489        super.reset();
     90
     91        this._resetSourcesFilters();
    8592
    8693        this.clear();
     
    99106        this._energyChart.needsLayout();
    100107        this._clearEnergyImpactText();
     108
     109        this._clearStatistics();
     110        this._clearSources();
    101111
    102112        function clearUsageView(view) {
     
    116126
    117127        this._mainThreadWorkIndicatorView.clear();
     128
     129        this._statisticsData = null;
     130        this._sectionLimit = CPUTimelineView.defaultSectionLimit;
    118131    }
    119132
     
    146159            chartSubtitleElement.classList.add("subtitle");
    147160            chartSubtitleElement.textContent = subtitle;
    148             chartSubtitleElement.title = tooltip;
     161            if (tooltip)
     162                chartSubtitleElement.title = tooltip;
    149163
    150164            let chartFlexContainerElement = chartElement.appendChild(document.createElement("div"));
     
    190204        dividerElement.classList.add("divider");
    191205
    192         let energyTooltip = WI.UIString("Estimated energy impact.")
    193         let energyContainerElement = createChartContainer(overviewElement, WI.UIString("Energy Impact"), energyTooltip);
     206        let energyContainerElement = createChartContainer(overviewElement, WI.UIString("Energy Impact"), WI.UIString("Estimated energy impact."));
    194207        energyContainerElement.classList.add("energy");
    195208
     
    289302        this._threadsDetailsElement.addEventListener("toggle", (event) => {
    290303            WI.settings.cpuTimelineThreadDetailsExpanded.value = this._threadsDetailsElement.open;
    291             this.updateLayout();
     304            if (this._threadsDetailsElement.open)
     305                this.updateLayout(WI.CPUTimelineView.LayoutReason.Internal);
    292306        });
    293307
     
    313327        this._workerViews = [];
    314328
     329        this._sourcesFilter = {
     330            timer: new Set,
     331            event: new Set,
     332            observer: new Set,
     333        };
     334
     335        let bottomOverviewElement = contentElement.appendChild(document.createElement("div"));
     336        bottomOverviewElement.classList.add("overview");
     337
     338        let statisticsContainerElement = createChartContainer(bottomOverviewElement, WI.UIString("Statistics"));
     339        statisticsContainerElement.classList.add("stats");
     340
     341        this._statisticsTable = statisticsContainerElement.appendChild(document.createElement("table"));
     342        this._statisticsRows = [];
     343
     344        {
     345            let {headerCell, numberCell} = this._createTableRow(this._statisticsTable);
     346            headerCell.textContent = WI.UIString("Script Entries:");
     347            this._scriptEntriesNumberElement = numberCell;
     348        }
     349
     350        this._clearStatistics();
     351
     352        let bottomDividerElement = bottomOverviewElement.appendChild(document.createElement("div"));
     353        bottomDividerElement.classList.add("divider");
     354
     355        let sourcesContainerElement = createChartContainer(bottomOverviewElement, WI.UIString("Sources"));
     356        sourcesContainerElement.classList.add("stats");
     357
     358        this._sourcesTable = sourcesContainerElement.appendChild(document.createElement("table"));
     359        this._sourcesRows = [];
     360
     361        {
     362            let {row, headerCell, numberCell, labelCell} = this._createTableRow(this._sourcesTable);
     363            headerCell.textContent = WI.UIString("Filter:");
     364            this._sourcesFilterRow = row;
     365            this._sourcesFilterRow.hidden = true;
     366            this._sourcesFilterNumberElement = numberCell;
     367            this._sourcesFilterLabelElement = labelCell;
     368
     369            let filterClearElement = numberCell.appendChild(document.createElement("span"));
     370            filterClearElement.className = "filter-clear";
     371            filterClearElement.textContent = multiplicationSign;
     372            filterClearElement.addEventListener("click", (event) => {
     373                this._resetSourcesFilters();
     374                this._layoutStatisticsAndSources();
     375            });
     376        }
     377        {
     378            let {row, headerCell, numberCell, labelCell} = this._createTableRow(this._sourcesTable);
     379            headerCell.textContent = WI.UIString("Timers:");
     380            this._timerInstallationsRow = row;
     381            this._timerInstallationsNumberElement = numberCell;
     382            this._timerInstallationsLabelElement = labelCell;
     383        }
     384        {
     385            let {row, headerCell, numberCell, labelCell} = this._createTableRow(this._sourcesTable);
     386            headerCell.textContent = WI.UIString("Event Handlers:");
     387            this._eventHandlersRow = row;
     388            this._eventHandlersNumberElement = numberCell;
     389            this._eventHandlersLabelElement = labelCell;
     390        }
     391        {
     392            let {row, headerCell, numberCell, labelCell} = this._createTableRow(this._sourcesTable);
     393            headerCell.textContent = WI.UIString("Observer Handlers:");
     394            this._observerHandlersRow = row;
     395            this._observerHandlersNumberElement = numberCell;
     396            this._observerHandlersLabelElement = labelCell;
     397        }
     398
     399        this._clearSources();
     400
    315401        this.element.addEventListener("mousemove", this._handleGraphMouseMove.bind(this));
    316402    }
     
    320406        if (this.layoutReason === WI.View.LayoutReason.Resize)
    321407            return;
     408
     409        if (this.layoutReason !== WI.CPUTimelineView.LayoutReason.Internal)
     410            this._sectionLimit = CPUTimelineView.defaultSectionLimit;
    322411
    323412        // Always update timeline ruler.
     
    346435        }
    347436
    348         let samplingData = this._computeSamplingData(graphStartTime, visibleEndTime);
    349         let nonIdleSamplesCount = samplingData.samples.length - samplingData.samplesIdle;
    350         if (!nonIdleSamplesCount) {
    351             this._breakdownChart.clear();
    352             this._breakdownChart.needsLayout();
    353             this._clearBreakdownLegend();
    354         } else {
    355             let percentScript = samplingData.samplesScript / nonIdleSamplesCount;
    356             let percentLayout = samplingData.samplesLayout / nonIdleSamplesCount;
    357             let percentPaint = samplingData.samplesPaint / nonIdleSamplesCount;
    358             let percentStyle = samplingData.samplesStyle / nonIdleSamplesCount;
    359 
    360             this._breakdownLegendScriptElement.textContent = `${Number.percentageString(percentScript)} (${samplingData.samplesScript})`;
    361             this._breakdownLegendLayoutElement.textContent = `${Number.percentageString(percentLayout)} (${samplingData.samplesLayout})`;
    362             this._breakdownLegendPaintElement.textContent = `${Number.percentageString(percentPaint)} (${samplingData.samplesPaint})`;
    363             this._breakdownLegendStyleElement.textContent = `${Number.percentageString(percentStyle)} (${samplingData.samplesStyle})`;
    364 
    365             this._breakdownChart.values = [percentScript * 100, percentLayout * 100, percentPaint * 100, percentStyle * 100];
    366             this._breakdownChart.needsLayout();
    367 
    368             let centerElement = this._breakdownChart.centerElement;
    369             let samplesElement = centerElement.firstChild;
    370             if (!samplesElement) {
    371                 samplesElement = centerElement.appendChild(document.createElement("div"));
    372                 samplesElement.classList.add("samples");
    373                 samplesElement.title = WI.UIString("Time spent on the main thread");
    374             }
    375 
    376             let millisecondsStringNoDecimal = WI.UIString("%.0fms").format(nonIdleSamplesCount);
    377             samplesElement.textContent = millisecondsStringNoDecimal;
    378         }
     437        this._statisticsData = this._computeStatisticsData(graphStartTime, visibleEndTime);
     438        this._layoutBreakdownChart();
     439        this._layoutStatisticsAndSources();
    379440
    380441        let dataPoints = [];
     
    638699        let graphWidth = (graphEndTime - graphStartTime) / secondsPerPixel;
    639700        let size = new WI.Size(graphWidth, CPUTimelineView.indicatorViewHeight);
    640         this._mainThreadWorkIndicatorView.updateChart(samplingData.samples, size, visibleEndTime, xScaleIndicatorRange);
     701        this._mainThreadWorkIndicatorView.updateChart(this._statisticsData.samples, size, visibleEndTime, xScaleIndicatorRange);
    641702
    642703        this._layoutEnergyChart(average, visibleDuration);
     
    644705
    645706    // Private
     707
     708    _layoutBreakdownChart()
     709    {
     710        let {samples, samplesScript, samplesLayout, samplesPaint, samplesStyle, samplesIdle} = this._statisticsData;
     711
     712        let nonIdleSamplesCount = samples.length - samplesIdle;
     713        if (!nonIdleSamplesCount) {
     714            this._breakdownChart.clear();
     715            this._breakdownChart.needsLayout();
     716            this._clearBreakdownLegend();
     717            return;
     718        }
     719
     720        let percentScript = samplesScript / nonIdleSamplesCount;
     721        let percentLayout = samplesLayout / nonIdleSamplesCount;
     722        let percentPaint = samplesPaint / nonIdleSamplesCount;
     723        let percentStyle = samplesStyle / nonIdleSamplesCount;
     724
     725        this._breakdownLegendScriptElement.textContent = `${Number.percentageString(percentScript)} (${samplesScript})`;
     726        this._breakdownLegendLayoutElement.textContent = `${Number.percentageString(percentLayout)} (${samplesLayout})`;
     727        this._breakdownLegendPaintElement.textContent = `${Number.percentageString(percentPaint)} (${samplesPaint})`;
     728        this._breakdownLegendStyleElement.textContent = `${Number.percentageString(percentStyle)} (${samplesStyle})`;
     729
     730        this._breakdownChart.values = [percentScript * 100, percentLayout * 100, percentPaint * 100, percentStyle * 100];
     731        this._breakdownChart.needsLayout();
     732
     733        let centerElement = this._breakdownChart.centerElement;
     734        let samplesElement = centerElement.firstChild;
     735        if (!samplesElement) {
     736            samplesElement = centerElement.appendChild(document.createElement("div"));
     737            samplesElement.classList.add("samples");
     738            samplesElement.title = WI.UIString("Time spent on the main thread");
     739        }
     740
     741        let millisecondsStringNoDecimal = WI.UIString("%.0fms").format(nonIdleSamplesCount);
     742        samplesElement.textContent = millisecondsStringNoDecimal;
     743    }
     744
     745    _layoutStatisticsAndSources()
     746    {
     747        this._layoutStatisticsSection();
     748        this._layoutSourcesSection();
     749    }
     750
     751    _layoutStatisticsSection()
     752    {
     753        let statistics = this._statisticsData;
     754
     755        this._clearStatistics();
     756
     757        this._scriptEntriesNumberElement.textContent = statistics.scriptEntries;
     758
     759        let createFilterElement = (type, name) => {
     760            let span = document.createElement("span");
     761            span.className = "filter";
     762            span.textContent = name;
     763            span.addEventListener("mouseup", (event) => {
     764                if (span.classList.contains("active"))
     765                    this._removeSourcesFilter(type, name);
     766                else
     767                    this._addSourcesFilter(type, name);
     768
     769                this._layoutStatisticsAndSources();
     770            });
     771
     772            span.classList.toggle("active", this._sourcesFilter[type].has(name));
     773
     774            return span;
     775        };
     776
     777        let expandAllSections = () => {
     778            this._sectionLimit = Infinity;
     779            this._layoutStatisticsAndSources();
     780        };
     781
     782        function createEllipsisElement() {
     783            let span = document.createElement("span");
     784            span.className = "show-more";
     785            span.role = "button";
     786            span.textContent = ellipsis;
     787            span.addEventListener("click", (event) => {
     788                expandAllSections();
     789            });
     790            return span;
     791        }
     792
     793        // Sort a Map of key => count values in descending order.
     794        function sortMapByEntryCount(map) {
     795            let entries = Array.from(map);
     796            entries.sort((entryA, entryB) => entryB[1] - entryA[1]);
     797            return new Map(entries);
     798        }
     799
     800        if (statistics.timerTypes.size) {
     801            let i = 0;
     802            let sorted = sortMapByEntryCount(statistics.timerTypes);
     803            for (let [timerType, count] of sorted) {
     804                let headerValue = i === 0 ? WI.UIString("Timers:") : "";
     805                let timerTypeElement = createFilterElement("timer", timerType);
     806                this._insertTableRow(this._statisticsTable, this._statisticsRows, {headerValue, numberValue: count, labelValue: timerTypeElement});
     807
     808                if (++i === this._sectionLimit && sorted.size > this._sectionLimit) {
     809                    this._insertTableRow(this._statisticsTable, this._statisticsRows, {labelValue: createEllipsisElement()});
     810                    break;
     811                }
     812            }
     813        }
     814
     815        if (statistics.eventTypes.size) {
     816            let i = 0;
     817            let sorted = sortMapByEntryCount(statistics.eventTypes);
     818            for (let [eventType, count] of sorted) {
     819                let headerValue = i === 0 ? WI.UIString("Events:") : "";
     820                let eventTypeElement = createFilterElement("event", eventType);
     821                this._insertTableRow(this._statisticsTable, this._statisticsRows, {headerValue, numberValue: count, labelValue: eventTypeElement});
     822
     823                if (++i === this._sectionLimit && sorted.size > this._sectionLimit) {
     824                    this._insertTableRow(this._statisticsTable, this._statisticsRows, {labelValue: createEllipsisElement()});
     825                    break;
     826                }
     827            }
     828        }
     829
     830        if (statistics.observerTypes.size) {
     831            let i = 0;
     832            let sorted = sortMapByEntryCount(statistics.observerTypes);
     833            for (let [observerType, count] of sorted) {
     834                let headerValue = i === 0 ? WI.UIString("Observers:") : "";
     835                let observerTypeElement = createFilterElement("observer", observerType);
     836                this._insertTableRow(this._statisticsTable, this._statisticsRows, {headerValue, numberValue: count, labelValue: observerTypeElement});
     837
     838                if (++i === this._sectionLimit && sorted.size > this._sectionLimit) {
     839                    this._insertTableRow(this._statisticsTable, this._statisticsRows, {labelValue: createEllipsisElement()});
     840                    break;
     841                }
     842            }
     843        }
     844    }
     845
     846    _layoutSourcesSection()
     847    {
     848        let statistics = this._statisticsData;
     849
     850        this._clearSources();
     851
     852        const unknownLocationKey = "unknown";
     853
     854        function keyForSourceCodeLocation(sourceCodeLocation) {
     855            if (!sourceCodeLocation)
     856                return unknownLocationKey;
     857
     858            return sourceCodeLocation.sourceCode.url + ":" + sourceCodeLocation.lineNumber + ":" + sourceCodeLocation.columnNumber;
     859        }
     860
     861        function labelForLocation(key, sourceCodeLocation, functionName) {
     862            if (key === unknownLocationKey) {
     863                let span = document.createElement("span");
     864                span.className = "unknown";
     865                span.textContent = WI.UIString("Unknown Location");
     866                return span;
     867            }
     868
     869            const options = {
     870                nameStyle: WI.SourceCodeLocation.NameStyle.Short,
     871                columnStyle: WI.SourceCodeLocation.ColumnStyle.Shown,
     872                dontFloat: true,
     873                ignoreNetworkTab: true,
     874                ignoreSearchTab: true,
     875            };
     876            return WI.createSourceCodeLocationLink(sourceCodeLocation, options);
     877        }
     878
     879        let timerFilters = this._sourcesFilter.timer;
     880        let eventFilters = this._sourcesFilter.event;
     881        let observerFilters = this._sourcesFilter.observer;
     882        let hasFilters = (timerFilters.size || eventFilters.size || observerFilters.size);
     883
     884        let sectionLimit = this._sectionLimit;
     885        if (isFinite(sectionLimit) && hasFilters)
     886            sectionLimit = CPUTimelineView.defaultSectionLimit * 2;
     887
     888        let expandAllSections = () => {
     889            this._sectionLimit = Infinity;
     890            this._layoutStatisticsAndSources();
     891        };
     892
     893        function createEllipsisElement() {
     894            let span = document.createElement("span");
     895            span.className = "show-more";
     896            span.role = "button";
     897            span.textContent = ellipsis;
     898            span.addEventListener("click", (event) => {
     899                expandAllSections();
     900            });
     901            return span;
     902        }
     903
     904        let timerMap = new Map;
     905        let eventHandlerMap = new Map;
     906        let observerCallbackMap = new Map;
     907        let seenTimers = new Set;
     908
     909        if (!hasFilters || timerFilters.size) {
     910            // Aggregate timers on the location where the timers were installed.
     911            // For repeating timers, this includes the total counts the interval fired in the selected time range.
     912            for (let record of statistics.timerInstallationRecords) {
     913                if (timerFilters.size) {
     914                    if (record.eventType === WI.ScriptTimelineRecord.EventType.AnimationFrameRequested && !timerFilters.has("requestAnimationFrame"))
     915                        continue;
     916                    if (record.eventType === WI.ScriptTimelineRecord.EventType.TimerInstalled && !timerFilters.has("setTimeout"))
     917                        continue;
     918                }
     919
     920                let callFrame = record.initiatorCallFrame;
     921                let sourceCodeLocation = callFrame ? callFrame.sourceCodeLocation : record.sourceCodeLocation;
     922                let functionName = callFrame ? callFrame.functionName : "";
     923                let key = keyForSourceCodeLocation(sourceCodeLocation);
     924                let entry = timerMap.getOrInitialize(key, {sourceCodeLocation, functionName, count: 0, repeating: false});
     925                if (record.details) {
     926                    let timerIdentifier = record.details.timerId;
     927                    let repeatingEntry = statistics.repeatingTimers.get(timerIdentifier);
     928                    let count = repeatingEntry ? repeatingEntry.count : 1;
     929                    entry.count += count;
     930                    if (record.details.repeating)
     931                        entry.repeating = true;
     932                    seenTimers.add(timerIdentifier);
     933                } else
     934                    entry.count += 1;
     935            }
     936
     937            // Aggregate repeating timers where we did not see the installation in the selected time range.
     938            // This will use the source code location of where the timer fired, which is better than nothing.
     939            if (!hasFilters || timerFilters.has("setTimeout")) {
     940                for (let [timerId, repeatingEntry] of statistics.repeatingTimers) {
     941                    if (seenTimers.has(timerId))
     942                        continue;
     943                    // FIXME: <https://webkit.org/b/195351> Web Inspector: CPU Usage Timeline - better resolution of installation source for repeated timers
     944                    // We could have a map of all repeating timer installations in the whole recording
     945                    // so that we can provide a function name for these repeating timers lacking an installation point.
     946                    let sourceCodeLocation = repeatingEntry.record.sourceCodeLocation;
     947                    let key = keyForSourceCodeLocation(sourceCodeLocation);
     948                    let entry = timerMap.getOrInitialize(key, {sourceCodeLocation, count: 0, repeating: false});
     949                    entry.count += repeatingEntry.count;
     950                    entry.repeating = true;
     951                }
     952            }
     953        }
     954
     955        if (!hasFilters || eventFilters.size) {
     956            for (let record of statistics.eventHandlerRecords) {
     957                if (eventFilters.size && !eventFilters.has(record.details))
     958                    continue;
     959                let sourceCodeLocation = record.sourceCodeLocation;
     960                let key = keyForSourceCodeLocation(sourceCodeLocation);
     961                let entry = eventHandlerMap.getOrInitialize(key, {sourceCodeLocation, count: 0});
     962                entry.count += 1;
     963            }
     964        }
     965
     966        if (!hasFilters || observerFilters.size) {
     967            for (let record of statistics.observerCallbackRecords) {
     968                if (observerFilters.size && !observerFilters.has(record.details))
     969                    continue;
     970                let sourceCodeLocation = record.sourceCodeLocation;
     971                let key = keyForSourceCodeLocation(record.sourceCodeLocation);
     972                let entry = observerCallbackMap.getOrInitialize(key, {sourceCodeLocation, count: 0});
     973                entry.count += 1;
     974            }
     975        }
     976
     977        const headerValue = "";
     978
     979        // Sort a Map of key => {count} objects in descending order.
     980        function sortMapByEntryCountProperty(map) {
     981            let entries = Array.from(map);
     982            entries.sort((entryA, entryB) => entryB[1].count - entryA[1].count);
     983            return new Map(entries);
     984        }
     985
     986        if (timerMap.size) {
     987            let i = 0;
     988            let sorted = sortMapByEntryCountProperty(timerMap);
     989            for (let [key, entry] of sorted) {
     990                let numberValue = entry.repeating ? WI.UIString("~%s", "Approximate Number", "Approximate count of events").format(entry.count) : entry.count;
     991                let sourceCodeLocation = entry.callFrame ? entry.callFrame.sourceCodeLocation : entry.sourceCodeLocation;
     992                let labelValue = labelForLocation(key, sourceCodeLocation);
     993                let followingRow = this._eventHandlersRow;
     994
     995                let row;
     996                if (i === 0) {
     997                    row = this._timerInstallationsRow;
     998                    this._timerInstallationsNumberElement.textContent = numberValue;
     999                    this._timerInstallationsLabelElement.append(labelValue);
     1000                } else
     1001                    row = this._insertTableRow(this._sourcesTable, this._sourcesRows, {headerValue, numberValue, labelValue, followingRow});
     1002
     1003                if (entry.functionName)
     1004                    row.querySelector(".label").append(` ${enDash} ${entry.functionName}`);
     1005
     1006                if (++i === sectionLimit && sorted.size > sectionLimit) {
     1007                    this._insertTableRow(this._sourcesTable, this._sourcesRows, {labelValue: createEllipsisElement(), followingRow});
     1008                    break;
     1009                }
     1010            }
     1011        }
     1012
     1013        if (eventHandlerMap.size) {
     1014            let i = 0;
     1015            let sorted = sortMapByEntryCountProperty(eventHandlerMap);
     1016            for (let [key, entry] of sorted) {
     1017                let numberValue = entry.count;
     1018                let labelValue = labelForLocation(key, entry.sourceCodeLocation);
     1019                let followingRow = this._observerHandlersRow;
     1020
     1021                if (i === 0) {
     1022                    this._eventHandlersNumberElement.textContent = numberValue;
     1023                    this._eventHandlersLabelElement.append(labelValue);
     1024                } else
     1025                    this._insertTableRow(this._sourcesTable, this._sourcesRows, {headerValue, numberValue, labelValue, followingRow});
     1026
     1027                if (++i === sectionLimit && sorted.size > sectionLimit) {
     1028                    this._insertTableRow(this._sourcesTable, this._sourcesRows, {labelValue: createEllipsisElement(), followingRow});
     1029                    break;
     1030                }
     1031            }
     1032        }
     1033
     1034        if (observerCallbackMap.size) {
     1035            let i = 0;
     1036            let sorted = sortMapByEntryCountProperty(observerCallbackMap);
     1037            for (let [key, entry] of sorted) {
     1038                let numberValue = entry.count;
     1039                let labelValue = labelForLocation(key, entry.sourceCodeLocation);
     1040
     1041                if (i === 0) {
     1042                    this._observerHandlersNumberElement.textContent = numberValue;
     1043                    this._observerHandlersLabelElement.append(labelValue);
     1044                } else
     1045                    this._insertTableRow(this._sourcesTable, this._sourcesRows, {headerValue, numberValue, labelValue});
     1046
     1047                if (++i === sectionLimit && sorted.size > sectionLimit) {
     1048                    this._insertTableRow(this._sourcesTable, this._sourcesRows, {labelValue: createEllipsisElement()});
     1049                    break;
     1050                }
     1051            }
     1052        }
     1053    }
    6461054
    6471055    _layoutEnergyChart(average, visibleDuration)
     
    6971105    }
    6981106
    699     _computeSamplingData(startTime, endTime)
     1107    _computeStatisticsData(startTime, endTime)
    7001108    {
    7011109        // Compute per-millisecond samples of what the main thread was doing.
     
    7181126        const includeRecordBeforeStart = true;
    7191127
     1128        function incrementTypeCount(map, key) {
     1129            let entry = map.get(key);
     1130            if (entry)
     1131                map.set(key, entry + 1);
     1132            else
     1133                map.set(key, 1);
     1134        }
     1135
     1136        let timerInstallationRecords = [];
     1137        let eventHandlerRecords = [];
     1138        let observerCallbackRecords = [];
     1139        let scriptEntries = 0;
     1140        let timerTypes = new Map;
     1141        let eventTypes = new Map;
     1142        let observerTypes = new Map;
     1143
     1144        let repeatingTimers = new Map;
     1145        let possibleRepeatingTimers = new Set;
     1146
    7201147        let scriptTimeline = this._recording.timelineForRecordType(WI.TimelineRecord.Type.Script);
    7211148        let scriptRecords = scriptTimeline ? scriptTimeline.recordsInTimeRange(startTime, endTime, includeRecordBeforeStart) : [];
    7221149        scriptRecords = scriptRecords.filter((record) => {
     1150            // Return true for event types that define script entries/exits.
     1151            // Return false for events with no time ranges or if they are contained in other events.
    7231152            switch (record.eventType) {
    7241153            case WI.ScriptTimelineRecord.EventType.ScriptEvaluated:
    7251154            case WI.ScriptTimelineRecord.EventType.APIScriptEvaluated:
     1155                scriptEntries++;
     1156                return true;
     1157
    7261158            case WI.ScriptTimelineRecord.EventType.ObserverCallback:
     1159                incrementTypeCount(observerTypes, record.details);
     1160                observerCallbackRecords.push(record);
     1161                scriptEntries++;
     1162                return true;
     1163
    7271164            case WI.ScriptTimelineRecord.EventType.EventDispatched:
     1165                incrementTypeCount(eventTypes, record.details);
     1166                eventHandlerRecords.push(record);
     1167                scriptEntries++;
     1168                return true;
     1169
    7281170            case WI.ScriptTimelineRecord.EventType.MicrotaskDispatched:
     1171                // Do not normally count this as a script entry, but they may have a time range
     1172                // that is not covered by script entry (queueMicrotask).
     1173                return true;
     1174
    7291175            case WI.ScriptTimelineRecord.EventType.TimerFired:
     1176                incrementTypeCount(timerTypes, "setTimeout");
     1177                if (possibleRepeatingTimers.has(record.details)) {
     1178                    let entry = repeatingTimers.get(record.details);
     1179                    if (entry)
     1180                        entry.count += 1;
     1181                    else
     1182                        repeatingTimers.set(record.details, {record, count: 1});
     1183                } else
     1184                    possibleRepeatingTimers.add(record.details);
     1185                scriptEntries++;
     1186                return true;
     1187
    7301188            case WI.ScriptTimelineRecord.EventType.AnimationFrameFired:
    731                 // These event types define script entry/exits.
     1189                incrementTypeCount(timerTypes, "requestAnimationFrame");
     1190                scriptEntries++;
    7321191                return true;
    7331192
    7341193            case WI.ScriptTimelineRecord.EventType.AnimationFrameRequested:
     1194            case WI.ScriptTimelineRecord.EventType.TimerInstalled:
     1195                // These event types have no time range, or are contained by the others.
     1196                timerInstallationRecords.push(record);
     1197                return false;
     1198
    7351199            case WI.ScriptTimelineRecord.EventType.AnimationFrameCanceled:
    736             case WI.ScriptTimelineRecord.EventType.TimerInstalled:
    7371200            case WI.ScriptTimelineRecord.EventType.TimerRemoved:
    7381201            case WI.ScriptTimelineRecord.EventType.ProbeSampleRecorded:
     
    8451308            samplesPaint,
    8461309            samplesStyle,
     1310            scriptEntries,
     1311            timerTypes,
     1312            eventTypes,
     1313            observerTypes,
     1314            timerInstallationRecords,
     1315            eventHandlerRecords,
     1316            observerCallbackRecords,
     1317            repeatingTimers,
    8471318        };
    8481319    }
     
    8571328
    8581329        this._workerViews = [];
     1330    }
     1331
     1332    _resetSourcesFilters()
     1333    {
     1334        if (!this._sourcesFilter)
     1335            return;
     1336
     1337        this._sourcesFilterRow.hidden = true;
     1338        this._sourcesFilterLabelElement.removeChildren();
     1339
     1340        this._timerInstallationsRow.hidden = false;
     1341        this._eventHandlersRow.hidden = false;
     1342        this._observerHandlersRow.hidden = false;
     1343
     1344        this._sourcesFilter.timer.clear();
     1345        this._sourcesFilter.event.clear();
     1346        this._sourcesFilter.observer.clear();
     1347    }
     1348
     1349    _addSourcesFilter(type, name)
     1350    {
     1351        this._sourcesFilter[type].add(name);
     1352        this._updateSourcesFilters();
     1353    }
     1354
     1355    _removeSourcesFilter(type, name)
     1356    {
     1357        this._sourcesFilter[type].delete(name);
     1358        this._updateSourcesFilters();
     1359    }
     1360
     1361    _updateSourcesFilters()
     1362    {
     1363        let timerFilters = this._sourcesFilter.timer;
     1364        let eventFilters = this._sourcesFilter.event;
     1365        let observerFilters = this._sourcesFilter.observer;
     1366
     1367        if (!timerFilters.size && !eventFilters.size && !observerFilters.size) {
     1368            this._resetSourcesFilters();
     1369            return;
     1370        }
     1371
     1372        let createActiveFilterElement = (type, name) => {
     1373            let span = document.createElement("span");
     1374            span.className = "filter active";
     1375            span.textContent = name;
     1376            span.addEventListener("mouseup", (event) => {
     1377                this._removeSourcesFilter(type, name);
     1378                this._layoutStatisticsAndSources();
     1379            });
     1380            return span;
     1381        }
     1382
     1383        this._sourcesFilterRow.hidden = false;
     1384        this._sourcesFilterLabelElement.removeChildren();
     1385
     1386        for (let name of timerFilters)
     1387            this._sourcesFilterLabelElement.appendChild(createActiveFilterElement("timer", name));
     1388        for (let name of eventFilters)
     1389            this._sourcesFilterLabelElement.appendChild(createActiveFilterElement("event", name));
     1390        for (let name of observerFilters)
     1391            this._sourcesFilterLabelElement.appendChild(createActiveFilterElement("observer", name));
     1392
     1393        this._timerInstallationsRow.hidden = !timerFilters.size;
     1394        this._eventHandlersRow.hidden = !eventFilters.size;
     1395        this._observerHandlersRow.hidden = !observerFilters.size;
     1396    }
     1397
     1398    _createTableRow(table)
     1399    {
     1400        let row = table.appendChild(document.createElement("tr"));
     1401
     1402        let headerCell = row.appendChild(document.createElement("th"));
     1403
     1404        let numberCell = row.appendChild(document.createElement("td"));
     1405        numberCell.className = "number";
     1406
     1407        let labelCell = row.appendChild(document.createElement("td"));
     1408        labelCell.className = "label";
     1409
     1410        return {row, headerCell, numberCell, labelCell};
     1411    }
     1412
     1413    _insertTableRow(table, rowList, {headerValue, numberValue, labelValue, followingRow})
     1414    {
     1415        let {row, headerCell, numberCell, labelCell} = this._createTableRow(table);
     1416        rowList.push(row);
     1417
     1418        if (followingRow)
     1419            table.insertBefore(row, followingRow);
     1420
     1421        if (headerValue)
     1422            headerCell.textContent = headerValue;
     1423
     1424        if (numberValue)
     1425            numberCell.textContent = numberValue;
     1426
     1427        if (labelValue)
     1428            labelCell.append(labelValue);
     1429
     1430        return row;
     1431    }
     1432
     1433    _clearStatistics()
     1434    {
     1435        this._scriptEntriesNumberElement.textContent = emDash;
     1436
     1437        for (let row of this._statisticsRows)
     1438            row.remove();
     1439        this._statisticsRows = [];
     1440    }
     1441
     1442    _clearSources()
     1443    {
     1444        this._timerInstallationsNumberElement.textContent = emDash;
     1445        this._timerInstallationsLabelElement.textContent = "";
     1446
     1447        this._eventHandlersNumberElement.textContent = emDash;
     1448        this._eventHandlersLabelElement.textContent = "";
     1449
     1450        this._observerHandlersNumberElement.textContent = emDash;
     1451        this._observerHandlersLabelElement.textContent = "";
     1452
     1453        for (let row of this._sourcesRows)
     1454            row.remove();
     1455        this._sourcesRows = [];
    8591456    }
    8601457
     
    10101607};
    10111608
     1609WI.CPUTimelineView.LayoutReason = {
     1610    Internal: Symbol("cpu-timeline-view-internal-layout"),
     1611};
     1612
    10121613// NOTE: UI follows this order.
    10131614WI.CPUTimelineView.SampleType = {
  • trunk/Source/WebInspectorUI/UserInterface/Views/Main.css

    r242242 r242562  
    239239.resource-link,
    240240.go-to-link {
    241     color: hsl(0, 0%, 33%);
     241    color: var(--link-text-color);
    242242    text-decoration: underline;
    243243    cursor: pointer;
     
    478478    }
    479479
    480     .resource-link,
    481     .go-to-link {
    482         color: var(--text-color-secondary);
    483     }
    484 
    485480    .expand-list-button {
    486481        color: inherit;
  • trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css

    r242300 r242562  
    5151    --background-color-code: white;
    5252
     53    /* Gray background with lighter foreground. In dark mode this is lighter. */
     54    --gray-background-color: hsl(0, 0%, 66%);
     55    --gray-foreground-color: white;
     56
     57    --link-text-color: hsl(0, 0%, 33%);
     58
    5359    --selected-foreground-color: white;
    5460    --selected-secondary-text-color: hsla(0, 100%, 100%, 0.7);
     
    216222        --background-color-code: hsl(0, 0%, 21%);
    217223
     224        --gray-background-color: hsl(0, 0%, 50%);
     225        --gray-foreground-color: hsl(0, 0%, 33%);
     226
     227        --link-text-color: var(--text-color-secondary);
     228
    218229        --background-color-alternate: hsla(0, 0%, var(--foreground-lightness), 0.05);
    219230        --background-color-selected: hsla(0, 0%, var(--foreground-lightness), 0.1);
Note: See TracChangeset for help on using the changeset viewer.