Open Bug 477157 Opened 12 years ago Updated 4 months ago

Borders scale differently than other sizes (e.g. margins, content-sizes, backgrounds), under full-page-zoom or HiDPI

Categories

(Core :: Layout, defect)

x86
Linux
defect
Not set
normal

Tracking

()

REOPENED
Webcompat Priority ?
Tracking Status
firefox79 --- affected

People

(Reporter: dholbert, Assigned: emilio)

References

(Blocks 2 open bugs)

Details

(Keywords: perf-alert, Whiteboard: [webcompat])

Attachments

(6 files, 1 obsolete file)

STR:
 1. Load attached testcase
 2. Zoom in and out, examining output at various zoomlevels

EXPECTED RESULTS:
 No red should be visible, at any zoom level.

ACTUAL RESULTS:
 Many zoomlevels show red slivers, between the divs and on their edges.

Basically, this indicates that we aren't scaling background-rects and border-rects in the same way, during full-page-zoom. (We're rounding in a different way, or something like that.)  I'm pretty sure this is the same issue I described in bug 472769 comment 43 and bug 472769 comment 44.
I can reproduce this, zooming in 1x, with both Firefox 3.0.5 and with the latest mozilla-central nightly.

Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.5) Gecko/2008121711 Ubuntu/9.04 (jaunty) Firefox/3.0.5
Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2a1pre) Gecko/20090205 Minefield/3.2a1pre
Attached file reference 1
Here's a reference case.  It just differs from the testcase in that it uses a *background* instead of a *border* to draw the blue rectangles. (It's the same size in both cases, so in theory, they should behave the same)

I get no red at any zoomlevel using this reference case.  (I tested it with both Firefox 3.0.5 and with my mozilla-central nightly.)
Here's a simple reftest for this bug.
Here's what we get from this reftest right now.

In the failure reftest-snapshot, it looks like the rectangle is at least 1px shorter & skinnier than its reference case, and the bottom & right edges look grayish, when they should be solid black.
Note also that in the attached reftest patch, the testcases 477157-1a.html and 477157-1b.html look identical to each other when viewed at zoomlevel 1.0 (i.e. if you view the files directly).
This is probably intentional.

Border widths are always adjusted to be exact multiples of device pixels, in the style system. This ensures that border lines with the same specified width are always rendered with the same width in device pixels after the border line rectangles are snapped to device pixels.

Backgrounds are just snapped to device pixels.

I think the border behaviour explains why at some zoom levels borders aren't covering the set of pixels you expect.
See also 499821, which only uses 'background' (no borders) and has "seams" similar to those in this bug at various zoom levels, breaking wiki.mozilla.org
(In reply to comment #8)
> See also 499821
Sorry -- I meant "bug 499821"  Bugzilla, linkify!
Blocks: 410959
I run into this problem with Ext toolkit, but I am sure that they are not the only one affected.  Up until now on all browsers the "correct" way to get numeric value from "computed style" was to use "parseInt( ... )" (to get rid of "px" suffix). This is how it is coded in endless number of existing applications.  As you can imagine with border width been reported as fractional, results are far from expected.  

Considering the fact that no other browsers (have not tried Chrome) behave differently, was it really good idea to "scale" width like this?  

Other thing which makes this questionable is the fact that it "breaks" relations between "offsetWidth/offsetHeight", "clientWidth/clientHeight" and "border width/height", padding.  Out of these 4 sizes, border width is the only "scaled" value.  So as soon as you are using non-default zoom level, formula: offset == client + border + padding is not true.  It is my understanding that this formula is part of a standard.
Sorry, second paragraph should read:

Considering the fact that no other browsers (have not tried Chrome) behave like this, ...
See Also: → 1204375
See Also: → 1162441
Blocks: 1204376
See Also: → 1204376
See Also: → 890383
Blocks: 1300734
Blocks: 1300870
See Also: 1204375
Blocks: 1327324
See Also: → 1327604
Dholbert, is this a valid bug? Comment 7 suggest that it isn't.

We hit this regularly: https://bugzilla.mozilla.org/buglist.cgi?query_format=advanced&longdesc=bug%20477157&product=Firefox&product=Toolkit&longdesc_type=substring

It would be good to know if we should accept this as a fact of life and keep working around it or whether we can expect this to be fixed some day.
Flags: needinfo?(dholbert)
[CC'ing some other folks who work in pixel-snapping-related code]

(In reply to Dão Gottwald [::dao] from comment #13)
> Dholbert, is this a valid bug? Comment 7 suggest that it isn't.

I'm not sure.  Per comment 7, our current behavior is the least-bad solution we had come up with at the time. Though: that comment & its reasoning predated ubiquitous HiDPI screens (which are like an always-on full-page-zoom, which therefore can make this much easier to trigger, particularly for intermediate HiDPI scale factors like 125%).

> It would be good to know if we should accept this as a fact of life and keep
> working around it or whether we can expect this to be fixed some day.

For now, I think this is a fact of life -- but I'm not confident enough to close it as WONTFIX or INVALID.  It's possible we'll eventually come up with a better solution that addresses the tradeoffs somehow, but I'm not seeing one at the moment.
Flags: needinfo?(dholbert)
Summary: Borders and backgrounds don't scale the same, in full-page-zoom → Borders and backgrounds don't scale the same, under full-page-zoom or HiDPI
Blocks: 1399577
No longer depends on: 1399577
Apparently the google.com and amazon.com front-pages have some minor visual :hover glitches that are caused by this bug on some HiDPI configurations (175% in particular) & also with high full-page-zoom-levels, as discussed over in bug 1399577.

Also: bug 332275 will potentially open up another way for users to hit versions of this bug more frequently across the web. (by letting users set a value other than 100% as their default full-page-zoom value)

We're not entirely on our own in our current behavior, FWIW -- Safari 11 matches us on this behavior (i.e. it shows red when you scale attachment 360825 [details] up or down with full-page-zoom, and it has the same issues described for us over in bug 1399577).  But Edge and Chrome have taken a different approach and have none of these problems (though presumably their approach causes different problems).

It'd be worth investigating what Edge/Chrome do here and seeing if it'd be worth changing, since this is a bit of a webcompat issue (at HiDPI, per bug 1399577), and it's an unintuitive/unexpected behavior for web developers to have to worry about coding for.
Summary: Borders and backgrounds don't scale the same, under full-page-zoom or HiDPI → Borders scale differently than other sizes (e.g. margins, content-sizes, backgrounds), under full-page-zoom or HiDPI
Whiteboard: [webcompat]
"But Edge and Chrome have taken a different approach and have none of these problems (though presumably their approach causes different problems)" Daniel I think I may have found one of these such issue: both Edge and Chrome have this issue when system scaling is 175%, but Firefox doesn't https://bugs.chromium.org/p/chromium/issues/detail?id=793060

If changing Firefox to behave like Chrome and Edge would result in Firefox also having that issue on twitter.com, maybe Firefox made the right choice.
Duplicate of this bug: 1427391

Migrating Webcompat whiteboard priorities to project flags. See bug 1547409.

Webcompat Priority: --- → ?

See bug 1547409. Migrating whiteboard priority tags to program flags.

Blocks: 1569502

This problem can also occur for the widely used shapes defined with css.
For example the following will result in gaps for Firefox' max zoom of 300%:

<div style="width: 16px; height: 16px; border: 1px solid red">
  <div style="border: 8px solid red; border-color: transparent red; width: 0"></div>
</div>

It is possible to work around the issue by not defining the outer size and using display: inline-block.
I found the behaviour unexpected though.

I could also reproduce this with display: inline-block. A minimal testcase is attached; it originally broke on a real site, with a demo about mazes (https://www.jamisbuck.org/mazes/).

Note that the testcase will render correctly at the default zoom level, but there will be discontinuities in the background as well as in the width of the maze.

Attached file testcase 2

So, borders are snapped to device pixels here: https://searchfox.org/mozilla-central/rev/9b99e1d9c6cf83539674cb016c7373f549ba59ca/servo/components/style/properties/gecko.mako.rs#516

It doesn't seem like other browsers do this anymore... We should probably change this. David, is there any good reason for this behavior nowadays? I always found the "round to dev-pixels" pretty weird.

Flags: needinfo?(dbaron)

I guess the most useful feature of this is making border: 0.3px and such not round down to 0px...

The most reduced test-case would be data:text/html,<div style="border: 0.1px solid;">Foo, I suspect. getComputedStyle(document.querySelector("div")).borderTopWidth would return 0.1px in other browsers, and 0.5px in Firefox (in a HiDPI display, 1px in a regular display, I guess).

Let graphics snap as it wants before doing so. Rendering of small borders looks
fine in here even with this patch.

This enables further cleanups too.

I sent a patch removing this, and confirmed it fixes both test-cases, but I haven't submitted it for review yet (so take it as a WIP). Let's see what does CI say: https://treeherder.mozilla.org/#/jobs?repo=try&revision=f591313619e690ee25703f5da15fb80a43dc67aa

I think there still is good reason to do this snapping. It produces better visual results; without this, some widths of borders are different widths in device pixels depending on their position, which produces very bad effects. There's a much longer explanation somewhere that I'd like to find later.

I guess the most useful feature of this is making border: 0.3px and such not round down to 0px...

The rounding code to do this at least used to explicitly round anything between 0 and 1 device pixels up to 1.

(In reply to David Baron :dbaron: 🏴󠁵󠁳󠁰󠁡󠁿 ⌚UTC-5 from comment #28)

I think there still is good reason to do this snapping. It produces better visual results; without this, some widths of borders are different widths in device pixels depending on their position, which produces very bad effects. There's a much longer explanation somewhere that I'd like to find later.

While that may be true, that's also an issue for a bunch of other stuff like backgrounds and such, right? I think it's mostly graphics' job to snap pixels to device position. Asymmetric borders are not great though.

In any case I think rounding at computed-value time is pretty weird... The fact that the computed style is affected by your DPI has always looked like a bug to me.

(In reply to David Baron :dbaron: 🏴󠁵󠁳󠁰󠁡󠁿 ⌚UTC-5 from comment #29)

The rounding code to do this at least used to explicitly round anything between 0 and 1 device pixels up to 1.

Yeah it still rounds anything non-zero to 1 dev px.

Also, don't you have the same issue anyway with border widths depending on position if you have a subpixel width between them?

From the CI run in comment 27, a bunch of tests fail:

  • Vast majority of them testing for our current snapping in particular (not too concerned about those).
  • Some of them are minor antialising bits which can be trivially fixed (the fragmentation and pagination-related ones).

All in all those are fixable, but I think if we landed my patch at the very least should not just remove the code, but keep it disabled only on Nightly for quite a bit to watch for regressions...

Duplicate of this bug: 1616150
Duplicate of this bug: 890383

I am running into this issue when I create buttons with borders that are only enabled when you hover over the button. This is using Firefox 75.0 on Windows 10. Here is a simple test case:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Firefox border bug</title>
<style>
.button {
	background-color: black;
	font-family: Arial, sans-serif;
	width: 100px;
	height: 50px;
	line-height: 50px;
	text-align: center;
	color: white;
}
.button:hover {
	border: 2px solid black;
	color: black;
	background-color: white;
	width: 96px;
	height: 46px;
	line-height: 46px;
}
a {
	text-decoration: none;
}
td {
	padding: 5px;
}
</style>
</head>
<body>
<table>
<tbody>
<tr>
<td>
<a href="http://www.example.com">
<div class="button">
Click Here
</div>
</a>
</td>
<td>
<a href="http://www.example.com">
<div class="button">
Click Here
</div>
</a>
</td>
</tbody>
</table>
</body>
</html>

If you view this in Firefox 75.0 with the Windows scale set to 125%, then the borders when the button is hovered over will display as 1.6px (instead of 2px). The borders will also show with incorrect sizes if the Windows scale is set to anything other than 100%. As a result, if you hover over the left button in this example, the right button will shift slightly to the left, because the width of the left button (including border) is reduced to 99.2px instead of 100px. If you view this in Firefox with the Windows scale set to 100% it displays correctly, and it displays correctly in all other browsers I have tested (Internet Explorer 11.778.18362.0, Chrome 81.0.4044.122, Edge 44.18362.449.0, Opera 68.0.3618.56).

David, Mats: Phabricator-bugzilla integration seems slow / broken today, but what do you think of https://phabricator.services.mozilla.com/D75360?

It seems closer to what other browsers are doing based on the discussion in the linked Chromium bug, and allows subpixel borders to work as expected.

Flags: needinfo?(mats)

Daniel, your feedback on the above revision would also be appreciated.

Flags: needinfo?(dholbert)

I'm thinking maybe we should ceil rather than floor the border line width, so that we don't ever leave gaps. But on the other hand that can cause borders to overlap.

This seems to match what other browsers do, and seems saner layout-wise,
at least.

So if you're flooring at paint time, which edge are you aligning with (for the cases where the paint-time flooring changes the number of pixels actually covered)? (top/left edge? outer edge?) And what do other browsers do?

It seems to me that aligning with the outer edge would be most desirable since that would avoid having a device pixel of background visible outside the border in those cases.

We align to the outer edges, yeah. I believe other browsers do the same.

Assignee: nobody → emilio
Attachment #9149083 - Attachment description: Bug 477157 - Ceil border and outline widths to 1 dev px at computed value time, but don't floor others to dev pixels until paint time. → Bug 477157 - Ceil border and outline widths to 1 dev px at computed value time, but don't floor others to dev pixels until paint time. r=mats!,dbaron!
Status: NEW → ASSIGNED

(In reply to Emilio Cobos Álvarez (:emilio) from comment #25)

The most reduced test-case would be data:text/html,<div style="border: 0.1px solid;">Foo, I suspect. getComputedStyle(document.querySelector("div")).borderTopWidth would return 0.1px in other browsers, and 0.5px in Firefox (in a HiDPI display, 1px in a regular display, I guess).

Does the approach decided, if decided, i.e "snapping?" to outer edges - does this eliminate the "leak" of the devicePixelRatio (as per email mid January). Or is this still layout-exposed?

(In reply to Simon Mainey from comment #42)

Does the approach decided, if decided, i.e "snapping?" to outer edges - does this eliminate the "leak" of the devicePixelRatio (as per email mid January). Or is this still layout-exposed?

It's still exposed as we round up to at least one device pixel, and that is still exposed in getComputedStyle and other layout APIs. It's a bit less visible though.

(In reply to Emilio Cobos Álvarez (:emilio) from comment #37)

Daniel, your feedback on the above revision would also be appreciated.

I may not have time to look closely at this before you're ready to land it, but I'll bet mats' feedback likely captures any objections/concerns that I might have on the approach.

(I'm happy that we're addressing this compat issue & minor footgun - thanks for tackling it!)

Flags: needinfo?(dholbert)
Pushed by ealvarez@mozilla.com:
https://hg.mozilla.org/integration/autoland/rev/5df17ecbcaa1
Ceil border and outline widths to 1 dev px at computed value time, but don't floor others to dev pixels until paint time. r=mats,dbaron
Pushed by emilio@crisal.io:
https://hg.mozilla.org/integration/autoland/rev/04543b8ded50
Annotate one more test as passing, and fix build in older compilers.
Status: ASSIGNED → RESOLVED
Closed: 4 months ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla79
Regressions: 1645008
Flags: needinfo?(mats)
Flags: needinfo?(dbaron)

I reverted this change, see https://hg.mozilla.org/integration/autoland/rev/e9818ea993d02a724e56da4dd9167fc1179de68f / bug 1645008.

Though that means that probably we should just WONTFIX this.

Status: RESOLVED → REOPENED
Resolution: FIXED → ---
Attachment #9149083 - Attachment is obsolete: true

Alert before backout:

== Change summary for alert #26249 (as of Wed, 17 Jun 2020 06:18:53 GMT) ==

Regressions:

10% facebook-cold Similarity2D android-hw-p2-8-0-android-aarch64-shippable opt 0.77 -> 0.69
8% facebook-cold Similarity android-hw-p2-8-0-android-aarch64-shippable opt 0.78 -> 0.71

For up to date results, see: https://treeherder.mozilla.org/perf.html#/alerts?id=26249

Keywords: perf-alert
You need to log in before you can comment on or make changes to this bug.