repo_description: Chrome DevTools for coding agents
repo_topics: browser chrome chrome-devtools debugging devtools mcp mcp-server puppeteer
code: /**
   * @license
   * Copyright 2025 Google LLC
   * SPDX-License-Identifier: Apache-2.0
   */
  
  import type {KeyInput} from '../third_party/index.js';
  
  // See the KeyInput type for the list of supported keys.
  const validKeys = new Set([
    '0',
    '1',
    '2',
    '3',
    '4',
    '5',
    '6',
    '7',
    '8',
    '9',
    'Power',
    'Eject',
    'Abort',
    'Help',
    'Backspace',
    'Tab',
    'Numpad5',
    'NumpadEnter',
    'Enter',
    '\r',
    '\n',
    'ShiftLeft',
    'ShiftRight',
    'ControlLeft',
    'ControlRight',
    'AltLeft',
    'AltRight',
    'Pause',
    'CapsLock',
    'Escape',
    'Convert',
    'NonConvert',
    'Space',
    'Numpad9',
    'PageUp',
    'Numpad3',
    'PageDown',
    'End',
    'Numpad1',
    'Home',
    'Numpad7',
    'ArrowLeft',
    'Numpad4',
    'Numpad8',
    'ArrowUp',
    'ArrowRight',
    'Numpad6',
    'Numpad2',
    'ArrowDown',
    'Select',
    'Open',
    'PrintScreen',
    'Insert',
    'Numpad0',
    'Delete',
    'NumpadDecimal',
    'Digit0',
    'Digit1',
    'Digit2',
    'Digit3',
    'Digit4',
    'Digit5',
    'Digit6',
    'Digit7',
    'Digit8',
    'Digit9',
    'KeyA',
    'KeyB',
    'KeyC',
    'KeyD',
    'KeyE',
    'KeyF',
    'KeyG',
    'KeyH',
    'KeyI',
    'KeyJ',
    'KeyK',
    'KeyL',
    'KeyM',
    'KeyN',
    'KeyO',
    'KeyP',
    'KeyQ',
    'KeyR',
    'KeyS',
    'KeyT',
    'KeyU',
    'KeyV',
    'KeyW',
    'KeyX',
    'KeyY',
    'KeyZ',
    'MetaLeft',
    'MetaRight',
    'ContextMenu',
    'NumpadMultiply',
    'NumpadAdd',
    'NumpadSubtract',
    'NumpadDivide',
    'F1',
    'F2',
    'F3',
    'F4',
    'F5',
    'F6',
    'F7',
    'F8',
    'F9',
    'F10',
    'F11',
    'F12',
    'F13',
    'F14',
    'F15',
    'F16',
    'F17',
    'F18',
    'F19',
    'F20',
    'F21',
    'F22',
    'F23',
    'F24',
    'NumLock',
    'ScrollLock',
    'AudioVolumeMute',
    'AudioVolumeDown',
    'AudioVolumeUp',
    'MediaTrackNext',
    'MediaTrackPrevious',
    'MediaStop',
    'MediaPlayPause',
    'Semicolon',
    'Equal',
    'NumpadEqual',
    'Comma',
    'Minus',
    'Period',
    'Slash',
    'Backquote',
    'BracketLeft',
    'Backslash',
    'BracketRight',
    'Quote',
    'AltGraph',
    'Props',
    'Cancel',
    'Clear',
    'Shift',
    'Control',
    'Alt',
    'Accept',
    'ModeChange',
    ' ',
    'Print',
    'Execute',
    '\u0000',
    'a',
    'b',
    'c',
    'd',
    'e',
    'f',
    'g',
    'h',
    'i',
    'j',
    'k',
    'l',
    'm',
    'n',
    'o',
    'p',
    'q',
    'r',
    's',
    't',
    'u',
    'v',
    'w',
    'x',
    'y',
    'z',
    'Meta',
    '*',
    '+',
    '-',
    '/',
    ';',
    '=',
    ',',
    '.',
    '`',
    '[',
    '\\',
    ']',
    "'",
    'Attn',
    'CrSel',
    'ExSel',
    'EraseEof',
    'Play',
    'ZoomOut',
    ')',
    '!',
    '@',
    '#',
    '$',
    '%',
    '^',
    '&',
    '(',
    'A',
    'B',
    'C',
    'D',
    'E',
    'F',
    'G',
    'H',
    'I',
    'J',
    'K',
    'L',
    'M',
    'N',
    'O',
    'P',
    'Q',
    'R',
    'S',
    'T',
    'U',
    'V',
    'W',
    'X',
    'Y',
    'Z',
    ':',
    '<',
    '_',
    '>',
    '?',
    '~',
    '{',
    '|',
    '}',
    '"',
    'SoftLeft',
    'SoftRight',
    'Camera',
    'Call',
    'EndCall',
    'VolumeDown',
    'VolumeUp',
  ]);
  
  function throwIfInvalidKey(key: string): KeyInput {
    if (validKeys.has(key)) {
      return key as KeyInput;
    }
    throw new Error(
      `${key} is invalid. Valid keys are: ${Array.from(validKeys.values()).join(',')}.`,
    );
  }
  
  /**
   * Returns the primary key, followed by modifiers in original order.
   */
  export function parseKey(keyInput: string): [KeyInput, ...KeyInput[]] {
    let key = '';
    const result: KeyInput[] = [];
    for (const ch of keyInput) {
      // Handle cases like Shift++.
      if (ch === '+' && key) {
        result.push(throwIfInvalidKey(key));
        key = '';
      } else {
        key += ch;
      }
    }
    if (key) {
      result.push(throwIfInvalidKey(key));
    }
  
    if (result.length === 0) {
      throw new Error(`Key ${keyInput} could not be parsed.`);
    }
  
    if (new Set(result).size !== result.length) {
      throw new Error(`Key ${keyInput} contains duplicate keys.`);
    }
  
    return [result.at(-1), ...result.slice(0, -1)] as [KeyInput, ...KeyInput[]];
  }
  