DevTools: add keyboard interactions to linkified nodes
- Replace usage of 'title' in the DOMLinkifier and AuditsReportRenderer
with 'tooltip'. This was a bug introduced during the initial DOMLinkifier
refactor, see: https://crrev.com/a74a3fc0
- Refactor to pass the options into the Elements.DOMLinkifier.linkifyNodeReference
instead of just the tooltip text, making it easier to extend and
following the options config pattern.
- DOM linkified nodes by default can take keyboard
focus by giving them a tab index and link activation on 'Enter' key.
- The nodes are also given the role of link for screen reader identification.
- API consumers can-opt out of keyboard focus by providing the option
{preventKeyboardFocus: true}; this is useful if link navigation is
handled by a context menu.
- Audits report nodes opt-in to keyboard focus.
- Performance event log nodes opt-in to keyboard focus.
- The following views are set to opt-out of keyboard focus to match their
existing behavior as a migration strategy; these views can opt-in gradually:
- AccessibilityNodeView
- StylesSidebarPane
This meets the following WCAG success criteria
1.3.1 Info and Relationships https://www.w3.org/WAI/WCAG21/quickref/#info-and-relationships
2.1.1 Keyboard https://www.w3.org/WAI/WCAG21/quickref/#keyboard
4.1.2 Name, Role, Value https://www.w3.org/WAI/WCAG21/quickref/#name-role-value
Screenshot (Audits tooltip): https://i.imgur.com/zgQUHC3.png
Screenshot (Audits Light Theme): https://i.imgur.com/2eB7pJ4.png
Screenshot (Audits Dark Theme): https://i.imgur.com/VfEFbAF.png
Screenshot (Performance Event Log): https://i.imgur.com/f9YcbC5.png
of the accessibility audit details to view a linkified node.
Test: Create an empty .html file, host it and run an accessibility audit, expand any
Bug: 963183
Change-Id: Ibc3acd4eccf03f408087248b1389d0dd807d9443
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1750153
Reviewed-by: Pavel Feldman <[email protected]>
Commit-Queue: Jeff Fisher <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#691276}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: c4f6092e42657a3b4544d70223b507fd39a4fd31
diff --git a/front_end/accessibility/AccessibilityNodeView.js b/front_end/accessibility/AccessibilityNodeView.js
index a8991b0..28996aa 100644
--- a/front_end/accessibility/AccessibilityNodeView.js
+++ b/front_end/accessibility/AccessibilityNodeView.js
@@ -508,7 +508,8 @@
valueElement = createElement('span');
element.appendChild(valueElement);
this._deferredNode.resolvePromise().then(node => {
- Common.Linkifier.linkify(node).then(linkfied => valueElement.appendChild(linkfied));
+ Common.Linkifier.linkify(node, {preventKeyboardFocus: true})
+ .then(linkfied => valueElement.appendChild(linkfied));
});
} else if (this._idref) {
element.classList.add('invalid');
diff --git a/front_end/animation/AnimationTimeline.js b/front_end/animation/AnimationTimeline.js
index a21cb84..fdf877e 100644
--- a/front_end/animation/AnimationTimeline.js
+++ b/front_end/animation/AnimationTimeline.js
@@ -678,7 +678,7 @@
/**
* @param {?SDK.DOMNode} node
*/
- async nodeResolved(node) {
+ nodeResolved(node) {
if (!node) {
this._description.createTextChild('<node>');
return;
diff --git a/front_end/audits/AuditsReportRenderer.js b/front_end/audits/AuditsReportRenderer.js
index 406bdf9..f3f1edf 100644
--- a/front_end/audits/AuditsReportRenderer.js
+++ b/front_end/audits/AuditsReportRenderer.js
@@ -52,8 +52,7 @@
if (!node)
continue;
- const element =
- await Common.Linkifier.linkify(node, /** @type {!Common.Linkifier.Options} */ ({title: detailsItem.snippet}));
+ const element = await Common.Linkifier.linkify(node, {tooltip: detailsItem.snippet});
origElement.title = '';
origElement.textContent = '';
origElement.appendChild(element);
diff --git a/front_end/common/ModuleExtensionInterfaces.js b/front_end/common/ModuleExtensionInterfaces.js
index 6d88d49..ce14692 100644
--- a/front_end/common/ModuleExtensionInterfaces.js
+++ b/front_end/common/ModuleExtensionInterfaces.js
@@ -121,7 +121,7 @@
.then(linkifier => linkifier.linkify(object, options));
};
-/** @typedef {{tooltip: string}} */
+/** @typedef {{tooltip: (string|undefined), preventKeyboardFocus: (boolean|undefined)}} */
Common.Linkifier.Options;
/**
diff --git a/front_end/elements/DOMLinkifier.js b/front_end/elements/DOMLinkifier.js
index ee9fd7c..a8301ac 100644
--- a/front_end/elements/DOMLinkifier.js
+++ b/front_end/elements/DOMLinkifier.js
@@ -61,10 +61,10 @@
/**
* @param {?SDK.DOMNode} node
- * @param {string=} tooltipContent
+ * @param {!Common.Linkifier.Options=} options
* @return {!Node}
*/
-Elements.DOMLinkifier.linkifyNodeReference = function(node, tooltipContent) {
+Elements.DOMLinkifier.linkifyNodeReference = function(node, options = {}) {
if (!node)
return createTextNode(Common.UIString('<node>'));
@@ -72,20 +72,27 @@
const shadowRoot = UI.createShadowRootWithCoreStyles(root, 'elements/domLinkifier.css');
const link = shadowRoot.createChild('div', 'node-link');
- Elements.DOMLinkifier.decorateNodeLabel(node, link, tooltipContent);
+ Elements.DOMLinkifier.decorateNodeLabel(node, link, options.tooltip);
link.addEventListener('click', () => Common.Revealer.reveal(node, false) && false, false);
link.addEventListener('mouseover', node.highlight.bind(node, undefined), false);
link.addEventListener('mouseleave', () => SDK.OverlayModel.hideDOMNodeHighlight(), false);
+ if (!options.preventKeyboardFocus) {
+ link.addEventListener('keydown', event => isEnterKey(event) && Common.Revealer.reveal(node, false) && false);
+ link.tabIndex = 0;
+ UI.ARIAUtils.markAsLink(link);
+ }
+
return root;
};
/**
* @param {!SDK.DeferredDOMNode} deferredNode
+ * @param {!Common.Linkifier.Options=} options
* @return {!Node}
*/
-Elements.DOMLinkifier.linkifyDeferredNodeReference = function(deferredNode) {
+Elements.DOMLinkifier.linkifyDeferredNodeReference = function(deferredNode, options = {}) {
const root = createElement('div');
const shadowRoot = UI.createShadowRootWithCoreStyles(root, 'elements/domLinkifier.css');
const link = shadowRoot.createChild('div', 'node-link');
@@ -93,6 +100,12 @@
link.addEventListener('click', deferredNode.resolve.bind(deferredNode, onDeferredNodeResolved), false);
link.addEventListener('mousedown', e => e.consume(), false);
+ if (!options.preventKeyboardFocus) {
+ link.addEventListener('keydown', event => isEnterKey(event) && deferredNode.resolve(onDeferredNodeResolved));
+ link.tabIndex = 0;
+ UI.ARIAUtils.markAsLink(link);
+ }
+
/**
* @param {?SDK.DOMNode} node
*/
@@ -115,9 +128,9 @@
*/
linkify(object, options) {
if (object instanceof SDK.DOMNode)
- return Elements.DOMLinkifier.linkifyNodeReference(object, options ? options.title : undefined);
+ return Elements.DOMLinkifier.linkifyNodeReference(object, options);
if (object instanceof SDK.DeferredDOMNode)
- return Elements.DOMLinkifier.linkifyDeferredNodeReference(object);
+ return Elements.DOMLinkifier.linkifyDeferredNodeReference(object, options);
throw new Error('Can\'t linkify non-node');
}
};
diff --git a/front_end/elements/StylesSidebarPane.js b/front_end/elements/StylesSidebarPane.js
index 61392fd..7bd9262 100644
--- a/front_end/elements/StylesSidebarPane.js
+++ b/front_end/elements/StylesSidebarPane.js
@@ -782,7 +782,7 @@
const separatorElement = createElement('div');
separatorElement.className = 'sidebar-separator';
separatorElement.createTextChild(ls`Inherited from${' '}`);
- const link = await Common.Linkifier.linkify(node);
+ const link = await Common.Linkifier.linkify(node, {preventKeyboardFocus: true});
separatorElement.appendChild(link);
return new Elements.SectionBlock(separatorElement);
}
@@ -932,7 +932,7 @@
return createTextNode(Common.UIString('via inspector'));
if (header && header.ownerNode) {
- const link = Elements.DOMLinkifier.linkifyDeferredNodeReference(header.ownerNode);
+ const link = Elements.DOMLinkifier.linkifyDeferredNodeReference(header.ownerNode, {preventKeyboardFocus: true});
link.textContent = '<style>';
return link;
}
diff --git a/front_end/elements/domLinkifier.css b/front_end/elements/domLinkifier.css
index 30aada8..7e74f1d 100644
--- a/front_end/elements/domLinkifier.css
+++ b/front_end/elements/domLinkifier.css
@@ -14,6 +14,10 @@
pointer-events: auto;
}
+.node-link[data-keyboard-focus="true"]:focus {
+ outline-width: unset;
+}
+
.node-label-name {
color: rgb(136, 18, 128);
}
diff --git a/front_end/elements/elementsTreeOutline.css b/front_end/elements/elementsTreeOutline.css
index ca84286..6f81f43 100644
--- a/front_end/elements/elementsTreeOutline.css
+++ b/front_end/elements/elementsTreeOutline.css
@@ -272,19 +272,10 @@
padding-left: 2px;
}
-.elements-tree-shortcut-title {
+.elements-tree-shortcut-title, .elements-tree-shortcut-link {
color: rgb(87, 87, 87);
}
-ol:hover > li > .elements-tree-shortcut-link {
- display: initial;
-}
-
-.elements-tree-shortcut-link {
- color: rgb(87, 87, 87);
- display: none;
-}
-
.elements-disclosure .gutter-container {
position: absolute;
top: 0;