Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions packages/playwright-core/src/server/webkit/webview/wvPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,7 @@ export class WVPage implements PageDelegate {

async getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null> {
const quads = await this.getContentQuads(handle);
if (!quads || !quads.length)
if (quads === 'error:notconnected' || !quads || !quads.length)
return null;
let minX = Infinity;
let maxX = -Infinity;
Expand Down Expand Up @@ -869,7 +869,7 @@ export class WVPage implements PageDelegate {
return 1;
}

async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null | 'error:notconnected'> {
const result = await handle.evaluateInUtility(([, node]) => {
let element: Element | null = node as Element;
while (element && element.nodeType !== 1 /* Node.ELEMENT_NODE */)
Expand All @@ -886,9 +886,32 @@ export class WVPage implements PageDelegate {
{ x: r.left, y: r.bottom },
]);
}, {});
if (result === 'error:notconnected')
return result;
if (!result || typeof result === 'string')
return null;
return result as types.Quad[];
let quads = result as types.Quad[];
let frame: frames.Frame | null = handle._frame;
while (frame?.parentFrame()) {
const frameElement = await this.getFrameElement(frame).catch(() => null);
if (!frameElement)
return null;
const offset = await frameElement.evaluateInUtility(([injected, iframe]) => {
const element = iframe as Element;
const style = injected.describeIFrameStyle(element);
if (style === 'error:notconnected' || style === 'transformed')
return style;
const rect = element.getBoundingClientRect();
return { x: rect.left + style.left, y: rect.top + style.top };
}, {}).finally(() => frameElement.dispose());
if (offset === 'error:notconnected')
return offset;
if (!offset || typeof offset === 'string')
return null;
quads = quads.map(quad => quad.map(point => ({ x: point.x + offset.x, y: point.y + offset.y })) as types.Quad);
frame = frame.parentFrame();
}
return quads;
}

async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>> {
Expand Down
30 changes: 30 additions & 0 deletions tests/page/elementhandle-bounding-box.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,36 @@ it('should return null for invisible elements', async ({ page, server }) => {
expect(await element.boundingBox()).toBe(null);
});

it('should get bounding box of element inside an iframe', async ({ page, server }) => {
await page.goto(server.EMPTY_PAGE);
await page.setContent(`
<div style="height:120px"></div>
<iframe style="border:0;margin-left:30px;width:300px;height:200px" src="${server.PREFIX}/input/button.html"></iframe>
`);
const frame = page.frames()[1];
const button = await frame.waitForSelector('button');
const iframeBox = (await (await page.$('iframe')).boundingBox())!;
const inner = await button.evaluate(b => { const r = b.getBoundingClientRect(); return { x: r.left, y: r.top }; });
const box = (await button.boundingBox())!;
expect(Math.round(box.x)).toBe(Math.round(iframeBox.x + inner.x));
expect(Math.round(box.y)).toBe(Math.round(iframeBox.y + inner.y));
});

it('should get bounding box of element inside a cross-origin iframe', async ({ page, server }) => {
await page.goto(server.EMPTY_PAGE);
await page.setContent(`
<div style="height:120px"></div>
<iframe style="border:0;margin-left:30px;width:300px;height:200px" src="${server.CROSS_PROCESS_PREFIX}/input/button.html"></iframe>
`);
const frame = page.frames()[1];
const button = await frame.waitForSelector('button');
const iframeBox = (await (await page.$('iframe')).boundingBox())!;
const inner = await button.evaluate(b => { const r = b.getBoundingClientRect(); return { x: r.left, y: r.top }; });
const box = (await button.boundingBox())!;
expect(Math.round(box.x)).toBe(Math.round(iframeBox.x + inner.x));
expect(Math.round(box.y)).toBe(Math.round(iframeBox.y + inner.y));
});

it('should force a layout', async ({ page, server }) => {
await page.setViewportSize({ width: 500, height: 500 });
await page.setContent('<div style="width: 100px; height: 100px">hello</div>');
Expand Down
Loading