Files
playwriter/extension-kapture/modules/background-click.js
Tommy D. Rossi ec02a90812 adding examples
2025-11-13 19:42:44 +01:00

123 lines
5.1 KiB
JavaScript

// Import helper functions from background-commands
import { getFromContentScript, respondWith, respondWithError, attachDebugger, getElement } from './background-commands.js';
export async function click({tabId, mousePosition}, { selector, xpath }) {
return await hover({ tabId, mousePosition }, { selector, xpath }, true);
}
export async function hover(tab, { selector, xpath }, click = false) {
const { tabId, mousePosition } = tab;
// Validate that either selector or xpath is provided
if (!selector && !xpath) {
return respondWithError(tabId, 'SELECTOR_OR_XPATH_REQUIRED', 'Either selector or xpath is required');
}
// Get element and validate it exists and is visible
const elementResult = await getElement(tabId, selector, xpath, true);
if (elementResult.error) return elementResult;
// Get current mouse position
const currentPosition = mousePosition || { x: 0, y: 0 };
// Calculate target position (center of element)
const targetX = elementResult.element.bounds.x + elementResult.element.bounds.width / 2;
const targetY = elementResult.element.bounds.y + elementResult.element.bounds.height / 2;
try {
// Show cursor
await getFromContentScript(tabId, '_cursor', { show: true });
// Set initial cursor position
await getFromContentScript(tabId, '_moveMouseSVG', { x: currentPosition.x, y: currentPosition.y });
// Animation configuration
const pixelsPerSecond = 1000; // Adjust for desired speed
const frameInterval = 16; // ~60fps
await attachDebugger(tabId, async () => {
const sendCmd = (cmd, params) => chrome.debugger.sendCommand({ tabId }, cmd, params);
const dispatchMouseEvent = (params) => sendCmd('Input.dispatchMouseEvent', params);
// Enable Input domain for mouse events
// await sendCmd('Input.enable');
// Function to animate to a position
const animateToPosition = async (fromX, fromY, toX, toY) => {
const distance = Math.sqrt(Math.pow(toX - fromX, 2) + Math.pow(toY - fromY, 2));
const duration = Math.min(1000, (distance / pixelsPerSecond) * 1000);
const animSteps = Math.max(1, Math.ceil(duration / frameInterval));
const dx = (toX - fromX) / animSteps;
const dy = (toY - fromY) / animSteps;
for (let i = 1; i <= animSteps; i++) {
const x = fromX + (dx * i);
const y = fromY + (dy * i);
// Move visual cursor
await getFromContentScript(tabId, '_moveMouseSVG', { x, y });
// Send mouse move event
await dispatchMouseEvent({ type: 'mouseMoved', x, y });
// Wait for next frame
await new Promise(resolve => setTimeout(resolve, frameInterval));
}
return { x: toX, y: toY };
};
// Initial animation to target
let finalPosition = await animateToPosition(currentPosition.x, currentPosition.y, targetX, targetY);
// Try up to 5 times to ensure we're over the target element
const maxAttempts = 5;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const finalCheck = await getElement(tabId, selector, xpath, true);
if (!finalCheck.error && finalCheck.element) {
const bounds = finalCheck.element.bounds;
// Check if cursor is actually over the element
const isOverElement = finalPosition.x >= bounds.x &&
finalPosition.x <= bounds.x + bounds.width &&
finalPosition.y >= bounds.y &&
finalPosition.y <= bounds.y + bounds.height;
if (!isOverElement) {
// Calculate new target center and animate to it
const actualTargetX = bounds.x + bounds.width / 2;
const actualTargetY = bounds.y + bounds.height / 2;
finalPosition = await animateToPosition(finalPosition.x, finalPosition.y, actualTargetX, actualTargetY);
} else {
// Cursor is over the element, we're done
break;
}
} else {
// Element not found, stop trying
break;
}
}
const bounds = await getFromContentScript(tabId, '_elementPosition', { id: elementResult.element.id });
const actualTargetX = bounds.x + bounds.width / 2;
const actualTargetY = bounds.y + bounds.height / 2;
await getFromContentScript(tabId, '_moveMouseSVG', { x: actualTargetX, y: actualTargetY });
if (click) {
await dispatchMouseEvent({type: 'mousePressed', x: actualTargetX, y: actualTargetY, button: 'left', clickCount: 1});
await dispatchMouseEvent({type: 'mouseReleased', x: actualTargetX, y: actualTargetY, button: 'left', clickCount: 1});
}
});
// Hide cursor after 1 second
setTimeout(async () => {
await getFromContentScript(tabId, '_cursor', { show: false });
}, 1000);
return respondWith(tabId, click ? { clicked: true } : { hovered: true }, selector, xpath);
} catch (error) {
// Make sure cursor is hidden on error
await getFromContentScript(tabId, '_cursor', { show: false });
return respondWithError(tabId, 'CLICK_FAILED', error.message, selector, xpath);
}
}