fix(scrollIntoView): make it work for nested frames case

This commit is contained in:
Pavel Feldman 2019-12-16 21:59:23 -08:00
parent 8828228702
commit 51bf9f15c8
7 changed files with 37 additions and 8 deletions

View file

@ -445,10 +445,13 @@ export class FrameManager implements PageDelegate {
} }
async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> { async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
const doc = await handle.evaluateHandle(node => node.ownerDocument ? node.ownerDocument.documentElement : null);
if (!doc)
return null;
const nodeInfo = await this._client.send('DOM.describeNode', { const nodeInfo = await this._client.send('DOM.describeNode', {
objectId: toRemoteObject(handle).objectId objectId: toRemoteObject(doc).objectId
}); });
if (typeof nodeInfo.node.frameId !== 'string') if (!nodeInfo || typeof nodeInfo.node.frameId !== 'string')
return null; return null;
return this._page._frameManager.frame(nodeInfo.node.frameId); return this._page._frameManager.frame(nodeInfo.node.frameId);
} }
@ -510,6 +513,16 @@ export class FrameManager implements PageDelegate {
throw new Error('Unable to adopt element handle from a different document'); throw new Error('Unable to adopt element handle from a different document');
return to._createHandle(result.object).asElement()!; return to._createHandle(result.object).asElement()!;
} }
async getFrameOwner(frame: frames.Frame): Promise<dom.ElementHandle | null> {
if (!frame.parentFrame())
return null;
const result = await this._client.send('DOM.getFrameOwner', { frameId: frame._id });
if (!result)
return null;
const utilityContext = await frame.parentFrame()._utilityContext();
return this.adoptBackendNodeId(result.backendNodeId, utilityContext);
}
} }
function toRemoteObject(handle: dom.ElementHandle): Protocol.Runtime.RemoteObject { function toRemoteObject(handle: dom.ElementHandle): Protocol.Runtime.RemoteObject {

View file

@ -152,6 +152,18 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
} }
async _scrollIntoViewIfNeeded() { async _scrollIntoViewIfNeeded() {
const path: frames.Frame[] = [];
for (let frame = await this.contentFrame(); frame; frame = frame.parentFrame())
path.push(frame);
for (const frame of path.reverse()) {
const owner = await frame._page._delegate.getFrameOwner(frame);
if (owner)
await owner._scrollIntoViewIfNeededWithinFrame();
}
this._scrollIntoViewIfNeededWithinFrame();
}
async _scrollIntoViewIfNeededWithinFrame() {
const error = await this.evaluate(async (node: Node, pageJavascriptEnabled: boolean) => { const error = await this.evaluate(async (node: Node, pageJavascriptEnabled: boolean) => {
if (!node.isConnected) if (!node.isConnected)
return 'Node is detached from document'; return 'Node is detached from document';

View file

@ -406,6 +406,10 @@ export class FrameManager implements PageDelegate {
assert(false, 'Multiple isolated worlds are not implemented'); assert(false, 'Multiple isolated worlds are not implemented');
return handle; return handle;
} }
async getFrameOwner(frame: frames.Frame): Promise<dom.ElementHandle | null> {
throw new Error('Not implemented');
}
} }
export function normalizeWaitUntil(waitUntil: frames.LifecycleEvent | frames.LifecycleEvent[]): frames.LifecycleEvent[] { export function normalizeWaitUntil(waitUntil: frames.LifecycleEvent | frames.LifecycleEvent[]): frames.LifecycleEvent[] {

View file

@ -62,6 +62,7 @@ export interface PageDelegate {
isElementHandle(remoteObject: any): boolean; isElementHandle(remoteObject: any): boolean;
adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>>; adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>>;
getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null>; getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null>;
getFrameOwner(frame: frames.Frame): Promise<dom.ElementHandle | null>;
getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null>; getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null>;
layoutViewport(): Promise<{ width: number, height: number }>; layoutViewport(): Promise<{ width: number, height: number }>;
setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise<void>; setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise<void>;

View file

@ -487,6 +487,10 @@ export class FrameManager implements PageDelegate {
}); });
return to._createHandle(result.object) as dom.ElementHandle<T>; return to._createHandle(result.object) as dom.ElementHandle<T>;
} }
async getFrameOwner(frame: frames.Frame): Promise<dom.ElementHandle | null> {
throw new Error('Not implemented');
}
} }
function toRemoteObject(handle: dom.ElementHandle): Protocol.Runtime.RemoteObject { function toRemoteObject(handle: dom.ElementHandle): Protocol.Runtime.RemoteObject {

View file

@ -261,8 +261,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
await button.click(); await button.click();
expect(await frame.evaluate(() => window.result)).toBe('Clicked'); expect(await frame.evaluate(() => window.result)).toBe('Clicked');
}); });
// @see https://github.com/GoogleChrome/puppeteer/issues/4110 it.skip(WEBKIT || FFOX)('should click the button with fixed position inside an iframe', async({page, server}) => {
xit('should click the button with fixed position inside an iframe', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setViewport({width: 500, height: 500}); await page.setViewport({width: 500, height: 500});
await page.setContent('<div style="width:100px;height:2000px">spacer</div>'); await page.setContent('<div style="width:100px;height:2000px">spacer</div>');

View file

@ -105,10 +105,6 @@ const utils = module.exports = {
frame.src = url; frame.src = url;
frame.id = frameId; frame.id = frameId;
document.body.appendChild(frame); document.body.appendChild(frame);
// TODO(einbinder) do this right
// Access a scriptable global object to ensure JS context is
// initialized. WebKit will create it lazily only as need be.
frame.contentWindow;
await new Promise(x => frame.onload = x); await new Promise(x => frame.onload = x);
return frame; return frame;
} }