Hi everyone,

Pradeep's excellent pull request [1] for absolute positioning raised some thorny issues relating to solving geometry constraints for absolutely positioned blocks in a parallel setting. The fundamental issue is that absolute positioning can depend not only on width and height of the containing block, but also the top/left/right position of the hypothetical box created by its static position. This is in conflict with our two-direction approach for computing widths and heights: widths are computed *top-down*, so that kids' widths depend on parents' widths, but heights are computed *bottom-up*, so that parents' heights depend on kids' heights.

There are two basic approaches to dealing with absolutely-positioned frames: (1) parent the absolutely-positioned frame to its containing block, and keep track of a separate "hypothetical frame" (which is what Gecko seems to do, with `nsHTMLReflowState::CalculateHypotheticalBox()` and `nsFrameManager::GetPlaceholderFrameFor()`), or (2) keep track of the containing block separately from the parent and use the "hypothetical frame" as the real frame, which is WebKit's approach (see the comments in `RenderObject::container()` and `RenderBox::computePositionedLogicalWidth()`).

The pull request #1681 takes approach #2. It deals with the parallel hazard by deferring height computation of absolutely positioned blocks until display list construction time (which is top-down). However, this has a problem in that the overflow for the containing block cannot be correctly calculated, because the height is not yet known. This will make it impossible for us to construct the display list only for frames in the visible region, since that depends on having correct overflow information.

After some thought I've begun to think that approach #2 (WebKit's approach) is a dead-end in a parallel setting. The fundamental problem is that the height of an absolutely-positioned block cannot be determined without knowing the height of its containing block (if `top` and `bottom` are specified). Yet knowing the overflow region of *at least* the containing block depends on knowing the height of the absolutely-positioned blocks for which it is the containing block. I say "at least" because if we were to adopt WebKit's approach (putting the absolutely-positioned frame at its hypothetical static location) then potentially many more intervening blocks would have their overflow regions depend on their absolutely positioned descendants:

             ^    height +------------------+ overflow
             |   +-------+ containing block |<--------+
             |   |       +--------+---------+         |
             |   |                | overflow          |
             |   |                v                   |
             |   |            +-------+               |
             |   |            | block |               |
    direction|   |            +---+---+               |
           of|   |                | overflow          |
    traversal|   |                v                   |
             |   |            +-------+               |
             |   |            | block |               |
             |   |            +---+---+               |
             |   |                | overflow          |
             |   |                v                   |
             |   |  +----------------------------+    |
             |   +->+ absolutely positioned flow +----+
             |      +----------------------------+

That's a potentially unbounded amount of extra information that needs to travel in the opposite direction to the traversal.

By contrast, approach #1 (Gecko's approach) avoids the unbounded amount of information flow in the opposite direction to the traversal. The height and overflow region only need to travel one step: from containing block to its immediate absolutely positioned flow child and back. This can be implemented by simply moving the computation of height and overflow of absolutely-positioned frames to the parent. This loses a small amount of parallelism during the assign-heights phase, but it should be small with fixed overhead.

Width assignment becomes potentially trickier with absolute positioning. Recall that in Servo actual width computation is a top-down traversal. But because the width of an absolutely-positioned frame can depend on its hypothetical frame's static position, it is possible that an absolutely-positioned frame will not have its width ready by the time the traversal wants to compute it, because its hypothetical frame has not yet been reached. This can be remedied by simply (a) having containing blocks *not* enqueue their absolutely-positioned kids in the usual manner during the parallel top-down traversal and (b) having the *hypothetical* frame be the frame responsible for computing its associated absolutely positioned frame's width, and enqueuing its kids.

Unfortunately, I don't know off the top of my head the best way to enforce that this logic is correct in the Rust type system. Races during layout can cause memory safety problems, so we need to make sure the parallel traversal logic is correct. We may be able to do something like (a) add on-in-production asserts that hypothetical frames encode only absolutely-positioned flows; (b) add on-in-production asserts that absolutely-positioned flows are never enqueued for parallel layout directly by their containing blocks; (c) lock down the "is this absolutely positioned?" flag to only be able to be touched by flow construction, never by layout itself. This may be sufficient for making the system foolproof, although I haven't thought too hard about it.

There is one minor issue of reference counting flows: there will need to be a reference from the absolutely-positioned flow to its hypothetical flow (so that the flow can find its static position if need be) and vice versa (so that width can be assigned as above). At least one of these references must be a strong reference. This is not too big of an issue because we need to reference count flows for incremental reflow at some point anyway.

As an added bonus, adopting approach #2 (Gecko's approach) will eliminate the need to push down "containing block" information and to have the idea of a "container" separate from the "parent", giving us a small algorithmic performance improvement over approach #1. Perhaps not having to store this information is part of the reason we're so fast at layout today, even sequentially.

Anyway, for now I'm inclined to just not handle hypothetical boxes and just make up some values (e.g. zero) where the spec says to calculate their positions. This will almost certainly break the Web, so I'm not suggesting that we do this for the long run, but I think landing complicated work like this is best done piecemeal.

Thoughts?

Patrick

[1]: https://github.com/mozilla/servo/pull/1681
_______________________________________________
dev-servo mailing list
dev-servo@lists.mozilla.org
https://lists.mozilla.org/listinfo/dev-servo

Reply via email to