215 lines
7.0 KiB
JavaScript
215 lines
7.0 KiB
JavaScript
// Import helper functions from background-commands
|
|
import { getFromContentScript, respondWith, respondWithError, attachDebugger } from './background-commands.js';
|
|
|
|
// Additional helper functions
|
|
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
// Send keypress event
|
|
export async function keypress({tabId}, params) {
|
|
const { key, selector, xpath, delay = 50 } = params;
|
|
|
|
if (!key) {
|
|
return respondWithError(tabId, 'KEY_REQUIRED', 'Key parameter is required');
|
|
}
|
|
|
|
// Validate delay is within reasonable bounds (0-60000ms = 0-60 seconds)
|
|
const keypressDelay = Math.max(0, Math.min(60000, delay));
|
|
|
|
try {
|
|
// First, focus the target element if selector/xpath provided
|
|
if (selector || xpath) {
|
|
const focusResult = await getFromContentScript(tabId, 'focus', { selector, xpath });
|
|
if (focusResult.error) {
|
|
return focusResult;
|
|
}
|
|
}
|
|
|
|
// Parse key combination to extract key and modifiers
|
|
const keyData = parseKeyCombination(key);
|
|
|
|
// Use attachDebugger helper to manage debugger lifecycle
|
|
return await attachDebugger(tabId, async () => {
|
|
// Helper function to send key event
|
|
const sendKeyEvent = async (type, autoRepeat = false, text = null) => {
|
|
const eventData = {
|
|
type,
|
|
...keyData,
|
|
windowsVirtualKeyCode: keyData.keyCode,
|
|
nativeVirtualKeyCode: keyData.keyCode
|
|
};
|
|
|
|
if (type === 'keyDown' || type === 'keyUp') {
|
|
eventData.autoRepeat = autoRepeat;
|
|
}
|
|
|
|
if (text) {
|
|
eventData.text = text;
|
|
}
|
|
|
|
await chrome.debugger.sendCommand({ tabId }, 'Input.dispatchKeyEvent', eventData);
|
|
};
|
|
|
|
// Send keydown event
|
|
await sendKeyEvent('keyDown', false);
|
|
|
|
// If delay > 500ms, simulate key repeat
|
|
if (keypressDelay > 500) {
|
|
// Calculate number of repeat events based on delay
|
|
// Typical repeat rate is ~30ms between events
|
|
const repeatInterval = 30;
|
|
const repeatCount = Math.floor((keypressDelay - 100) / repeatInterval);
|
|
|
|
// Wait a bit before starting repeat (typical OS behavior)
|
|
await wait(50);
|
|
|
|
// Send repeated keydown events
|
|
for (let i = 0; i < repeatCount; i++) {
|
|
await sendKeyEvent('keyDown', true);
|
|
|
|
// Wait between repeat events
|
|
if (i < repeatCount - 1) {
|
|
await wait(repeatInterval);
|
|
}
|
|
}
|
|
} else if (keypressDelay > 0) {
|
|
// For delays <= 500ms, just wait the specified time
|
|
await wait(keypressDelay);
|
|
}
|
|
|
|
// Send keyup event
|
|
await sendKeyEvent('keyUp');
|
|
|
|
// Wait a bit for the page to process the key events
|
|
await wait(50);
|
|
|
|
// Keys that typically cause scrolling need extra time to settle
|
|
const scrollKeys = ['PageUp', 'PageDown', 'Space', ' ', 'ArrowUp', 'ArrowDown', 'Home', 'End'];
|
|
if (!selector && !xpath && scrollKeys.includes(keyData.key)) {
|
|
// Additional delay for scroll to complete when key is pressed globally (not on a specific element)
|
|
await wait(100);
|
|
}
|
|
|
|
// Get result including element info if selector was provided
|
|
const resultData = {
|
|
keyPressed: true,
|
|
key: key,
|
|
delay: keypressDelay
|
|
};
|
|
|
|
// If this was an auto-repeat key press, include that info
|
|
if (keypressDelay > 500) {
|
|
resultData.autoRepeat = true;
|
|
resultData.repeatCount = Math.floor((keypressDelay - 100) / 30);
|
|
}
|
|
|
|
return await respondWith(tabId, resultData, selector, xpath);
|
|
});
|
|
} catch (error) {
|
|
return respondWithError(tabId, 'KEYPRESS_FAILED', error.message, selector, xpath);
|
|
}
|
|
}
|
|
|
|
// Parse key combination and return CDP-compatible key data
|
|
function parseKeyCombination(keyCombination) {
|
|
let modifiers = 0;
|
|
let key = '';
|
|
|
|
// CDP modifier flags
|
|
const CDP_MODIFIERS = {
|
|
alt: 1,
|
|
ctrl: 2,
|
|
meta: 4,
|
|
shift: 8
|
|
};
|
|
|
|
// Special key mappings
|
|
const KEY_MAPPINGS = {
|
|
'enter': { key: 'Enter', code: 'Enter', keyCode: 13 },
|
|
'return': { key: 'Enter', code: 'Enter', keyCode: 13 },
|
|
'tab': { key: 'Tab', code: 'Tab', keyCode: 9 },
|
|
'delete': { key: 'Delete', code: 'Delete', keyCode: 46 },
|
|
'backspace': { key: 'Backspace', code: 'Backspace', keyCode: 8 },
|
|
'escape': { key: 'Escape', code: 'Escape', keyCode: 27 },
|
|
'esc': { key: 'Escape', code: 'Escape', keyCode: 27 },
|
|
'space': { key: ' ', code: 'Space', keyCode: 32, text: ' ' },
|
|
' ': { key: ' ', code: 'Space', keyCode: 32, text: ' ' },
|
|
'arrowup': { key: 'ArrowUp', code: 'ArrowUp', keyCode: 38 },
|
|
'arrowdown': { key: 'ArrowDown', code: 'ArrowDown', keyCode: 40 },
|
|
'arrowleft': { key: 'ArrowLeft', code: 'ArrowLeft', keyCode: 37 },
|
|
'arrowright': { key: 'ArrowRight', code: 'ArrowRight', keyCode: 39 },
|
|
'pageup': { key: 'PageUp', code: 'PageUp', keyCode: 33 },
|
|
'pagedown': { key: 'PageDown', code: 'PageDown', keyCode: 34 },
|
|
'home': { key: 'Home', code: 'Home', keyCode: 36 },
|
|
'end': { key: 'End', code: 'End', keyCode: 35 },
|
|
'insert': { key: 'Insert', code: 'Insert', keyCode: 45 },
|
|
'f1': { key: 'F1', code: 'F1', keyCode: 112 },
|
|
'f2': { key: 'F2', code: 'F2', keyCode: 113 },
|
|
'f3': { key: 'F3', code: 'F3', keyCode: 114 },
|
|
'f4': { key: 'F4', code: 'F4', keyCode: 115 },
|
|
'f5': { key: 'F5', code: 'F5', keyCode: 116 },
|
|
'f6': { key: 'F6', code: 'F6', keyCode: 117 },
|
|
'f7': { key: 'F7', code: 'F7', keyCode: 118 },
|
|
'f8': { key: 'F8', code: 'F8', keyCode: 119 },
|
|
'f9': { key: 'F9', code: 'F9', keyCode: 120 },
|
|
'f10': { key: 'F10', code: 'F10', keyCode: 121 },
|
|
'f11': { key: 'F11', code: 'F11', keyCode: 122 },
|
|
'f12': { key: 'F12', code: 'F12', keyCode: 123 }
|
|
};
|
|
|
|
// Parse modifiers from combination
|
|
let remaining = keyCombination;
|
|
const modifierPatterns = [
|
|
{ pattern: /^(Control|Ctrl)\+/i, modifier: 'ctrl' },
|
|
{ pattern: /^Shift\+/i, modifier: 'shift' },
|
|
{ pattern: /^Alt|Option\+/i, modifier: 'alt' },
|
|
{ pattern: /^(Meta|Cmd|Command)\+/i, modifier: 'meta' }
|
|
];
|
|
|
|
// Extract modifiers
|
|
let foundModifier = true;
|
|
while (foundModifier && remaining) {
|
|
foundModifier = false;
|
|
for (const { pattern, modifier } of modifierPatterns) {
|
|
if (pattern.test(remaining)) {
|
|
modifiers |= CDP_MODIFIERS[modifier];
|
|
remaining = remaining.replace(pattern, '');
|
|
foundModifier = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// What's left is the key
|
|
key = remaining;
|
|
|
|
// Look up special key mapping
|
|
const lowerKey = key.toLowerCase();
|
|
if (KEY_MAPPINGS[lowerKey]) {
|
|
return {
|
|
...KEY_MAPPINGS[lowerKey],
|
|
modifiers,
|
|
text: KEY_MAPPINGS[lowerKey].text || undefined
|
|
};
|
|
}
|
|
|
|
// For single character keys
|
|
if (key.length === 1) {
|
|
const keyCode = key.toUpperCase().charCodeAt(0);
|
|
return {
|
|
key: key,
|
|
code: 'Key' + key.toUpperCase(),
|
|
keyCode: keyCode,
|
|
modifiers,
|
|
text: modifiers === 0 ? key : undefined // Only send text for unmodified keys
|
|
};
|
|
}
|
|
|
|
// For other keys, try to generate a reasonable code
|
|
return {
|
|
key: key,
|
|
code: key,
|
|
keyCode: 0, // Unknown keycode
|
|
modifiers
|
|
};
|
|
}
|