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,50 +240,52 @@ 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 => {
const observer = new IntersectionObserver(entries => {
resolve(entries[0]!.intersectionRatio);
observer.disconnect();
});
observer.observe(element);
});
if (visibleRatio !== 1.0) {
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;
},
this.#page.isJavaScriptEnabled()
); );
if (error) { if (error) {
throw new Error(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 => {
resolve(entries[0]!.intersectionRatio);
observer.disconnect();
});
observer.observe(element);
});
};
if (!pageJavascriptEnabled || (await visibleRatio()) !== 1.0) {
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',
});
}
},
this.#page.isJavaScriptEnabled()
);
}
} }
async #getOOPIFOffsets( async #getOOPIFOffsets(

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 => {
return messages.push(msg.text()); if (msg.type() === 'log') {
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');