Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ref test for ResizeObserver devicePixelContenBoxSize #36057

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion resize-observer/create-pattern-data-url.js
Expand Up @@ -18,5 +18,5 @@ export default function createPatternDataURL() {
g, t, t, b,
].flat());
ctx.putImageData(imageData, 0, 0);
return {patternSize, dataURL: ctx.canvas.toDataURL()};
return {patternSize, imageData, dataURL: ctx.canvas.toDataURL()};
}
77 changes: 77 additions & 0 deletions resize-observer/devicepixel3-ref.html
@@ -0,0 +1,77 @@
<!doctype html>
<style>
.outer {
display: flex;
align-items: center;
flex-direction: column;
}
.outer>* {
display: block;
height: 100px;
}
</style>
<body>
<div class="outer"></div>
<script type="module">
import createPatternDataURL from './create-pattern-data-url.js';

const {patternSize, dataURL} = createPatternDataURL();

/**
* Set the pattern's size on this element so that it draws where
* 1 pixel in the pattern maps to 1 devicePixel.
*/
function setPattern(elem) {
const oneDevicePixel = 1 / devicePixelRatio;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am always cautious about usage of multiplications by window.devicePixelRatio, fractional sizes can lead to surprising rounding errors. I trust that you understand this better than I do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean by "slight different". If you mean the patterns don't align between divs that's expected as the pattern starts at the left most pixel of each div

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is what can happen.

Example: devicePixelRation = 1.66.
You will set background size to a float.
To display a background image, browser will round background size to an integer.
There is no way for you to know if size got rounded up or down.
patternPixel pattern size might end up not being an exact multiple of background size, so you can't guarantee it'll display correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that can happen. The browser computes the element size (and therefore the background size) in CSS pixels. The background size is also specified in CSS pixels. Because they are the same units they'll both be converted to device pixels in the same way. It would be a background pattern bug for the pattern not to match what's specified

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I no longer work on Chrome, so I can't consult the rendering team for the details why rendering fractional pixel widths can create scaling artifacts. If you can find a reviewer that agrees with your interpretation what rendering will look like, I am happy to ok this pull request.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside: Trippy test! It's cool how this shows when MacOS re-computes window-to-device mapping after you stop moving the window.

Is this test testing devicePixelContentBoxSize? If so, do we really need to render pixels to test it, rather than just checking the values in javascript? The javascript approach will be a faster test to run, and won't have a dependency on how css background images paint.

WPT uses a device pixel ratio of 1 (issue) so it is strange that firefox is failing. Is their failure just related to devicePixelContentBoxSize with fractional css width/heights? If so, this would also be cleaner to test using javascript.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, @progers explained that browsers do random things with patterns (or at least Chrome does) and while this test doesn't trigger those random things there's no guarantee that won't change in the future.

So, closing this PR

const patternPixels = oneDevicePixel * patternSize;
elem.style.backgroundImage = `url("${dataURL}")`;
elem.style.backgroundSize = `${patternPixels}px ${patternPixels}px`;
}

/*
This ref creates elements like this

<body>
<div class="outer">
<div></div>
<div></div>
<div></div>
...
</div>
</body>

Where the outer div is a flexbox centering the child elements.
Each of the child elements is set to a different width in percent.

The devicePixelContentBox size of each child element is observed
with a ResizeObserver and when changed, a pattern is applied to
the element and the pattern's size set so each pixel in the pattern
will be one device pixel.

A similar process happens in the test HTML using canvases
and patterns generated using putImageData.

The test and this reference page should then match.
*/

const outerElem = document.querySelector('.outer');

/**
* Set the pattern's size on this element so that it draws where
* 1 pixel in the pattern maps to 1 devicePixel.
*/
function setPatterns(entries) {
for (const entry of entries) {
setPattern(entry.target)
}
}

const observer = new ResizeObserver(setPatterns);
for (let percentSize = 7; percentSize < 100; percentSize += 13) {
const innerElem = document.createElement('div');
innerElem.style.width = `${percentSize}%`;
observer.observe(innerElem, {box:"device-pixel-content-box"});
outerElem.appendChild(innerElem);
}
</script>
</body>
89 changes: 89 additions & 0 deletions resize-observer/devicepixel3.html
@@ -0,0 +1,89 @@
<!doctype html>
<link rel="match" href="devicepixel3-ref.html">
<meta name="assert" content="Resize Observer's reported device pixel content box size should be consistent with the actual pixel-snapped painted box size">
<style>
.outer {
display: flex;
align-items: center;
flex-direction: column;
}
.outer>* {
display: block;
height: 100px;
}
</style>
<body>
<div class="outer"></div>
<script type="module">
import createPatternDataURL from './create-pattern-data-url.js';

const {patternSize, imageData: patternImageData} = createPatternDataURL();


function setCanvasPattern(canvas, devicePixelWidth, devicePixelHeight) {
canvas.width = devicePixelWidth;
canvas.height = devicePixelHeight;
const ctx = canvas.getContext('2d');
const imgData = ctx.createImageData(devicePixelWidth, devicePixelHeight);
const srcU32View = new Uint32Array(patternImageData.data.buffer);
const dstU32View = new Uint32Array(imgData.data.buffer);
for (let dstY = 0; dstY < devicePixelHeight; ++dstY) {
for (let dstX = 0; dstX < devicePixelWidth; ++dstX) {
const srcX = dstX % patternSize;
const srcY = dstY % patternSize;
dstU32View[dstY * devicePixelWidth + dstX] = srcU32View[srcY * patternSize + srcX];
}
}
ctx.putImageData(imgData, 0, 0);
}


/*
This test creates elements like this

<body>
<div class="outer">
<canvas></canvas>
<canvas></canvas>
<canvas></canvas>
...
</div>
</body>

Where the outer div is a flexbox centering the child canvases.
Each of the child canvases is set to a different width in percent.

The size of each canvas in device pixels is queried with ResizeObserver
and then each canvases' resolution is set to that size so that there should
be one pixel in each canvas for each device pixel.

Each canvas is filled with a pattern using putImageData.

In the reference the canvas elements are replaced with divs.
For the divs the same pattern is applied with CSS and its size
adjusted so the pattern should appear with one pixel in the pattern
corresponding to 1 device pixel.

The reference and this page should then match.
*/

const outerElem = document.querySelector('.outer');

function setPatternsUsingSizeInfo(entries) {
for (const entry of entries) {
setCanvasPattern(
entry.target,
entry.devicePixelContentBoxSize[0].inlineSize,
entry.devicePixelContentBoxSize[0].blockSize);
}
}

const observer = new ResizeObserver(setPatternsUsingSizeInfo);
for (let percentSize = 7; percentSize < 100; percentSize += 13) {
const canvasElem = document.createElement('canvas');
canvasElem.style.width = `${percentSize}%`;
observer.observe(canvasElem, {box:"device-pixel-content-box"});
outerElem.appendChild(canvasElem);
}
</script>
</body>