Re: [CSS21] Clearance error - hypothetical border edge should be actual border edge (Was: Re: [css2.1] Issue 158 and Issue 178 Resolution)

On 25/08/2010 17:47, Bruno Fassino wrote:
> I've not been able to follow all the latest details of this
> discussion, anyway I've two naive questions.
>
> On 08/23/2010 03:40 PM, fantasai wrote:
>>
>> # Computing the clearance of an element on which 'clear' is set is
>> # done by first determining the hypothetical position of the element's
>> # top border edge within its parent block.<del>This position is
>> # determined after the top margin of the element has been collapsed
>> # with previous adjacent margins (including the top margin of the
>> # parent block).</del>  <ins>This position is the same as the where
>> # the actual top border edge would have been if the element had a
>> # non-zero top border and its 'clear' property had been 'none'.</ins>
>
>
> Is "top" in the above last line intended? If yes, I must have missed
> something, since I thought the discussion was moving toward using
> "bottom" there.

It was... but then the pieces of the puzzle finally fitted together, and 
I was able to see what 9.5.2 was trying to say and what the WG was 
hoping it says.

It turns out that what 9.5.2 is trying to say is:

"Use some notion of hypothetical top border position to determine if the 
clearing element is past the relevant floats.  If clearance is necessary 
then the appropriate margin collapsing is inhibited, and the clearance 
is set to the greater of the amount necessary to put the real top border 
flush with the bottom of the float and the amount necessary to put it 
where the hypothetical border position was (to cover the case where 
inhibiting the margin collapsing causes the float to move upwards)."

What the WG was hoping it says is the same as the above but with the 
proviso that there be no unexpected loss of margins and no unexpected 
whitespace.

Trouble is, as test cases show, the notion of hypothetical top border 
position used in the spec (namely, temporary top border) doesn't satisfy 
the WG's goal... and nor does the notion we were contemplating in which 
a temporary bottom border is introduced.  This deters us from both these 
notions.

So we need to go back to first principles to understand what we're 
trying to achieve.  A good first step is to ignore the possibility of a 
self-collapsing clearing element.  Then we all intuitively know what we 
would like the hypothetical border top position to be: it's merely the 
"obvious"/real border top position.  It makes complete sense to use this 
to determine if clearance is necessary, and the case for using some 
other notion would have to be boldly fought, since any author would be 
likely to say, "hang on, that browser's introduced clearance when it's 
patently obvious it wasn't necessary" (or vice versa).  We violate the 
WG's goal, even for "easy" cases, if we use any other notion.  (We were 
getting ahead of ourselves in the earlier threads when trying to choose 
between other, different notions; we should have already dismissed them 
all on the basis of much easier test cases than the ones we were 
considering.)

As you noted, the difficulty for my preferred notion – real top border 
position – arises when the clearing element is self-collapsing, because 
the spec's convention for this position is non-intuitive, which in turn 
makes deciding upon the desired clearance behaviour rather difficult. 
(Although we're already winning, because we've confined any ugly 
renderings to atypical, self-collapsing cases.)

Let's see what happens in your test case:

> On 08/23/2010 7:23 PM, Anton Prowse wrote:
>>
>> I suggest the
>> following change to resolve both this issue and Issue 158:
>>
>>   # Computing the clearance of an element on which 'clear' is set is
>>   # done by first determining the hypothetical position of the element's
>>   # top border edge within its parent block<del>. This position is
>>   # determined after the top margin of the element has been collapsed
>>   # with previous adjacent margins (including the top margin of the
>>   # parent block).</del>  <ins>as if the value of its 'clear' property
>>   # had been 'none', as per the rules in 8.3.1.</ins>
>>   #
>>   # If this hypothetical position of the element's top border edge is
>>   # not past the relevant floats, then clearance<del>must be</del>
>>   #<ins>is introduced and margins collapsed anew according to the rules
>>   # in 8.3.1.
>>   #
>>   # Then the amount of clearance is</ins>  set to the greater of:
>>   #
>>   # 1. The amount necessary to place the<ins>top</ins>  border edge of
>>   #    the block even with the bottom outer edge of the lowest float
>>   #    that is to be cleared.
>>   # 2.<del>The amount necessary to make the sum of the
>>   #    following...</del>
>>   #<ins>The amount necessary to place the top border edge of the
>>   #    block at the previously-computed hypothetical position.</ins>
>
>
> I remember we already discussed the idea to use full (normal) margin
> collapsing to compute the hypothetical position, and we saw this
> produces "unexpected" results in some cases, like this one:
>
> <div style="background: yellow">before</div>
> <div style="background: lime">
>         <div style="float: left; height: 50px; width: 100px;
> background:blue"></div>
>         <div style="clear: left; margin-bottom: 100px"></div>
> </div>
> <div style="background: yellow">next</div>
>
> Unless I'm missing something, your formulation still produces an
> "unexpected" result here.

Indeed, it's a nasty result.  I analyzed this test case in detail in 
[1], but allow me to quickly summarize that again:

First, assume clear:none.  The float's parent P is self-collapsing, so 
by 8.3.1.6.2, P's top border position is 100px below the bottom of 
"before".  The clearing element C is also self-collapsing, and so by 
8.3.1.6.1, C's top border position is the same as P's.  C's top border 
position is not past the float and so we introduce clearance.

C no longer collapses its top margin with P's top margin.  We recompute 
the layout, and we're in that funny case where the float moves upwards. 
  Specifically, the float is now flush with the bottom of "before".  For 
C's current position, 8.3.1.6.2 applies and we find that it is also 
flush with the bottom of "before".  _We observe that inhibiting the 
margin collapsing moved both P's and C's top border position upwards._

The clearance is then the greater of 50px (the amount needed to move C's 
top border downwards to below the float) and 100px (the amount needed to 
move it to the previously-calculated hypothetical position).  Hence 
clearance is 100px, C's bottom margin extends a further 100px downwards, 
and "next" is 200px below the bottom of "before".

Ugly; we've pushed "next" down too far.  In my original analysis, I 
claimed this to be a consequence of the WG's 2007 decision to use bottom 
border to determine the top border of the lime div P (through 
8.3.1.6.2).  Had top border been used instead, as the spec used to 
require, then P's hypothetical top border position would have been 
directly below "before", as would the top of the float and the 
hypothetical position of C's top border.  Clearance would still be 
introduced – but it wouldn't affect the top border position of the lime 
div, and so the float would stay where it was, the clearance would be 
50px and C would "move down" to just below the float, with "next" 100px 
below that, as we might have liked.

However, now that I have more insight into the issue, I see that it the 
situation is more subtle.

Firstly, equally nasty layouts occur with the pre-2007 convention for 
top border position of self-collapsing elements.  Sure, it fixes the 
test case above, but what about the following one:

<div style="background: yellow">before</div>
<div style="background: lime">
        <div style="float: left; height: 50px; width: 100px;
background:blue"></div>
        <div style="clear: left; margin-top: 100px"></div>
</div>
<div style="background: yellow">next</div>

This time, the clearing element has a /top/ margin of 100px.  Let's 
assume the pre-2007 definition of top border position of self-collapsing 
elements.

With clear:none, the float's parent P is self-collapsing, so by 
8.3.1.6.2, P's top border position is flush with the bottom of "before". 
  The clearing element C is also self-collapsing, and so by 8.3.1.6.1, 
C's top border position is the same as P's.  C's top border position is 
not past the float and so we introduce clearance.

C no longer collapses its top margin with P's top margin.  We recompute 
the layout, and P's top border position (and hence the float) stays 
flush with the bottom of "before".  For C's current position, 8.3.1.6.2 
applies and we find that it is 100px below the bottom of "before". 
Clearance is -50px, and C – and hence "next" – moves upwards... meaning 
that C's top margin is effectively ignored.

So you lose either way; the existence of the problem is *not* dependent 
on the choice of temporary border edge used in 8.3.1.6.2 to determine 
top border position.

Secondly, it's not the use of 8.3.1.6.2 /per se/ that causes our 
problem; rather, it's the use of that rule when the self-collapsing 
element has clearance.  Let's look again at the first example.

With clear:none, P's (and hence C's) position was "low", relative to the 
100px collapsed margin to which C's margins contribute.  Inhibition of 
margin collapsing forces P and the float upwards, and also means that 
C's top border position is calculated differently: suddenly it's "high" 
(in fact, incident with P's), relative to the 100px collapsed margin. 
Now, proceeding to use clearance to push C back downwards isn't what 
we're complaining about, assuming that we accept the desirability of 
Calculation 2 in the first place, since we have no basis on which to 
distinguish self-collapsing elements from others in this regard.  What's 
problematic is that C's bottom margin is now in some sense "below" C's 
position, pushing subsequent elements even further downwards when 
clearance moves C downwards, whereas initially it was "above" it when 
clear:none was assumed.

We can solve this with the following modification of 9.5.2, which 
bypasses 8.3.1.6.2 once clearance is introduced, for the situation that 
causes us difficulty:

   # If this hypothetical position of the element's top border edge is
   # not past the relevant floats, then clearance <del>must be</del>
   # <ins>is introduced and margins collapsed anew according to the rules
   # in 8.3.1.  However, if the element's top and bottom margins
   # collapse, and the resulting margin collapsed with the parent's top
   # margin when the hypothetical position was calculated, then the
   # element's top border position is taken to be the same as that of a
   # hypothetical immediately-following sibling with zero top margin and
   # non-zero top border rather than that specified in 8.3.1.
   #
   # Then the amount of clearance is</ins> set to the greater of:

This introduces a change only in the narrow case we're interested in. 
What we're doing is engineering the situation to ensure that the 
element's bottom margin doesn't influence the size of the collapsed 
margin below the redefined top border edge position.  This definition 
change doesn't matter to the clearing element itself, since the whole 
point of clearance is to shift the rendering so that the clearing 
element ends up where we want it; the mechanics of how the end result is 
achieved is unnoticeable.  It remains to verify that it doesn't harm any 
otherwise-good test cases by influencing the position of surrounding 
elements, as follows.

It doesn't affect the clearing element's children, since these are 
necessarily self-collapsing and so their position remains the same as 
that of the element itself (by 8.3.1.6.1), whatever that is defined to 
be.  (Note that this is unlike the situation arising from some unnatural 
notion of hypothetical top border edge being used in 9.5.2, in which a 
child's hypothetical position could end up higher than its parent's 
actual position, causing pathological cases when both parent and child 
have clear set.  That is another reason to discount any notion of 
hypothetical top border edge other than the "real" one I've been 
advocating throughout this thread.)

It doesn't affect the clearing element's previous siblings since they 
share the parent's position – a relationship that is unaffected by the 
introduction of clearance, and a post-clearance position that is 
unaffected by how the position of the clearing element is defined (and 
which is necessarily not below the bottom of the relevant floats, and 
hence is above the post-clearance position of the clearing element).

It doesn't affect the clearing element's subsequent siblings, in the 
sense that these remain in positions at least as low as the clearing 
element's position (due to 8.3.1.6.2's bottom border device).


I believe that this addition to my original proposal excludes all 
undesirable renderings, and hence fulfills the WG's original goal. 
Moreover, it is the only proposal known to do so.


[1] http://lists.w3.org/Archives/Public/www-style/2010Jul/0035.html

Cheers,
Anton Prowse
http://dev.moonhenge.net

Received on Wednesday, 25 August 2010 23:20:37 UTC