fix(click): make it work for display:contents elements (#16356)

After protocol fixes in all browsers, we can now scroll and click display:contents elements.
The only problem is that `elementsFromPoint()` misbehaves in Chromium and Firefox, so we
need a workaround. Hopefully, it will be fixed upstream - shadow dom spec folks think
"it becomes a real compatibility concern".

This needs Chromium 105 roll.
This commit is contained in:
Dmitry Gozman 2022-08-08 16:05:09 -07:00 committed by GitHub
parent 36b92d8847
commit 0fa20d5d1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 60 additions and 4 deletions

View File

@ -870,9 +870,19 @@ export class InjectedScript {
let container: Document | ShadowRoot | null = document; let container: Document | ShadowRoot | null = document;
let element: Element | undefined; let element: Element | undefined;
while (container) { while (container) {
// elementFromPoint works incorrectly in Chromium (http://crbug.com/1188919), // All browsers have different behavior around elementFromPoint and elementsFromPoint.
// so we use elementsFromPoint instead. // https://github.com/w3c/csswg-drafts/issues/556
// http://crbug.com/1188919
const elements: Element[] = container.elementsFromPoint(x, y); const elements: Element[] = container.elementsFromPoint(x, y);
const singleElement = container.elementFromPoint(x, y);
if (singleElement && elements[0] && parentElementOrShadowHost(singleElement) === elements[0]) {
const style = document.defaultView?.getComputedStyle(singleElement);
if (style?.display === 'contents') {
// Workaround a case where elementsFromPoint misses the inner-most element with display:contents.
// https://bugs.chromium.org/p/chromium/issues/detail?id=1342092
elements.unshift(singleElement);
}
}
const innerElement = elements[0] as Element | undefined; const innerElement = elements[0] as Element | undefined;
if (!innerElement || element === innerElement) if (!innerElement || element === innerElement)
break; break;

View File

@ -60,10 +60,16 @@ it('should wait for display:none to become visible', async ({ page, server }) =>
await testWaiting(page, div => div.style.display = 'block'); await testWaiting(page, div => div.style.display = 'block');
}); });
it.fixme('should scroll display:contents into view', async ({ page, server }) => { it('should scroll display:contents into view', async ({ page, browserName, browserMajorVersion }) => {
it.skip(browserName === 'chromium' && browserMajorVersion < 105, 'Needs https://chromium-review.googlesource.com/c/chromium/src/+/3758670');
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/15034' }); it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/15034' });
await page.setContent(` await page.setContent(`
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; }
::-webkit-scrollbar { display: none; }
* { scrollbar-width: none; }
</style>
<div id=container style="width:200px;height:200px;overflow:scroll;border:1px solid black;"> <div id=container style="width:200px;height:200px;overflow:scroll;border:1px solid black;">
<div style="margin-top:500px;background:red;"> <div style="margin-top:500px;background:red;">
<div style="height:50px;width:100px;background:cyan;"> <div style="height:50px;width:100px;background:cyan;">

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { test as it } from './pageTest'; import { expect, test as it } from './pageTest';
it('should not hit scroll bar', async ({ page, browserName, platform }) => { it('should not hit scroll bar', async ({ page, browserName, platform }) => {
it.fixme(browserName === 'webkit' && platform === 'darwin'); it.fixme(browserName === 'webkit' && platform === 'darwin');
@ -37,3 +37,43 @@ it('should not hit scroll bar', async ({ page, browserName, platform }) => {
`); `);
await page.click('text=Story', { timeout: 2000 }); await page.click('text=Story', { timeout: 2000 });
}); });
it('should scroll into view display:contents', async ({ page, browserName, browserMajorVersion }) => {
it.skip(browserName === 'chromium' && browserMajorVersion < 105, 'Needs https://chromium-review.googlesource.com/c/chromium/src/+/3758670');
await page.setContent(`
<div style="background:red;height:2000px">filler</div>
<div>
Example text, and button here:
<button style="display: contents" onclick="window._clicked=true;">click me</button>
</div>
`);
await page.click('text=click me', { timeout: 5000 });
expect(await page.evaluate('window._clicked')).toBe(true);
});
it('should scroll into view display:contents with a child', async ({ page, browserName, browserMajorVersion }) => {
it.skip(browserName === 'chromium' && browserMajorVersion < 105, 'Needs https://chromium-review.googlesource.com/c/chromium/src/+/3758670');
await page.setContent(`
<div style="background:red;height:2000px">filler</div>
Example text, and button here:
<button style="display: contents" onclick="window._clicked=true;"><div>click me</div></button>
`);
await page.click('text=click me', { timeout: 5000 });
expect(await page.evaluate('window._clicked')).toBe(true);
});
it('should scroll into view display:contents with position', async ({ page, browserName }) => {
it.fixme(browserName === 'chromium', 'DOM.getBoxModel does not work for display:contents');
await page.setContent(`
<div style="background:red;height:2000px">filler</div>
<div>
Example text, and button here:
<button style="display: contents" onclick="window._clicked=true;">click me</button>
</div>
`);
await page.click('text=click me', { position: { x: 5, y: 5 }, timeout: 5000 });
expect(await page.evaluate('window._clicked')).toBe(true);
});