Update domutils to handle cursor:auto (#1489)
This commit is contained in:
@@ -555,7 +555,16 @@ function isInteractableInput(element) {
|
|||||||
return !isReadonlyElement(element) && type !== "hidden";
|
return !isReadonlyElement(element) && type !== "hidden";
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInteractable(element) {
|
function isValidCSSSelector(selector) {
|
||||||
|
try {
|
||||||
|
document.querySelector(selector);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInteractable(element, hoverStylesMap) {
|
||||||
if (element.shadowRoot) {
|
if (element.shadowRoot) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -689,6 +698,17 @@ function isInteractable(element) {
|
|||||||
if (elementCursor === "pointer") {
|
if (elementCursor === "pointer") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if element has hover styles that change cursor to pointer
|
||||||
|
// This is to handle the case where an element's cursor is "auto", but resolves to "pointer" on hover
|
||||||
|
if (elementCursor === "auto") {
|
||||||
|
for (const [selector, styles] of hoverStylesMap) {
|
||||||
|
if (element.matches(selector) && styles.cursor === "pointer") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: hardcode to fix the bug about hover style now
|
// FIXME: hardcode to fix the bug about hover style now
|
||||||
if (element.className.toString().includes("hover:cursor-pointer")) {
|
if (element.className.toString().includes("hover:cursor-pointer")) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1202,6 +1222,9 @@ function buildTreeFromBody(frame = "main.frame") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildElementTree(starter = document.body, frame, full_tree = false) {
|
function buildElementTree(starter = document.body, frame, full_tree = false) {
|
||||||
|
// Generate hover styles map at the start
|
||||||
|
const hoverStylesMap = getHoverStylesMap();
|
||||||
|
|
||||||
var elements = [];
|
var elements = [];
|
||||||
var resultArray = [];
|
var resultArray = [];
|
||||||
|
|
||||||
@@ -1232,7 +1255,7 @@ function buildElementTree(starter = document.body, frame, full_tree = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the element is interactable
|
// Check if the element is interactable
|
||||||
if (isInteractable(element)) {
|
if (isInteractable(element, hoverStylesMap)) {
|
||||||
var elementObj = buildElementObject(frame, element, true);
|
var elementObj = buildElementObject(frame, element, true);
|
||||||
elements.push(elementObj);
|
elements.push(elementObj);
|
||||||
// If the element is interactable but has no interactable parent,
|
// If the element is interactable but has no interactable parent,
|
||||||
@@ -1884,6 +1907,90 @@ function scrollToElementTop(element) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all styles associated with :hover selectors
|
||||||
|
*
|
||||||
|
* Chrome doesn't allow you to compute these in run-time because hover is a protected attribute (from JS code)
|
||||||
|
*
|
||||||
|
* Instead of checking the hover state, we can look at the stylesheet and find all the :hover selectors
|
||||||
|
* and try to infer styles associated with them
|
||||||
|
*
|
||||||
|
* It's not 100% accurate, but it's a good start
|
||||||
|
*
|
||||||
|
* References:
|
||||||
|
* https://stackoverflow.com/questions/23040926/how-can-i-get-elementhover-style
|
||||||
|
* https://stackoverflow.com/questions/7013559/is-there-a-way-to-get-element-hover-style-while-the-element-not-in-hover-state
|
||||||
|
* https://stackoverflow.com/questions/17226676/how-to-simulate-a-mouseover-in-pure-javascript-that-activates-the-css-hover
|
||||||
|
*/
|
||||||
|
function getHoverStylesMap() {
|
||||||
|
const hoverMap = new Map();
|
||||||
|
const sheets = document.styleSheets;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const sheet of sheets) {
|
||||||
|
try {
|
||||||
|
const rules = sheet.cssRules || sheet.rules;
|
||||||
|
for (const rule of rules) {
|
||||||
|
if (rule.type === 1 && rule.selectorText) {
|
||||||
|
// Split multiple selectors (e.g., "a:hover, button:hover")
|
||||||
|
const selectors = rule.selectorText.split(",").map((s) => s.trim());
|
||||||
|
|
||||||
|
for (const selector of selectors) {
|
||||||
|
// Check if this is a hover rule
|
||||||
|
if (selector.includes(":hover")) {
|
||||||
|
// Get all parts of the selector
|
||||||
|
const parts = selector.split(/\s*[>+~]\s*/);
|
||||||
|
|
||||||
|
// Get the main hoverable element (the one with :hover)
|
||||||
|
const hoverPart = parts.find((part) => part.includes(":hover"));
|
||||||
|
if (!hoverPart) continue;
|
||||||
|
|
||||||
|
// Get base selector without :hover
|
||||||
|
const baseSelector = hoverPart.replace(/:hover/g, "").trim();
|
||||||
|
|
||||||
|
// Skip invalid selectors
|
||||||
|
if (!isValidCSSSelector(baseSelector)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get or create styles object for this selector
|
||||||
|
let styles = hoverMap.get(baseSelector) || {};
|
||||||
|
|
||||||
|
// Add all style properties
|
||||||
|
for (const prop of rule.style) {
|
||||||
|
styles[prop] = rule.style[prop];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a nested selector (like :hover > .something)
|
||||||
|
// store it in a special format
|
||||||
|
if (parts.length > 1) {
|
||||||
|
const fullSelector = selector;
|
||||||
|
styles["__nested__"] = styles["__nested__"] || [];
|
||||||
|
styles["__nested__"].push({
|
||||||
|
selector: fullSelector,
|
||||||
|
styles: Object.fromEntries(
|
||||||
|
[...rule.style].map((prop) => [prop, rule.style[prop]]),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hoverMap.set(baseSelector, styles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Could not access stylesheet:", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error processing stylesheets:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hoverMap;
|
||||||
|
}
|
||||||
|
|
||||||
// Helper method for debugging
|
// Helper method for debugging
|
||||||
function findNodeById(arr, targetId, path = []) {
|
function findNodeById(arr, targetId, path = []) {
|
||||||
for (let i = 0; i < arr.length; i++) {
|
for (let i = 0; i < arr.length; i++) {
|
||||||
@@ -2109,3 +2216,24 @@ function getIncrementElements() {
|
|||||||
|
|
||||||
return [Array.from(idToElement.values()), cleanedTreeList];
|
return [Array.from(idToElement.values()), cleanedTreeList];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
// How to run the code:
|
||||||
|
|
||||||
|
// Get all interactable elements and draw boxes
|
||||||
|
buildElementsAndDrawBoundingBoxes();
|
||||||
|
|
||||||
|
// Remove the boxes
|
||||||
|
removeBoundingBoxes();
|
||||||
|
|
||||||
|
// Get the element tree
|
||||||
|
const [elements, tree] = buildTreeFromBody();
|
||||||
|
console.log(elements); // All elements
|
||||||
|
console.log(tree); // Tree structure
|
||||||
|
|
||||||
|
// Test if a specific element is interactable
|
||||||
|
const element = document.querySelector('button');
|
||||||
|
const hoverMap = getHoverStylesMap();
|
||||||
|
console.log(isInteractable(element, hoverMap));
|
||||||
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user