Commit 05503b60 authored by James Lissiak's avatar James Lissiak Committed by Commit Bot

DevTools: Fix a11y issues in Performance Monitor tool

Accessibility testing of the Performance Monitor tool revealed several
issues which are addressed in this patch:

- Keyboard A11y: The graph toggling UI could not be navigated or
  manipulated via the keyboard. This restricts keyboard-only users from
  interacting correctly with the tool.
  - The fix was to allow users to navigate through the list of
    MetricIndicators in the left panel using the Tab key.
    Also adding a keypress event listener to allow toggling each of the
    MetricIndicators with either the 'Enter' or 'Space' keys.

- Color A11y: The UI relied solely on color differences to identify each
  graph. This prevents users with reduced color perception from easily
  using the tool.
  - The fix was to add a text label to each graph and improve the
    contrast ratio of the fill color.

- Screen Reader A11y: There was no screen reader accessible information
  in the perf monitor tool. This prevents visually impared users from
  using the tool.
  - The fix was to add announceable text to the buttons and a
    description to the graph (A future improvement would be to also
    announce the graph values in realtime as the data comes in)

Screenshot of changes for comparison:
https://imgur.com/a/nUNInsl

Change-Id: Iaf8d2b5bf54f91b776372ae9d4c5e182d1bd6721
Reviewed-on: https://chromium-review.googlesource.com/c/1488051
Commit-Queue: James Lissiak <jalissia@microsoft.com>
Reviewed-by: default avatarJoel Einbinder <einbinder@chromium.org>
Cr-Commit-Position: refs/heads/master@{#636154}
parent 23d9d525
...@@ -25,6 +25,9 @@ PerformanceMonitor.PerformanceMonitor = class extends UI.HBox { ...@@ -25,6 +25,9 @@ PerformanceMonitor.PerformanceMonitor = class extends UI.HBox {
this._controlPane = new PerformanceMonitor.PerformanceMonitor.ControlPane(this.contentElement); this._controlPane = new PerformanceMonitor.PerformanceMonitor.ControlPane(this.contentElement);
const chartContainer = this.contentElement.createChild('div', 'perfmon-chart-container'); const chartContainer = this.contentElement.createChild('div', 'perfmon-chart-container');
this._canvas = /** @type {!HTMLCanvasElement} */ (chartContainer.createChild('canvas')); this._canvas = /** @type {!HTMLCanvasElement} */ (chartContainer.createChild('canvas'));
this._canvas.tabIndex = -1;
UI.ARIAUtils.setAccessibleName(
this._canvas, Common.UIString('Graphs displaying a real-time view of performance metrics'));
this.contentElement.createChild('div', 'perfmon-chart-suspend-overlay fill').createChild('div').textContent = this.contentElement.createChild('div', 'perfmon-chart-suspend-overlay fill').createChild('div').textContent =
Common.UIString('Paused'); Common.UIString('Paused');
this._controlPane.addEventListener( this._controlPane.addEventListener(
...@@ -147,7 +150,7 @@ PerformanceMonitor.PerformanceMonitor = class extends UI.HBox { ...@@ -147,7 +150,7 @@ PerformanceMonitor.PerformanceMonitor = class extends UI.HBox {
const labelDistanceSeconds = 10; const labelDistanceSeconds = 10;
const lightGray = UI.themeSupport.patchColorText('rgba(0, 0, 0, 0.02)', UI.ThemeSupport.ColorUsage.Foreground); const lightGray = UI.themeSupport.patchColorText('rgba(0, 0, 0, 0.02)', UI.ThemeSupport.ColorUsage.Foreground);
ctx.font = '10px ' + Host.fontFamily(); ctx.font = '10px ' + Host.fontFamily();
ctx.fillStyle = UI.themeSupport.patchColorText('rgba(0, 0, 0, 0.3)', UI.ThemeSupport.ColorUsage.Foreground); ctx.fillStyle = UI.themeSupport.patchColorText('rgba(0, 0, 0, 0.55)', UI.ThemeSupport.ColorUsage.Foreground);
const currentTime = Date.now() / 1000; const currentTime = Date.now() / 1000;
for (let sec = Math.ceil(currentTime);; --sec) { for (let sec = Math.ceil(currentTime);; --sec) {
const x = this._width - ((currentTime - sec) * 1000 - this._pollIntervalMs) * this._pixelsPerMs; const x = this._width - ((currentTime - sec) * 1000 - this._pollIntervalMs) * this._pixelsPerMs;
...@@ -197,6 +200,9 @@ PerformanceMonitor.PerformanceMonitor = class extends UI.HBox { ...@@ -197,6 +200,9 @@ PerformanceMonitor.PerformanceMonitor = class extends UI.HBox {
ctx.stroke(path.path); ctx.stroke(path.path);
ctx.restore(); ctx.restore();
} }
ctx.fillStyle = UI.themeSupport.patchColorText('rgba(0, 0, 0, 0.55)', UI.ThemeSupport.ColorUsage.Foreground);
ctx.font = `10px ${Host.fontFamily()}`;
ctx.fillText(chartInfo.title, 8, 10);
this._drawVerticalGrid(ctx, height - bottomPadding, max, chartInfo); this._drawVerticalGrid(ctx, height - bottomPadding, max, chartInfo);
ctx.restore(); ctx.restore();
} }
...@@ -245,9 +251,9 @@ PerformanceMonitor.PerformanceMonitor = class extends UI.HBox { ...@@ -245,9 +251,9 @@ PerformanceMonitor.PerformanceMonitor = class extends UI.HBox {
let scaleValue = Math.floor(max / base) * base; let scaleValue = Math.floor(max / base) * base;
const span = max; const span = max;
const topPadding = 5; const topPadding = 18;
const visibleHeight = height - topPadding; const visibleHeight = height - topPadding;
ctx.fillStyle = UI.themeSupport.patchColorText('rgba(0, 0, 0, 0.3)', UI.ThemeSupport.ColorUsage.Foreground); ctx.fillStyle = UI.themeSupport.patchColorText('rgba(0, 0, 0, 0.55)', UI.ThemeSupport.ColorUsage.Foreground);
ctx.strokeStyle = this._gridColor; ctx.strokeStyle = this._gridColor;
ctx.beginPath(); ctx.beginPath();
for (let i = 0; i < 2; ++i) { for (let i = 0; i < 2; ++i) {
...@@ -285,7 +291,7 @@ PerformanceMonitor.PerformanceMonitor = class extends UI.HBox { ...@@ -285,7 +291,7 @@ PerformanceMonitor.PerformanceMonitor = class extends UI.HBox {
*/ */
_buildMetricPath(chartInfo, metricInfo, height, scaleMax, stackedChartBaseLandscape) { _buildMetricPath(chartInfo, metricInfo, height, scaleMax, stackedChartBaseLandscape) {
const path = new Path2D(); const path = new Path2D();
const topPadding = 5; const topPadding = 18;
const visibleHeight = height - topPadding; const visibleHeight = height - topPadding;
if (visibleHeight < 1) if (visibleHeight < 1)
return path; return path;
...@@ -509,7 +515,11 @@ PerformanceMonitor.PerformanceMonitor.MetricIndicator = class { ...@@ -509,7 +515,11 @@ PerformanceMonitor.PerformanceMonitor.MetricIndicator = class {
this._valueElement = this.element.createChild('div', 'perfmon-indicator-value'); this._valueElement = this.element.createChild('div', 'perfmon-indicator-value');
this._valueElement.style.color = color; this._valueElement.style.color = color;
this.element.addEventListener('click', () => this._toggleIndicator()); this.element.addEventListener('click', () => this._toggleIndicator());
this.element.addEventListener('keypress', event => this._handleKeypress(event));
this.element.classList.toggle('active', active); this.element.classList.toggle('active', active);
UI.ARIAUtils.markAsCheckbox(this.element);
UI.ARIAUtils.setChecked(this.element, this._active);
this.element.tabIndex = 0;
} }
/** /**
...@@ -545,8 +555,18 @@ PerformanceMonitor.PerformanceMonitor.MetricIndicator = class { ...@@ -545,8 +555,18 @@ PerformanceMonitor.PerformanceMonitor.MetricIndicator = class {
_toggleIndicator() { _toggleIndicator() {
this._active = !this._active; this._active = !this._active;
this.element.classList.toggle('active', this._active); this.element.classList.toggle('active', this._active);
UI.ARIAUtils.setChecked(this.element, this._active);
this._onToggle(this._active); this._onToggle(this._active);
} }
/**
* @param {!Event} event
*/
_handleKeypress(event) {
const keyboardEvent = /** @type {!KeyboardEvent} */ (event);
if (keyboardEvent.key === ' ' || keyboardEvent.key === 'Enter')
this._toggleIndicator();
}
}; };
PerformanceMonitor.PerformanceMonitor.MetricIndicator._format = PerformanceMonitor.PerformanceMonitor.MetricIndicator._format =
......
...@@ -55,6 +55,10 @@ ...@@ -55,6 +55,10 @@
background-color: #f8f8f8; background-color: #f8f8f8;
} }
.perfmon-indicator[data-keyboard-focus="true"]:focus {
background-color: #f8f8f8;
}
.perfmon-indicator-swatch { .perfmon-indicator-swatch {
margin-right: 6px; margin-right: 6px;
} }
......
...@@ -11,6 +11,13 @@ UI.ARIAUtils.markAsButton = function(element) { ...@@ -11,6 +11,13 @@ UI.ARIAUtils.markAsButton = function(element) {
element.setAttribute('role', 'button'); element.setAttribute('role', 'button');
}; };
/**
* @param {!Element} element
*/
UI.ARIAUtils.markAsCheckbox = function(element) {
element.setAttribute('role', 'checkbox');
};
/** /**
* @param {!Element} element * @param {!Element} element
*/ */
...@@ -108,6 +115,14 @@ UI.ARIAUtils.setControls = function(element, controlledElement) { ...@@ -108,6 +115,14 @@ UI.ARIAUtils.setControls = function(element, controlledElement) {
element.setAttribute('aria-controls', controlledElement.id); element.setAttribute('aria-controls', controlledElement.id);
}; };
/**
* @param {!Element} element
* @param {boolean} value
*/
UI.ARIAUtils.setChecked = function(element, value) {
element.setAttribute('aria-checked', !!value);
};
/** /**
* @param {!Element} element * @param {!Element} element
* @param {boolean} value * @param {boolean} value
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment