From 486bbe010d5ee5c446d9e8daf61a080232379c3f Mon Sep 17 00:00:00 2001 From: Alex Rudenko Date: Mon, 17 Jan 2022 07:32:52 +0100 Subject: [PATCH] fix: correctly compute clickable points for elements inside OOPIFs (#7900) Issues: #7849 --- src/common/FrameManager.ts | 1 - src/common/JSHandle.ts | 33 ++++++++++++++++++++++++++++- test/oopif.spec.ts | 43 +++++++++++++++++++++++++++++++++++++- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/common/FrameManager.ts b/src/common/FrameManager.ts index 19f2a3b1d0ea5..ba1c9ba2237f1 100644 --- a/src/common/FrameManager.ts +++ b/src/common/FrameManager.ts @@ -73,7 +73,6 @@ export class FrameManager extends EventEmitter { private _contextIdToContext = new Map(); private _isolatedWorlds = new Set(); private _mainFrame: Frame; - private _disconnectPromise?: Promise; constructor( client: CDPSession, diff --git a/src/common/JSHandle.ts b/src/common/JSHandle.ts index e6358fba96ef6..306a57dfda48d 100644 --- a/src/common/JSHandle.ts +++ b/src/common/JSHandle.ts @@ -80,6 +80,7 @@ export function createJSHandle( context, context._client, remoteObject, + frame, frameManager.page(), frameManager ); @@ -331,6 +332,7 @@ export class JSHandle { export class ElementHandle< ElementType extends Element = Element > extends JSHandle { + private _frame: Frame; private _page: Page; private _frameManager: FrameManager; @@ -341,12 +343,14 @@ export class ElementHandle< context: ExecutionContext, client: CDPSession, remoteObject: Protocol.Runtime.RemoteObject, + frame: Frame, page: Page, frameManager: FrameManager ) { super(context, client, remoteObject); this._client = client; this._remoteObject = remoteObject; + this._frame = frame; this._page = page; this._frameManager = frameManager; } @@ -475,7 +479,7 @@ export class ElementHandle< objectId: this._remoteObject.objectId, }) .catch(debugError), - this._client.send('Page.getLayoutMetrics'), + this._page.client().send('Page.getLayoutMetrics'), ]); if (!result || !result.quads.length) throw new Error('Node is either not clickable or not an HTMLElement'); @@ -483,8 +487,35 @@ export class ElementHandle< // Fallback to `layoutViewport` in case of using Firefox. const { clientWidth, clientHeight } = layoutMetrics.cssLayoutViewport || layoutMetrics.layoutViewport; + let offsetX = 0; + let offsetY = 0; + let frame = this._frame; + while (frame.parentFrame()) { + const parent = frame.parentFrame(); + if (!frame.isOOPFrame()) { + frame = parent; + continue; + } + const { backendNodeId } = await parent._client.send('DOM.getFrameOwner', { + frameId: frame._id, + }); + const { quads } = await parent._client.send('DOM.getContentQuads', { + backendNodeId: backendNodeId, + }); + if (!quads || !quads.length) { + break; + } + const protocolQuads = quads.map((quad) => this._fromProtocolQuad(quad)); + const topLeftCorner = protocolQuads[0][0]; + offsetX += topLeftCorner.x; + offsetY += topLeftCorner.y; + frame = parent; + } const quads = result.quads .map((quad) => this._fromProtocolQuad(quad)) + .map((quad) => + quad.map((part) => ({ x: part.x + offsetX, y: part.y + offsetY })) + ) .map((quad) => this._intersectQuadWithViewport(quad, clientWidth, clientHeight) ) diff --git a/test/oopif.spec.ts b/test/oopif.spec.ts index 0880363eb07ae..883936d6e618e 100644 --- a/test/oopif.spec.ts +++ b/test/oopif.spec.ts @@ -210,10 +210,20 @@ describeChromeOnly('OOPIF', function () { await frame.evaluate(() => { const button = document.createElement('button'); button.id = 'test-button'; + button.innerText = 'click'; + button.onclick = () => { + button.id = 'clicked'; + }; document.body.appendChild(button); }); - + await page.evaluate(() => { + document.body.style.border = '150px solid black'; + document.body.style.margin = '250px'; + document.body.style.padding = '50px'; + }); + await frame.waitForSelector('#test-button', { visible: true }); await frame.click('#test-button'); + await frame.waitForSelector('#clicked'); }); it('should report oopif frames', async () => { const { server } = getTestState(); @@ -268,6 +278,37 @@ describeChromeOnly('OOPIF', function () { await utils.detachFrame(oopIframe, 'frame1'); expect(oopIframe.childFrames()).toHaveLength(0); }); + + it('clickablePoint should work for elements inside OOPIFs', async () => { + const { server } = getTestState(); + await page.goto(server.EMPTY_PAGE); + const framePromise = page.waitForFrame((frame) => { + return page.frames().indexOf(frame) === 1; + }); + await utils.attachFrame( + page, + 'frame1', + server.CROSS_PROCESS_PREFIX + '/empty.html' + ); + const frame = await framePromise; + await page.evaluate(() => { + document.body.style.border = '50px solid black'; + document.body.style.margin = '50px'; + document.body.style.padding = '50px'; + }); + await frame.evaluate(() => { + const button = document.createElement('button'); + button.id = 'test-button'; + button.innerText = 'click'; + document.body.appendChild(button); + }); + const button = await frame.waitForSelector('#test-button', { + visible: true, + }); + const result = await button.clickablePoint(); + expect(result.x).toBeGreaterThan(150); // padding + margin + border left + expect(result.y).toBeGreaterThan(150); // padding + margin + border top + }); }); /**