fix(hit target): account for iframes with padding (#19732)

Padding on iframes moves the `documentElement` inside the iframe, so we
should account for it when converting coordinates between frames.

Fixes #19613.
This commit is contained in:
Dmitry Gozman 2022-12-27 16:59:34 -08:00 committed by GitHub
parent 3334d89ad7
commit 0b223b9036
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 3 deletions

View file

@ -877,7 +877,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
return { framePoint: undefined };
}
// Translate from viewport coordinates to frame coordinates.
const pointInFrame = { x: point.x - box.x - style.borderLeft, y: point.y - box.y - style.borderTop };
const pointInFrame = { x: point.x - box.x - style.left, y: point.y - box.y - style.top };
data.push({ frame, frameElement, pointInFrame });
frame = frame.parentFrame()!;
}

View file

@ -486,7 +486,7 @@ export class InjectedScript {
return { left: parseInt(style.borderLeftWidth || '', 10), top: parseInt(style.borderTopWidth || '', 10) };
}
describeIFrameStyle(iframe: Element): 'error:notconnected' | 'transformed' | { borderLeft: number, borderTop: number } {
describeIFrameStyle(iframe: Element): 'error:notconnected' | 'transformed' | { left: number, top: number } {
if (!iframe.ownerDocument || !iframe.ownerDocument.defaultView)
return 'error:notconnected';
const defaultView = iframe.ownerDocument.defaultView;
@ -495,7 +495,10 @@ export class InjectedScript {
return 'transformed';
}
const iframeStyle = defaultView.getComputedStyle(iframe);
return { borderLeft: parseInt(iframeStyle.borderLeftWidth || '', 10), borderTop: parseInt(iframeStyle.borderTopWidth || '', 10) };
return {
left: parseInt(iframeStyle.borderLeftWidth || '', 10) + parseInt(iframeStyle.paddingLeft || '', 10),
top: parseInt(iframeStyle.borderTopWidth || '', 10) + parseInt(iframeStyle.paddingTop || '', 10),
};
}
retarget(node: Node, behavior: 'none' | 'follow-label' | 'no-follow-label' | 'button-link'): Element | null {

View file

@ -394,3 +394,51 @@ it('should detect overlayed element in a transformed iframe', async ({ page }) =
const error = await locator.click({ timeout: 2000 }).catch(e => e);
expect(error.message).toContain('<section>Overlay</section> intercepts pointer events');
});
it('should click in iframe with padding', async ({ page }) => {
await page.setContent(`
<style>
body, html, iframe { margin: 0; padding: 0; border: none; box-sizing: border-box; }
iframe { background: gray; width: 200px; height: 200px; padding-top: 100px; }
</style>
<iframe srcdoc="
<style>
body, html { margin: 0; padding: 0; }
div { height: 100px; }
</style>
<div>Non-target</div>
<div id=target>Target</div>
<div>Non-target</div>
<script>
document.querySelector('#target').addEventListener('click', () => window.top._clicked = true);
</script>
"></iframe>
`);
const locator = page.frameLocator('iframe').locator('#target');
await locator.click();
expect(await page.evaluate('window._clicked')).toBe(true);
});
it('should click in iframe with padding 2', async ({ page }) => {
await page.setContent(`
<style>
body, html, iframe { margin: 0; padding: 0; border: none; box-sizing: content-box; }
iframe { background: gray; width: 200px; height: 200px; padding-top: 100px; }
</style>
<iframe srcdoc="
<style>
body, html { margin: 0; padding: 0; }
div { height: 100px; }
</style>
<div>Non-target</div>
<div id=target>Target</div>
<div>Non-target</div>
<script>
document.querySelector('#target').addEventListener('click', () => window.top._clicked = true);
</script>
"></iframe>
`);
const locator = page.frameLocator('iframe').locator('#target');
await locator.click();
expect(await page.evaluate('window._clicked')).toBe(true);
});