fix(page): fix page.#scrollIntoViewIfNeeded method (#8631)

This patch fixes page.#scrollIntoViewIfNeeded, so that it works with devtools protocol.
Now it blocks the main thread and waits until the scrolling action finishes in Chrome.
Fallbacks to the old implementation if `DOM.scrollIntoViewIfNeeded` is not supported for Firefox.

Issues: #8627, #1805
This commit is contained in:
Asen Bozhilov 2022-07-08 09:53:45 +03:00 committed by GitHub
parent 1de0383abf
commit b47f066c2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 40 additions and 35 deletions

View File

@ -240,33 +240,39 @@ export class ElementHandle<
async #scrollIntoViewIfNeeded(this: ElementHandle<Element>): Promise<void> { async #scrollIntoViewIfNeeded(this: ElementHandle<Element>): Promise<void> {
const error = await this.evaluate( const error = await this.evaluate(
async (element, pageJavascriptEnabled): Promise<string | false> => { async (element): Promise<string | undefined> => {
if (!element.isConnected) { if (!element.isConnected) {
return 'Node is detached from document'; return 'Node is detached from document';
} }
if (element.nodeType !== Node.ELEMENT_NODE) { if (element.nodeType !== Node.ELEMENT_NODE) {
return 'Node is not of type HTMLElement'; return 'Node is not of type HTMLElement';
} }
// force-scroll if page's javascript is disabled. return;
if (!pageJavascriptEnabled) {
element.scrollIntoView({
block: 'center',
inline: 'center',
// @ts-expect-error Chrome still supports behavior: instant but
// it's not in the spec so TS shouts We don't want to make this
// breaking change in Puppeteer yet so we'll ignore the line.
behavior: 'instant',
});
return false;
} }
const visibleRatio = await new Promise(resolve => { );
if (error) {
throw new Error(error);
}
try {
await this._client.send('DOM.scrollIntoViewIfNeeded', {
objectId: this._remoteObject.objectId,
});
} catch (_err) {
// Fallback to Element.scrollIntoView if DOM.scrollIntoViewIfNeeded is not supported
await this.evaluate(
async (element, pageJavascriptEnabled): Promise<void> => {
const visibleRatio = async () => {
return await new Promise(resolve => {
const observer = new IntersectionObserver(entries => { const observer = new IntersectionObserver(entries => {
resolve(entries[0]!.intersectionRatio); resolve(entries[0]!.intersectionRatio);
observer.disconnect(); observer.disconnect();
}); });
observer.observe(element); observer.observe(element);
}); });
if (visibleRatio !== 1.0) { };
if (!pageJavascriptEnabled || (await visibleRatio()) !== 1.0) {
element.scrollIntoView({ element.scrollIntoView({
block: 'center', block: 'center',
inline: 'center', inline: 'center',
@ -276,13 +282,9 @@ export class ElementHandle<
behavior: 'instant', behavior: 'instant',
}); });
} }
return false;
}, },
this.#page.isJavaScriptEnabled() this.#page.isJavaScriptEnabled()
); );
if (error) {
throw new Error(error);
} }
} }

View File

@ -164,7 +164,10 @@ describe('Page.click', function () {
await page.goto(server.PREFIX + '/offscreenbuttons.html'); await page.goto(server.PREFIX + '/offscreenbuttons.html');
const messages: any[] = []; const messages: any[] = [];
page.on('console', msg => { page.on('console', msg => {
if (msg.type() === 'log') {
return messages.push(msg.text()); return messages.push(msg.text());
}
return;
}); });
for (let i = 0; i < 11; ++i) { for (let i = 0; i < 11; ++i) {
// We might've scrolled to click a button - reset to (0, 0). // We might've scrolled to click a button - reset to (0, 0).

View File

@ -203,7 +203,7 @@ describe('ElementHandle specs', function () {
}) })
).toBe(true); ).toBe(true);
}); });
it('should work for TextNodes', async () => { it('should not work for TextNodes', async () => {
const {page, server} = getTestState(); const {page, server} = getTestState();
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');