diff --git a/front_end/layer_viewer/PaintProfilerView.js b/front_end/layer_viewer/PaintProfilerView.js index c5d88b9fb0..011753a029 100644 --- a/front_end/layer_viewer/PaintProfilerView.js +++ b/front_end/layer_viewer/PaintProfilerView.js @@ -42,7 +42,8 @@ LayerViewer.PaintProfilerView = class extends UI.HBox { this._canvasContainer = this.contentElement.createChild('div', 'paint-profiler-canvas-container'); this._progressBanner = this.contentElement.createChild('div', 'full-widget-dimmed-banner hidden'); this._progressBanner.textContent = Common.UIString('Profiling\u2026'); - this._pieChart = new PerfUI.PieChart(55, this._formatPieChartTime.bind(this), true); + this._pieChart = new PerfUI.PieChart( + {chartName: ls`Profiling Results`, size: 55, formatter: this._formatPieChartTime.bind(this)}); this._pieChart.element.classList.add('paint-profiler-pie-chart'); this.contentElement.appendChild(this._pieChart.element); diff --git a/front_end/perf_ui/PieChart.js b/front_end/perf_ui/PieChart.js index 773c9ed0ee..4ce73b2a40 100644 --- a/front_end/perf_ui/PieChart.js +++ b/front_end/perf_ui/PieChart.js @@ -28,20 +28,25 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/** @typedef {{size: number, formatter: function(number):string, showLegend: (boolean|undefined), chartName: string}} */ +PerfUI.PieChartOptions; + /** * @unrestricted */ PerfUI.PieChart = class { /** - * @param {number} size - * @param {function(number):string=} formatter - * @param {boolean=} showTotal + * @param {!PerfUI.PieChartOptions} options */ - constructor(size, formatter, showTotal) { + constructor(options) { + const {size, formatter, showLegend, chartName} = options; this.element = createElement('div'); this._shadowRoot = UI.createShadowRootWithCoreStyles(this.element, 'perf_ui/pieChart.css'); const root = this._shadowRoot.createChild('div', 'root'); - const svg = this._createSVGChild(root, 'svg'); + UI.ARIAUtils.markAsGroup(root); + UI.ARIAUtils.setAccessibleName(root, chartName); + this._chartRoot = root.createChild('div', 'chart-root'); + const svg = this._createSVGChild(this._chartRoot, 'svg'); this._group = this._createSVGChild(svg, 'g'); this._innerR = 0.618; const strokeWidth = 1 / size; @@ -55,12 +60,13 @@ PerfUI.PieChart = class { circle.setAttribute('stroke', 'hsl(0, 0%, 80%)'); circle.setAttribute('fill', 'transparent'); circle.setAttribute('stroke-width', strokeWidth); - this._foregroundElement = root.createChild('div', 'pie-chart-foreground'); - if (showTotal) - this._totalElement = this._foregroundElement.createChild('div', 'pie-chart-total'); + this._foregroundElement = this._chartRoot.createChild('div', 'pie-chart-foreground'); + this._totalElement = this._foregroundElement.createChild('div', 'pie-chart-total'); this._formatter = formatter; this._slices = []; this._lastAngle = -Math.PI / 2; + if (showLegend) + this._legend = root.createChild('div', 'pie-chart-legend'); this._setSize(size); } @@ -78,8 +84,12 @@ PerfUI.PieChart = class { totalString = this._formatter ? this._formatter(totalValue) : totalValue; else totalString = ''; - if (this._totalElement) - this._totalElement.textContent = totalString; + this._totalElement.textContent = totalString; + if (this._legend) { + this._legend.removeChildren(); + const legendItem = this._addLegendItem(totalValue, ls`Total`); + UI.ARIAUtils.setLabelledBy(this._totalElement, legendItem); + } } /** @@ -88,15 +98,16 @@ PerfUI.PieChart = class { _setSize(value) { this._group.setAttribute('transform', 'scale(' + (value / 2) + ') translate(1, 1) scale(0.99, 0.99)'); const size = value + 'px'; - this.element.style.width = size; - this.element.style.height = size; + this._chartRoot.style.width = size; + this._chartRoot.style.height = size; } /** * @param {number} value * @param {string} color + * @param {string=} name */ - addSlice(value, color) { + addSlice(value, color, name) { let sliceAngle = value / this._totalValue * 2 * Math.PI; if (!isFinite(sliceAngle)) return; @@ -117,6 +128,10 @@ PerfUI.PieChart = class { `M${x1},${y1} A1,1,0,${largeArc},1,${x2},${y2} L${x3},${y3} A${r2},${r2},0,${largeArc},0,${x4},${y4} Z`); path.setAttribute('fill', color); this._slices.push(path); + if (this._legend) { + const legendItem = this._addLegendItem(value, name, color); + UI.ARIAUtils.setLabelledBy(path, legendItem); + } } /** @@ -129,4 +144,30 @@ PerfUI.PieChart = class { parent.appendChild(child); return child; } + + /** + * @param {number} value + * @param {string=} name + * @param {string=} color + * @returns {!Element} + */ + _addLegendItem(value, name, color) { + const node = this._legend.ownerDocument.createElement('div'); + node.className = 'pie-chart-legend-row'; + // make sure total always appears at the bottom + if (this._legend.childElementCount) + this._legend.insertBefore(node, this._legend.lastElementChild); + else + this._legend.appendChild(node); + const sizeDiv = node.createChild('div', 'pie-chart-size'); + const swatchDiv = node.createChild('div', 'pie-chart-swatch'); + const nameDiv = node.createChild('div', 'pie-chart-name'); + if (color) + swatchDiv.style.backgroundColor = color; + else + swatchDiv.classList.add('pie-chart-empty-swatch'); + nameDiv.textContent = name; + sizeDiv.textContent = this._formatter ? this._formatter(value) : value; + return node; + } }; diff --git a/front_end/perf_ui/pieChart.css b/front_end/perf_ui/pieChart.css index bef554fa1e..ee46974086 100644 --- a/front_end/perf_ui/pieChart.css +++ b/front_end/perf_ui/pieChart.css @@ -5,9 +5,15 @@ */ .root { + align-items: center; + display: flex; + min-width: fit-content; + white-space: nowrap; +} + +.chart-root { position: relative; - width: 100%; - height: 100%; + overflow: hidden; } .pie-chart-foreground { @@ -17,10 +23,45 @@ z-index: 10; top: 0; display: flex; + pointer-events: none; } .pie-chart-total { margin: auto; padding: 2px 5px; background-color: rgba(255, 255, 255, 0.6); + pointer-events: auto; +} + +.pie-chart-legend { + margin-left: 30px; +} + +.pie-chart-legend-row { + margin: 5px auto; + padding-right: 25px; +} + +.pie-chart-swatch { + display: inline-block; + width: 11px; + height: 11px; + margin: 0 6px; + top: 1px; + position: relative; + border: 1px solid rgba(100, 100, 100, 0.2); +} + +.pie-chart-swatch.pie-chart-empty-swatch { + border: none; +} + +.pie-chart-name { + display: inline-block; +} + +.pie-chart-size { + display: inline-block; + text-align: right; + width: 70px; } diff --git a/front_end/profiler/HeapSnapshotView.js b/front_end/profiler/HeapSnapshotView.js index c33e950627..6bedd8e7ab 100644 --- a/front_end/profiler/HeapSnapshotView.js +++ b/front_end/profiler/HeapSnapshotView.js @@ -292,7 +292,6 @@ Profiler.HeapSnapshotView = class extends UI.SimpleView { this._statisticsView.addRecord(statistics.jsArrays, Common.UIString('JS Arrays'), '#7af'); this._statisticsView.addRecord(statistics.native, Common.UIString('Typed Arrays'), '#fc5'); this._statisticsView.addRecord(statistics.system, Common.UIString('System Objects'), '#98f'); - this._statisticsView.addRecord(statistics.total, Common.UIString('Total')); return statistics; } @@ -1611,10 +1610,14 @@ Profiler.HeapSnapshotStatisticsView = class extends UI.VBox { constructor() { super(); this.element.classList.add('heap-snapshot-statistics-view'); - this._pieChart = new PerfUI.PieChart(150, Profiler.HeapSnapshotStatisticsView._valueFormatter, true); + this._pieChart = new PerfUI.PieChart({ + chartName: ls`Heap memory usage`, + size: 150, + formatter: Profiler.HeapSnapshotStatisticsView._valueFormatter, + showLegend: true + }); this._pieChart.element.classList.add('heap-snapshot-stats-pie-chart'); this.element.appendChild(this._pieChart.element); - this._labels = this.element.createChild('div', 'heap-snapshot-stats-legend'); } /** @@ -1635,22 +1638,10 @@ Profiler.HeapSnapshotStatisticsView = class extends UI.VBox { /** * @param {number} value * @param {string} name - * @param {string=} color + * @param {string} color */ addRecord(value, name, color) { - if (color) - this._pieChart.addSlice(value, color); - - const node = this._labels.createChild('div'); - const swatchDiv = node.createChild('div', 'heap-snapshot-stats-swatch'); - const nameDiv = node.createChild('div', 'heap-snapshot-stats-name'); - const sizeDiv = node.createChild('div', 'heap-snapshot-stats-size'); - if (color) - swatchDiv.style.backgroundColor = color; - else - swatchDiv.classList.add('heap-snapshot-stats-empty-swatch'); - nameDiv.textContent = name; - sizeDiv.textContent = Profiler.HeapSnapshotStatisticsView._valueFormatter(value); + this._pieChart.addSlice(value, color, name); } }; diff --git a/front_end/profiler/heapProfiler.css b/front_end/profiler/heapProfiler.css index 7e5ce499e7..45027e45a0 100644 --- a/front_end/profiler/heapProfiler.css +++ b/front_end/profiler/heapProfiler.css @@ -182,38 +182,6 @@ flex-shrink: 0; } -.heap-snapshot-stats-legend { - margin-left: 24px; - flex-shrink: 0; -} - -.heap-snapshot-stats-legend > div { - margin-top: 1px; - width: 170px; -} - -.heap-snapshot-stats-swatch { - display: inline-block; - width: 10px; - height: 10px; - border: 1px solid rgba(100, 100, 100, 0.3); -} - -.heap-snapshot-stats-swatch.heap-snapshot-stats-empty-swatch { - border: none; -} - -.heap-snapshot-stats-name, -.heap-snapshot-stats-size { - display: inline-block; - margin-left: 6px; -} - -.heap-snapshot-stats-size { - float: right; - text-align: right; -} - .heap-allocation-stack .stack-frame { display: flex; justify-content: space-between; diff --git a/front_end/resources/ClearStorageView.js b/front_end/resources/ClearStorageView.js index ceb8b9f56d..9b0601967f 100644 --- a/front_end/resources/ClearStorageView.js +++ b/front_end/resources/ClearStorageView.js @@ -40,12 +40,11 @@ Resources.ClearStorageView = class extends UI.ThrottledWidget { ls`Learn more`); learnMoreRow.appendChild(learnMore); this._quotaUsage = null; - this._pieChart = new PerfUI.PieChart(110, Number.bytesToString, true); - this._pieChartLegend = createElement('div'); + this._pieChart = new PerfUI.PieChart( + {chartName: ls`Storage Usage`, size: 110, formatter: Number.bytesToString, showLegend: true}); const usageBreakdownRow = quota.appendRow(); usageBreakdownRow.classList.add('usage-breakdown-row'); usageBreakdownRow.appendChild(this._pieChart.element); - usageBreakdownRow.appendChild(this._pieChartLegend); const clearButtonSection = this._reportView.appendSection('', 'clear-storage-button').appendRow(); this._clearButton = UI.createTextButton(ls`Clear site data`, this._clear.bind(this)); @@ -234,11 +233,7 @@ Resources.ClearStorageView = class extends UI.ThrottledWidget { continue; const title = this._getStorageTypeName(usageForType.storageType); const color = this._pieColors.get(usageForType.storageType) || '#ccc'; - this._pieChart.addSlice(value, color); - const rowElement = this._pieChartLegend.createChild('div', 'usage-breakdown-legend-row'); - rowElement.createChild('span', 'usage-breakdown-legend-value').textContent = Number.bytesToString(value); - rowElement.createChild('span', 'usage-breakdown-legend-swatch').style.backgroundColor = color; - rowElement.createChild('span', 'usage-breakdown-legend-title').textContent = title; + this._pieChart.addSlice(value, color, title); } } @@ -251,7 +246,6 @@ Resources.ClearStorageView = class extends UI.ThrottledWidget { */ _resetPieChart(total) { this._pieChart.setTotal(total); - this._pieChartLegend.removeChildren(); } /** diff --git a/front_end/resources/clearStorageView.css b/front_end/resources/clearStorageView.css index 9cb3cf5061..e1bb6a1c14 100644 --- a/front_end/resources/clearStorageView.css +++ b/front_end/resources/clearStorageView.css @@ -26,27 +26,3 @@ .usage-breakdown-row { min-width: fit-content; } - -.usage-breakdown-legend-row { - margin: 5px auto; -} - -.usage-breakdown-legend-title { - display: inline-block; -} - -.usage-breakdown-legend-value { - display: inline-block; - width: 70px; - text-align: right; -} - -.usage-breakdown-legend-swatch { - display: inline-block; - width: 11px; - height: 11px; - margin: 0 6px; - position: relative; - top: 1px; - border: 1px solid rgba(0, 0, 0, 0.2); -} diff --git a/front_end/timeline/TimelineUIUtils.js b/front_end/timeline/TimelineUIUtils.js index 7c64b4973e..4242a120f5 100644 --- a/front_end/timeline/TimelineUIUtils.js +++ b/front_end/timeline/TimelineUIUtils.js @@ -1523,12 +1523,16 @@ Timeline.TimelineUIUtils = class { total += aggregatedStats[categoryName]; const element = createElementWithClass('div', 'timeline-details-view-pie-chart-wrapper hbox'); - const pieChart = new PerfUI.PieChart(110, value => Number.preciseMillisToString(value), true); + const pieChart = new PerfUI.PieChart({ + chartName: ls`Time spent in rendering`, + size: 110, + formatter: value => Number.preciseMillisToString(value), + showLegend: true, + }); pieChart.element.classList.add('timeline-details-view-pie-chart'); pieChart.setTotal(total); const pieChartContainer = element.createChild('div', 'vbox'); pieChartContainer.appendChild(pieChart.element); - const footerElement = element.createChild('div', 'timeline-aggregated-info-legend'); /** * @param {string} name @@ -1539,12 +1543,7 @@ Timeline.TimelineUIUtils = class { function appendLegendRow(name, title, value, color) { if (!value) return; - pieChart.addSlice(value, color); - const rowElement = footerElement.createChild('div'); - rowElement.createChild('span', 'timeline-aggregated-legend-value').textContent = - Number.preciseMillisToString(value, 1); - rowElement.createChild('span', 'timeline-aggregated-legend-swatch').style.backgroundColor = color; - rowElement.createChild('span', 'timeline-aggregated-legend-title').textContent = title; + pieChart.addSlice(value, color, title); } // In case of self time, first add self, then children of the same category. diff --git a/front_end/timeline/timelinePanel.css b/front_end/timeline/timelinePanel.css index f54a86c249..225c6a9caf 100644 --- a/front_end/timeline/timelinePanel.css +++ b/front_end/timeline/timelinePanel.css @@ -172,26 +172,6 @@ height: 100%; } -.timeline-aggregated-legend-title { - display: inline-block; -} - -.timeline-aggregated-legend-value { - display: inline-block; - width: 70px; - text-align: right; -} - -.timeline-aggregated-legend-swatch { - display: inline-block; - width: 11px; - height: 11px; - margin: 0 6px; - position: relative; - top: 1px; - border: 1px solid rgba(0, 0, 0, 0.2); -} - .popover ul { margin: 0; padding: 0; @@ -385,12 +365,6 @@ line-height: 12px; } -.timeline-aggregated-info-legend > div { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - .timeline-flamechart { overflow: hidden; } diff --git a/front_end/ui/ARIAUtils.js b/front_end/ui/ARIAUtils.js index b612b4ec18..84e9ceec8a 100644 --- a/front_end/ui/ARIAUtils.js +++ b/front_end/ui/ARIAUtils.js @@ -109,6 +109,14 @@ UI.ARIAUtils.markAsPresentation = function(element) { element.setAttribute('role', 'presentation'); }; +/** + * @param {!Element} element + */ +UI.ARIAUtils.ensureId = function(element) { + if (!element.id) + element.id = UI.ARIAUtils.nextId('ariaElement'); +}; + /** * @param {string} prefix * @return {string} @@ -186,6 +194,15 @@ UI.ARIAUtils.setAccessibleName = function(element, name) { element.setAttribute('aria-label', name); }; +/** + * @param {!Element} element + * @param {!Element} labelElement + */ +UI.ARIAUtils.setLabelledBy = function(element, labelElement) { + UI.ARIAUtils.ensureId(labelElement); + element.setAttribute('aria-labelledby', labelElement.id); +}; + /** * @param {string} message * @param {!Element} element