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

On 26/08/2010 01:18, Anton Prowse wrote:
> 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.

> 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).

If desired, we could go one step further and remove the discontinuities 
that the clearance concept has historically introduced.  Specifically, 
when a self-collapsing clearing element collapses with its parent and 
with self-collapsing following siblings, the position of the following 
siblings (currently at the same position as the parent due to 8.3.1.6.1) 
may change, relative to the clearing element, if clearance is 
introduced, due to the use of 8.3.1.6.2 (no collapsing with parent) to 
calculate their new position.

The following test case demonstrates this phenomenon:

<div>before</div>
<div style="float:left; width:50px; height:50px"></div>
<div>
	<div style="clear:left"></div>
	<div style="margin-top:20px"></div>
	<div>next</div>
</div>

Under every clearance proposal touched upon so far (including the 
current spec, the pre-2007 spec, and my own proposal above), the second 
child's position is the same as the first child's prior to clearance, 
but moves to 20px below the first child's once clearance is introduced.

To address this, move my above modification to 8.3.1 (which is arguably 
where it should have been anyway) and tweak that section with a new 
8.3.1.6.2.2 as follows:

  # If the top and bottom margins of a box are adjoining, then it is
  # possible for margins to collapse through it. In this case, the
  # position of the element depends on its relationship with the other
  # elements whose margins are being collapsed.
  #   * If the element's margins are collapsed with its parent's top
  #     margin, the top border edge of the box is defined to be the
  #     same as the parent's.
  #   * Otherwise, either the element's parent is not taking part in
  #     the margin collapsing, or only the parent's bottom margin is
  #     involved.
  |<ins>  * If the element has clearance but its margins collapsed with
  |         its parent's top margin when _determining if clearance were
  |         needed_<link to appropriate paragraph of 9.5.2>, the top
  |         border edge position is the same as that of a hypothetical
  |         immediately-following sibling with zero top margin and
  |         non-zero top border.
  |       * Otherwise, if the element's margins are collapsed with the
  |         top margin of a previous sibling that has clearance and
  |         whose margins collapsed with its parent's top margin when
  |         _determining if clearance were needed_, the top border edge
  |         position is the same as that previous sibling's.
  #       * Otherwise,</ins> the position of the element's top border
  #         edge is the same as it would have been if the element had a
  #         non-zero bottom border.

The new bullet point ensures that subsequent self-collapsing siblings 
still line up alongside the clearing element, as they did before 
clearance were introduced.


> [..] (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.

In fact, the case of parent and child both with clear set presents 
difficulties anyway, whatever the clearance implementation details we 
decide upon.  Consider the following test case (or just jump to the 
conclusion eight paragraphs further down!):

<div>before</div>
<div style="margin-top:20px">
	<div style="float:left; width:40px; height:40px">A</div>
	<div style="margin-top:40px; clear:left">
		<div style="float:left; width:30px; height:30px">B</div>
		<div style="margin-top:50px;clear:left">grandchild</div>
	</div>
</div>

For ease of description, let's set up a y co-ordinate axis with 0 flush 
with the bottom of "before" and y>0 heading downwards.

First assume that we handle the child clearing element before the 
grandchild clearing element.  Assuming clear:none on all elements, 
there's a 50px collapsed margin below which lies the top border edge of 
all three in-flow divs at y=50.  Clearance is introduced for the child, 
the top of float A moves upwards to y=20 and the child (and hence the 
top of float B) moves downwards to y=60, flush with the bottom of float 
A. Clearance is introduced for the grandchild – and we have to stop and 
decide what's supposed to happen to the child.

Presumably the child doesn't now change position, even though its top 
margin no longer collapses with that of the grandchild and even though 
the grandchild's margin was the only reason the child has ended up as 
low as it currently is?  (I'm pretty sure I can come up with some fairly 
evil scenarios if recalculation is permitted.)

So, assuming the child stays where it is, the grandchild ends up at 
y=90, flush with the bottom of float B.

Now assume that we handle the grandchild clearing element before the 
grandchild clearing element.  Assuming clear:none on all elements, 
clearance is introduced for the grandchild, the top of floats A and B 
move upwards to y=40, and the grandchild moves downwards to y=80, flush 
with the bottom of float A.  Next, clearance is introduced for the child 
– and we have to stop and decide what's supposed to happen to the 
grandchild.

Again, let's suppose that the grandchild stays fixed.  The top of float 
A moves upwards to y=20 and the child (and hence the top of float B) 
moves down to y=60.  The position of the grandchild is 10px *above* the 
bottom of float B – which forces a recalculation for the grandchild to 
move it to flush with the bottom of float B.

With this test case, the final renderings are the same.  However, if we 
respecify the height of floats A and B as 20px and 10px respectively, 
the final renderings are different: the parent is at y=20 and the 
grandchild at y=60, yet the child is at y=50 when the child is handled 
before the grandchild but at y=40 when the child is handled after the 
grandchild.  (This time, no recalculation is necessary in the latter case.)

To conclude, the clearance concept remains underspecified.  The least 
troublesome approach would be to state that clearing elements are 
treated in depth-first tree order, and that once calculated, an element 
does not change position when performing clearance calculations for 
subsequent elements.


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

Received on Saturday, 28 August 2010 09:40:01 UTC