feat: add nested iframe selector generation func for capture text

This commit is contained in:
RohitR311
2025-01-04 09:01:13 +05:30
parent b6faf5cf17
commit 3f73a48c31

View File

@@ -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) { // Get the owner document of the current element
// If we're in an iframe, get its parent document const ownerDocument = current.ownerDocument;
const frameElement = currentDoc.defaultView?.frameElement as HTMLIFrameElement;
if (frameElement && isAccessibleIframe(frameElement)) { // Check if this document belongs to an iframe
path.unshift({ const frameElement = ownerDocument?.defaultView?.frameElement as HTMLIFrameElement;
frame: frameElement,
document: currentDoc, if (frameElement) {
element: current path.unshift({
}); frame: frameElement,
current = frameElement; document: ownerDocument,
currentDoc = frameElement.ownerDocument; element: current
depth++; });
} else { // Move up to the parent document's element (the iframe)
break; 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
iframePath.forEach((context, index) => {
// Get selector for the iframe element in its parent document
const frameSelector = finder(context.frame, {
root: index === 0 ? document.body : (iframePath[index - 1].document.body as Element)
});
// For the last context, get selector for target element // Generate selector for each iframe boundary
if (index === iframePath.length - 1) { iframePath.forEach((context, index) => {
const elementSelector = finder(element, { // Get selector for the iframe element
root: context.document.body as Element const frameSelector = finder(context.frame, {
}); root: index === 0 ? document.body :
// Use :>> for iframe traversal in the selector (iframePath[index - 1].document.body as Element)
selectorParts.push(`${frameSelector} :>> ${elementSelector}`); });
} else {
selectorParts.push(frameSelector); // For the last context, get selector for target element
} if (index === iframePath.length - 1) {
}); const elementSelector = finder(element, {
root: context.document.body as Element
return { });
// Join all parts with :>> to indicate iframe traversal selectorParts.push(`${frameSelector} :>> ${elementSelector}`);
fullSelector: selectorParts.join(' :>> '), } else {
// Include additional metadata about the frames if needed selectorParts.push(frameSelector);
frameCount: iframePath.length, }
isAccessible: true });
};
return {
fullSelector: selectorParts.join(' :>> '),
isFrameContent: 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
}; };
} }