Automatically generate context string for restricted keydown tracking.

Bug: chromium:1467464
Change-Id: Iffccf013520e434360f2776071eb9eae09b15d7f
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5352348
Reviewed-by: Ergün Erdoğmuş <ergunsh@chromium.org>
Commit-Queue: Ergün Erdoğmuş <ergunsh@chromium.org>
Auto-Submit: Danil Somsikov <dsv@chromium.org>
diff --git a/front_end/ui/legacy/Dialog.ts b/front_end/ui/legacy/Dialog.ts
index 4bd1326..7a806bc 100644
--- a/front_end/ui/legacy/Dialog.ts
+++ b/front_end/ui/legacy/Dialog.ts
@@ -55,7 +55,8 @@
     this.contentElement.tabIndex = 0;
     this.contentElement.addEventListener('focus', () => this.widget().focus(), false);
     if (jslogContext) {
-      this.contentElement.setAttribute('jslog', `${VisualLogging.dialog(jslogContext).track({resize: true})}`);
+      this.contentElement.setAttribute(
+          'jslog', `${VisualLogging.dialog(jslogContext).track({resize: true, keydown: 'Escape'})}`);
     }
     this.widget().setDefaultFocusedElement(this.contentElement);
     this.setPointerEventsBehavior(PointerEventsBehavior.BlockedByGlassPane);
diff --git a/front_end/ui/legacy/Infobar.ts b/front_end/ui/legacy/Infobar.ts
index 64c611b..981a7d8 100644
--- a/front_end/ui/legacy/Infobar.ts
+++ b/front_end/ui/legacy/Infobar.ts
@@ -60,7 +60,8 @@
       jslogContext?: string) {
     this.element = document.createElement('div');
     if (jslogContext) {
-      this.element.setAttribute('jslog', `${VisualLogging.dialog(jslogContext).track({resize: true})}`);
+      this.element.setAttribute(
+          'jslog', `${VisualLogging.dialog(jslogContext).track({resize: true, keydown: 'Enter|Escape'})}`);
     }
     this.element.classList.add('flex-none');
     this.shadowRoot =
diff --git a/front_end/ui/legacy/ListControl.ts b/front_end/ui/legacy/ListControl.ts
index 5c3b9c0..f9b0d2c 100644
--- a/front_end/ui/legacy/ListControl.ts
+++ b/front_end/ui/legacy/ListControl.ts
@@ -361,7 +361,8 @@
     if (!element) {
       element = this.delegate.createElementForItem(item);
       if (!element.hasAttribute('jslog')) {
-        element.setAttribute('jslog', `${VisualLogging.item().track({click: true})}`);
+        element.setAttribute(
+            'jslog', `${VisualLogging.item().track({click: true, keydown: 'ArrowUp|ArrowDown|PageUp|PageDown'})}`);
       }
       this.itemToElement.set(item, element);
       this.updateElementARIA(element, index);
diff --git a/front_end/ui/legacy/ListWidget.ts b/front_end/ui/legacy/ListWidget.ts
index 8d9de8b..b6eae63 100644
--- a/front_end/ui/legacy/ListWidget.ts
+++ b/front_end/ui/legacy/ListWidget.ts
@@ -370,7 +370,7 @@
     const input = (createInput('', type) as HTMLInputElement);
     input.placeholder = title;
     input.addEventListener('input', this.validateControls.bind(this, false), false);
-    input.setAttribute('jslog', `${VisualLogging.textField().track({keydown: true}).context(name)}`);
+    input.setAttribute('jslog', `${VisualLogging.textField().track({change: true, keydown: 'Enter'}).context(name)}`);
     ARIAUtils.setLabel(input, title);
     this.controlByName.set(name, input);
     this.controls.push(input);
diff --git a/front_end/ui/legacy/SearchableView.ts b/front_end/ui/legacy/SearchableView.ts
index d8b51e8..d1cdaa4 100644
--- a/front_end/ui/legacy/SearchableView.ts
+++ b/front_end/ui/legacy/SearchableView.ts
@@ -153,7 +153,9 @@
     this.searchInputElement.classList.add('search-replace', 'custom-search-input');
     this.searchInputElement.id = 'search-input-field';
     this.searchInputElement.placeholder = i18nString(UIStrings.findString);
-    this.searchInputElement.setAttribute('jslog', `${VisualLogging.textField('search').track({change: true})}`);
+    this.searchInputElement.setAttribute(
+        'jslog',
+        `${VisualLogging.textField('search').track({change: true, keydown: 'ArrowUp|ArrowDown|Enter|Escape'})}`);
     searchControlElement.appendChild(this.searchInputElement);
 
     this.matchesElement = searchControlElement.createChild('label', 'search-results-matches');
@@ -183,7 +185,8 @@
         (searchInputElements.createChild('input', 'search-replace toolbar-replace-control hidden') as HTMLInputElement);
     this.replaceInputElement.addEventListener('keydown', this.onReplaceKeyDown.bind(this), true);
     this.replaceInputElement.placeholder = i18nString(UIStrings.replace);
-    this.replaceInputElement.setAttribute('jslog', `${VisualLogging.textField('replace').track({change: true})}`);
+    this.replaceInputElement.setAttribute(
+        'jslog', `${VisualLogging.textField('replace').track({change: true, keydown: 'Enter'})}`);
 
     this.buttonsContainer = this.footerElement.createChild('div', 'toolbar-search-buttons');
     const firstRowButtons = this.buttonsContainer.createChild('div', 'first-row-buttons');
diff --git a/front_end/ui/legacy/SoftContextMenu.ts b/front_end/ui/legacy/SoftContextMenu.ts
index 326bbc0..77c39d5 100644
--- a/front_end/ui/legacy/SoftContextMenu.ts
+++ b/front_end/ui/legacy/SoftContextMenu.ts
@@ -121,7 +121,9 @@
     this.glassPane.setAnchorBehavior(this.parentMenu ? AnchorBehavior.PreferRight : AnchorBehavior.PreferBottom);
 
     this.contextMenuElement = this.glassPane.contentElement.createChild('div', 'soft-context-menu');
-    this.contextMenuElement.setAttribute('jslog', `${VisualLogging.menu().track({resize: true}).parent('mapped')}`);
+    this.contextMenuElement.setAttribute('jslog', `${VisualLogging.menu().track({resize: true}).parent('mapped').track({
+                                           keydown: 'ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Enter|Space|Escape',
+                                         })}`);
     if (this.loggableParent) {
       VisualLogging.setMappedParent(this.contextMenuElement, this.loggableParent);
     }
diff --git a/front_end/ui/legacy/SoftDropDown.ts b/front_end/ui/legacy/SoftDropDown.ts
index 6e75a58..762a193 100644
--- a/front_end/ui/legacy/SoftDropDown.ts
+++ b/front_end/ui/legacy/SoftDropDown.ts
@@ -77,7 +77,9 @@
         .appendChild(this.list.element);
     ARIAUtils.markAsMenu(this.list.element);
     VisualLogging.setMappedParent(this.list.element, this.element);
-    this.list.element.setAttribute('jslog', `${VisualLogging.menu().parent('mapped').track({resize: true})}`);
+    this.list.element.setAttribute(
+        'jslog',
+        `${VisualLogging.menu().parent('mapped').track({resize: true, keydown: 'ArrowUp|ArrowDown|PageUp|PageDown'})}`);
 
     this.listWasShowing200msAgo = false;
     this.element.addEventListener('mousedown', event => {
diff --git a/front_end/ui/legacy/Treeoutline.ts b/front_end/ui/legacy/Treeoutline.ts
index f78cbf4..25d7a4c 100644
--- a/front_end/ui/legacy/Treeoutline.ts
+++ b/front_end/ui/legacy/Treeoutline.ts
@@ -470,7 +470,9 @@
     this.listItemNode.addEventListener('click', (this.treeElementToggled.bind(this) as EventListener), false);
     this.listItemNode.addEventListener('dblclick', this.handleDoubleClick.bind(this), false);
     this.listItemNode.setAttribute(
-        'jslog', `${VisualLogging.treeItem().parent('parentTreeItem').context(jslogContext)}`);
+        'jslog', `${VisualLogging.treeItem().parent('parentTreeItem').context(jslogContext).track({
+          keydown: 'ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End',
+        })}`);
     ARIAUtils.markAsTreeitem(this.listItemNode);
 
     this.childrenInternal = null;
diff --git a/front_end/ui/legacy/UIUtils.ts b/front_end/ui/legacy/UIUtils.ts
index 65240d6..b87a89a 100644
--- a/front_end/ui/legacy/UIUtils.ts
+++ b/front_end/ui/legacy/UIUtils.ts
@@ -1108,7 +1108,8 @@
     element.type = type;
   }
   if (jslogContext) {
-    element.setAttribute('jslog', `${VisualLogging.textField().track({keydown: true}).context(jslogContext)}`);
+    element.setAttribute(
+        'jslog', `${VisualLogging.textField().track({keydown: 'Enter', change: true}).context(jslogContext)}`);
   }
   return element;
 }
diff --git a/front_end/ui/legacy/ViewManager.ts b/front_end/ui/legacy/ViewManager.ts
index 14debbc..98e94f8 100644
--- a/front_end/ui/legacy/ViewManager.ts
+++ b/front_end/ui/legacy/ViewManager.ts
@@ -424,8 +424,10 @@
 
     this.titleElement = document.createElement('div');
     this.titleElement.classList.add('expandable-view-title');
-    this.titleElement.setAttribute(
-        'jslog', `${VisualLogging.sectionHeader().context(view.viewId()).track({click: true})}`);
+    this.titleElement.setAttribute('jslog', `${VisualLogging.sectionHeader().context(view.viewId()).track({
+                                     click: true,
+                                     keydown: 'Enter|Space|ArrowLeft|ArrowRight',
+                                   })}`);
     ARIAUtils.markAsTreeitem(this.titleElement);
     this.titleExpandIcon = IconButton.Icon.create('triangle-right', 'title-expand-icon');
     this.titleElement.appendChild(this.titleExpandIcon);
diff --git a/front_end/ui/legacy/XLink.ts b/front_end/ui/legacy/XLink.ts
index 17eb35b..d60fb0e 100644
--- a/front_end/ui/legacy/XLink.ts
+++ b/front_end/ui/legacy/XLink.ts
@@ -34,7 +34,7 @@
     // TODO(dgozman): migrate css from 'devtools-link' to 'x-link'.
     const element = html `
   <x-link href='${url}' tabindex="0" class='${className} devtools-link' ${preventClick ? 'no-click' : ''}
-  jslog=${VisualLogging.link().track({click: true}).context(jsLogContext)}>${Platform.StringUtilities.trimMiddle(linkText, MaxLengthForDisplayedURLs)}</x-link>`;
+  jslog=${VisualLogging.link().track({click: true, keydown:'Enter|Space'}).context(jsLogContext)}>${Platform.StringUtilities.trimMiddle(linkText, MaxLengthForDisplayedURLs)}</x-link>`;
     // clang-format on
     return element as HTMLElement;
   }
diff --git a/front_end/ui/visual_logging/LoggingEvents.test.ts b/front_end/ui/visual_logging/LoggingEvents.test.ts
index 6656d6f..645e56a 100644
--- a/front_end/ui/visual_logging/LoggingEvents.test.ts
+++ b/front_end/ui/visual_logging/LoggingEvents.test.ts
@@ -95,11 +95,11 @@
         Host.InspectorFrontendHost.InspectorFrontendHostInstance,
         'recordKeyDown',
     );
-    const event = new KeyboardEvent('keydown', {code: 'Enter'});
+    const event = new KeyboardEvent('keydown', {code: 'Enter', key: 'Enter'});
     sinon.stub(event, 'currentTarget').value(element);
     void VisualLogging.LoggingEvents.logKeyDown(throttler, ['Enter', 'Escape'])(event);
     await assertThrottled(recordKeyDown);
-    assert.deepStrictEqual(stabilizeEvent(recordKeyDown.firstCall.firstArg), {veid: 0});
+    assert.deepStrictEqual(stabilizeEvent(recordKeyDown.firstCall.firstArg), {veid: 0, context: 513111094});
   });
 
   it('calls UI binding to log a keydown with an provided context', async () => {
diff --git a/front_end/ui/visual_logging/LoggingEvents.ts b/front_end/ui/visual_logging/LoggingEvents.ts
index e9b964d..a232246 100644
--- a/front_end/ui/visual_logging/LoggingEvents.ts
+++ b/front_end/ui/visual_logging/LoggingEvents.ts
@@ -100,6 +100,9 @@
       }
       const loggingState = event?.currentTarget ? getLoggingState(event.currentTarget) : null;
       const keyDownEvent: Host.InspectorFrontendHostAPI.KeyDownEvent = {veid: loggingState?.veid};
+      if (!context && codes?.length) {
+        context = contextFromKeyCodes(event);
+      }
       if (context) {
         keyDownEvent.context = await contextAsNumber(context);
       }
@@ -109,6 +112,27 @@
       });
     };
 
+function contextFromKeyCodes(event: Event): string|undefined {
+  if (!(event instanceof KeyboardEvent)) {
+    return undefined;
+  }
+  const components = [];
+  if (event.shiftKey) {
+    components.push('shift');
+  }
+  if (event.ctrlKey) {
+    components.push('ctrl');
+  }
+  if (event.altKey) {
+    components.push('alt');
+  }
+  if (event.metaKey) {
+    components.push('meta');
+  }
+  components.push(event.key.toLowerCase());
+  return components.join('-');
+}
+
 async function contextAsNumber(context: string|undefined): Promise<number|undefined> {
   if (typeof context === 'undefined') {
     return undefined;