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;