feat: add nested iframe selector generation func for capture text
This commit is contained in:
@@ -849,145 +849,115 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getIframeOffset = (iframe: HTMLIFrameElement): { x: number; y: number } => {
|
|
||||||
const rect = iframe.getBoundingClientRect();
|
|
||||||
return {
|
|
||||||
x: rect.left,
|
|
||||||
y: rect.top
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const isAccessibleIframe = (iframe: HTMLIFrameElement): boolean => {
|
|
||||||
try {
|
|
||||||
return !!iframe.contentDocument;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => {
|
const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => {
|
||||||
// Get the initial element at the specified coordinates
|
// First, get the element at the specified coordinates in the main document
|
||||||
let currentElement = document.elementFromPoint(x, y) as HTMLElement;
|
let element = document.elementFromPoint(x, y) as HTMLElement;
|
||||||
if (!currentElement) return null;
|
if (!element) return null;
|
||||||
|
|
||||||
let deepestElement = currentElement;
|
// Check if the element is an iframe
|
||||||
let current = currentElement;
|
if (element.tagName !== 'IFRAME') return element;
|
||||||
let currentX = x;
|
|
||||||
let currentY = y;
|
let currentIframe = element as HTMLIFrameElement;
|
||||||
|
let deepestElement = element;
|
||||||
let depth = 0;
|
let depth = 0;
|
||||||
const MAX_DEPTH = 20; // Prevent infinite loops with deeply nested iframes
|
const MAX_DEPTH = 4; // Limit the depth of nested iframes to prevent infinite loops
|
||||||
|
|
||||||
// Continue traversing while we find nested iframes
|
while (currentIframe && depth < MAX_DEPTH) {
|
||||||
while (current && depth < MAX_DEPTH) {
|
try {
|
||||||
// Check if the current element is an iframe and if we can access it
|
// Convert coordinates from main document to iframe's coordinate system
|
||||||
if (current instanceof HTMLIFrameElement && isAccessibleIframe(current)) {
|
const iframeRect = currentIframe.getBoundingClientRect();
|
||||||
// Calculate the offset of the iframe
|
const iframeX = x - iframeRect.left;
|
||||||
const iframeOffset = getIframeOffset(current);
|
const iframeY = y - iframeRect.top;
|
||||||
|
|
||||||
// Transform coordinates to be relative to the iframe's content window
|
// Access the iframe's content document and get the element at the transformed coordinates
|
||||||
const relativeX = currentX - iframeOffset.x;
|
const iframeDoc = currentIframe.contentDocument || currentIframe.contentWindow?.document;
|
||||||
const relativeY = currentY - iframeOffset.y;
|
if (!iframeDoc) break;
|
||||||
|
|
||||||
// Find the element at these coordinates within the iframe
|
const iframeElement = iframeDoc.elementFromPoint(iframeX, iframeY) as HTMLElement;
|
||||||
const iframeElement = current.contentDocument?.elementFromPoint(relativeX, relativeY) as HTMLElement;
|
if (!iframeElement) break;
|
||||||
|
|
||||||
// If we don't find an element or we get the same element, stop traversing
|
// If the element found is another iframe, continue traversing
|
||||||
if (!iframeElement || iframeElement === current) break;
|
if (iframeElement.tagName === 'IFRAME') {
|
||||||
|
deepestElement = iframeElement;
|
||||||
// Update our tracking variables
|
currentIframe = iframeElement as HTMLIFrameElement;
|
||||||
deepestElement = iframeElement;
|
depth++;
|
||||||
current = iframeElement;
|
} else {
|
||||||
currentX = relativeX;
|
// If it's not an iframe, we've found our deepest element
|
||||||
currentY = relativeY;
|
deepestElement = iframeElement;
|
||||||
depth++;
|
break;
|
||||||
} else {
|
}
|
||||||
// If the current element is not an iframe, we're done traversing
|
} catch (error) {
|
||||||
break;
|
// Handle potential security errors when accessing cross-origin iframes
|
||||||
}
|
console.warn('Cannot access iframe content:', error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return deepestElement;
|
return deepestElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IframeContext {
|
|
||||||
frame: HTMLIFrameElement;
|
|
||||||
document: Document;
|
|
||||||
element: HTMLElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
const genSelectorForIframe = (element: HTMLElement) => {
|
const genSelectorForIframe = (element: HTMLElement) => {
|
||||||
// Helper function to check if we can access an iframe's content
|
// Helper function to get the complete iframe path up to document root
|
||||||
const isAccessibleIframe = (iframe: HTMLIFrameElement): boolean => {
|
|
||||||
try {
|
|
||||||
return !!iframe.contentDocument;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get complete path up through nested iframes to document root
|
|
||||||
const getIframePath = (el: HTMLElement) => {
|
const getIframePath = (el: HTMLElement) => {
|
||||||
const path: IframeContext[] = [];
|
const path = [];
|
||||||
let current = el;
|
let current = el;
|
||||||
let currentDoc = el.ownerDocument;
|
let depth = 0;
|
||||||
let depth = 0;
|
const MAX_DEPTH = 4;
|
||||||
const MAX_DEPTH = 20; // Limit depth to prevent infinite loops
|
|
||||||
|
|
||||||
while (current && depth < MAX_DEPTH) {
|
while (current && depth < MAX_DEPTH) {
|
||||||
// If we're in an iframe, get its parent document
|
// Get the owner document of the current element
|
||||||
const frameElement = currentDoc.defaultView?.frameElement as HTMLIFrameElement;
|
const ownerDocument = current.ownerDocument;
|
||||||
if (frameElement && isAccessibleIframe(frameElement)) {
|
|
||||||
path.unshift({
|
// Check if this document belongs to an iframe
|
||||||
frame: frameElement,
|
const frameElement = ownerDocument?.defaultView?.frameElement as HTMLIFrameElement;
|
||||||
document: currentDoc,
|
|
||||||
element: current
|
if (frameElement) {
|
||||||
});
|
path.unshift({
|
||||||
current = frameElement;
|
frame: frameElement,
|
||||||
currentDoc = frameElement.ownerDocument;
|
document: ownerDocument,
|
||||||
depth++;
|
element: current
|
||||||
} else {
|
});
|
||||||
break;
|
// Move up to the parent document's element (the iframe)
|
||||||
|
current = frameElement;
|
||||||
|
depth++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return path;
|
||||||
return path;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the iframe path for our target element
|
|
||||||
const iframePath = getIframePath(element);
|
const iframePath = getIframePath(element);
|
||||||
if (iframePath.length === 0) return null;
|
if (iframePath.length === 0) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const selectorParts: string[] = [];
|
const selectorParts: string[] = [];
|
||||||
|
|
||||||
// Generate selector for each iframe boundary
|
// Generate selector for each iframe boundary
|
||||||
iframePath.forEach((context, index) => {
|
iframePath.forEach((context, index) => {
|
||||||
// Get selector for the iframe element in its parent document
|
// Get selector for the iframe element
|
||||||
const frameSelector = finder(context.frame, {
|
const frameSelector = finder(context.frame, {
|
||||||
root: index === 0 ? document.body : (iframePath[index - 1].document.body as Element)
|
root: index === 0 ? document.body :
|
||||||
|
(iframePath[index - 1].document.body as Element)
|
||||||
|
});
|
||||||
|
|
||||||
|
// For the last context, get selector for target element
|
||||||
|
if (index === iframePath.length - 1) {
|
||||||
|
const elementSelector = finder(element, {
|
||||||
|
root: context.document.body as Element
|
||||||
|
});
|
||||||
|
selectorParts.push(`${frameSelector} :>> ${elementSelector}`);
|
||||||
|
} else {
|
||||||
|
selectorParts.push(frameSelector);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// For the last context, get selector for target element
|
return {
|
||||||
if (index === iframePath.length - 1) {
|
fullSelector: selectorParts.join(' :>> '),
|
||||||
const elementSelector = finder(element, {
|
isFrameContent: true
|
||||||
root: context.document.body as Element
|
};
|
||||||
});
|
|
||||||
// Use :>> for iframe traversal in the selector
|
|
||||||
selectorParts.push(`${frameSelector} :>> ${elementSelector}`);
|
|
||||||
} else {
|
|
||||||
selectorParts.push(frameSelector);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
// Join all parts with :>> to indicate iframe traversal
|
|
||||||
fullSelector: selectorParts.join(' :>> '),
|
|
||||||
// Include additional metadata about the frames if needed
|
|
||||||
frameCount: iframePath.length,
|
|
||||||
isAccessible: true
|
|
||||||
};
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Error generating iframe selector:', e);
|
console.warn('Error generating iframe selector:', e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1060,8 +1030,7 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => {
|
|||||||
formSelector,
|
formSelector,
|
||||||
iframeSelector: iframeSelector ? {
|
iframeSelector: iframeSelector ? {
|
||||||
full: iframeSelector.fullSelector,
|
full: iframeSelector.fullSelector,
|
||||||
frame: iframeSelector.frameCount,
|
isIframe: iframeSelector.isFrameContent,
|
||||||
accesible: iframeSelector.isAccessible
|
|
||||||
} : null
|
} : null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user