first commit
This commit is contained in:
233
app/chrome-extension/inject-scripts/click-helper.js
Normal file
233
app/chrome-extension/inject-scripts/click-helper.js
Normal file
@@ -0,0 +1,233 @@
|
||||
/* eslint-disable */
|
||||
// click-helper.js
|
||||
// This script is injected into the page to handle click operations
|
||||
|
||||
if (window.__CLICK_HELPER_INITIALIZED__) {
|
||||
// Already initialized, skip
|
||||
} else {
|
||||
window.__CLICK_HELPER_INITIALIZED__ = true;
|
||||
/**
|
||||
* Click on an element matching the selector or at specific coordinates
|
||||
* @param {string} selector - CSS selector for the element to click
|
||||
* @param {boolean} waitForNavigation - Whether to wait for navigation to complete after click
|
||||
* @param {number} timeout - Timeout in milliseconds for waiting for the element or navigation
|
||||
* @param {Object} coordinates - Optional coordinates for clicking at a specific position
|
||||
* @param {number} coordinates.x - X coordinate relative to the viewport
|
||||
* @param {number} coordinates.y - Y coordinate relative to the viewport
|
||||
* @returns {Promise<Object>} - Result of the click operation
|
||||
*/
|
||||
async function clickElement(
|
||||
selector,
|
||||
waitForNavigation = false,
|
||||
timeout = 5000,
|
||||
coordinates = null,
|
||||
) {
|
||||
try {
|
||||
let element = null;
|
||||
let elementInfo = null;
|
||||
let clickX, clickY;
|
||||
|
||||
if (coordinates && typeof coordinates.x === 'number' && typeof coordinates.y === 'number') {
|
||||
clickX = coordinates.x;
|
||||
clickY = coordinates.y;
|
||||
|
||||
element = document.elementFromPoint(clickX, clickY);
|
||||
|
||||
if (element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
elementInfo = {
|
||||
tagName: element.tagName,
|
||||
id: element.id,
|
||||
className: element.className,
|
||||
text: element.textContent?.trim().substring(0, 100) || '',
|
||||
href: element.href || null,
|
||||
type: element.type || null,
|
||||
isVisible: true,
|
||||
rect: {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
top: rect.top,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
left: rect.left,
|
||||
},
|
||||
clickMethod: 'coordinates',
|
||||
clickPosition: { x: clickX, y: clickY },
|
||||
};
|
||||
} else {
|
||||
elementInfo = {
|
||||
clickMethod: 'coordinates',
|
||||
clickPosition: { x: clickX, y: clickY },
|
||||
warning: 'No element found at the specified coordinates',
|
||||
};
|
||||
}
|
||||
} else {
|
||||
element = document.querySelector(selector);
|
||||
if (!element) {
|
||||
return {
|
||||
error: `Element with selector "${selector}" not found`,
|
||||
};
|
||||
}
|
||||
|
||||
const rect = element.getBoundingClientRect();
|
||||
elementInfo = {
|
||||
tagName: element.tagName,
|
||||
id: element.id,
|
||||
className: element.className,
|
||||
text: element.textContent?.trim().substring(0, 100) || '',
|
||||
href: element.href || null,
|
||||
type: element.type || null,
|
||||
isVisible: true,
|
||||
rect: {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
top: rect.top,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
left: rect.left,
|
||||
},
|
||||
clickMethod: 'selector',
|
||||
};
|
||||
|
||||
// First sroll so that the element is in view, then check visibility.
|
||||
element.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
elementInfo.isVisible = isElementVisible(element);
|
||||
if (!elementInfo.isVisible) {
|
||||
return {
|
||||
error: `Element with selector "${selector}" is not visible`,
|
||||
elementInfo,
|
||||
};
|
||||
}
|
||||
|
||||
const updatedRect = element.getBoundingClientRect();
|
||||
clickX = updatedRect.left + updatedRect.width / 2;
|
||||
clickY = updatedRect.top + updatedRect.height / 2;
|
||||
}
|
||||
|
||||
let navigationPromise;
|
||||
if (waitForNavigation) {
|
||||
navigationPromise = new Promise((resolve) => {
|
||||
const beforeUnloadListener = () => {
|
||||
window.removeEventListener('beforeunload', beforeUnloadListener);
|
||||
resolve(true);
|
||||
};
|
||||
window.addEventListener('beforeunload', beforeUnloadListener);
|
||||
|
||||
setTimeout(() => {
|
||||
window.removeEventListener('beforeunload', beforeUnloadListener);
|
||||
resolve(false);
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
if (element && elementInfo.clickMethod === 'selector') {
|
||||
element.click();
|
||||
} else {
|
||||
simulateClick(clickX, clickY);
|
||||
}
|
||||
|
||||
// Wait for navigation if needed
|
||||
let navigationOccurred = false;
|
||||
if (waitForNavigation) {
|
||||
navigationOccurred = await navigationPromise;
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Element clicked successfully',
|
||||
elementInfo,
|
||||
navigationOccurred,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
error: `Error clicking element: ${error.message}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate a mouse click at specific coordinates
|
||||
* @param {number} x - X coordinate relative to the viewport
|
||||
* @param {number} y - Y coordinate relative to the viewport
|
||||
*/
|
||||
function simulateClick(x, y) {
|
||||
const clickEvent = new MouseEvent('click', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
});
|
||||
|
||||
const element = document.elementFromPoint(x, y);
|
||||
|
||||
if (element) {
|
||||
element.dispatchEvent(clickEvent);
|
||||
} else {
|
||||
document.dispatchEvent(clickEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an element is visible
|
||||
* @param {Element} element - The element to check
|
||||
* @returns {boolean} - Whether the element is visible
|
||||
*/
|
||||
function isElementVisible(element) {
|
||||
if (!element) return false;
|
||||
|
||||
const style = window.getComputedStyle(element);
|
||||
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rect = element.getBoundingClientRect();
|
||||
if (rect.width === 0 || rect.height === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
rect.bottom < 0 ||
|
||||
rect.top > window.innerHeight ||
|
||||
rect.right < 0 ||
|
||||
rect.left > window.innerWidth
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const centerX = rect.left + rect.width / 2;
|
||||
const centerY = rect.top + rect.height / 2;
|
||||
|
||||
const elementAtPoint = document.elementFromPoint(centerX, centerY);
|
||||
if (!elementAtPoint) return false;
|
||||
|
||||
return element === elementAtPoint || element.contains(elementAtPoint);
|
||||
}
|
||||
|
||||
// Listen for messages from the extension
|
||||
chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => {
|
||||
if (request.action === 'clickElement') {
|
||||
clickElement(
|
||||
request.selector,
|
||||
request.waitForNavigation,
|
||||
request.timeout,
|
||||
request.coordinates,
|
||||
)
|
||||
.then(sendResponse)
|
||||
.catch((error) => {
|
||||
sendResponse({
|
||||
error: `Unexpected error: ${error.message}`,
|
||||
});
|
||||
});
|
||||
return true; // Indicates async response
|
||||
} else if (request.action === 'chrome_click_element_ping') {
|
||||
sendResponse({ status: 'pong' });
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user