Open
Bug 1144259
Opened 10 years ago
Updated 2 years ago
{inc} "display:inline; position:relative" elements will misposition their abspos descendants, on any reflow where the inline element changes position
Categories
(Core :: Layout: Positioned, defect)
Core
Layout: Positioned
Tracking
()
NEW
People
(Reporter: dholbert, Unassigned)
References
Details
(Keywords: regression, testcase)
Attachments
(6 files, 1 obsolete file)
STR:
1. Load testcase.
2. Watch content jump after dynamic tweak (at 200ms)
3. Resize window, to force a relayout.
EXPECTED RESULTS:
The text "STOMP" should be superimposed on top of "hello" the whole time.
ACTUAL RESULTS:
The text "STOMP" *shifts down*, when the dynamic tweak happens. But when you resize the window & trigger a relayout, then it ends up back in its original spot, on top of "hello".
Reporter | ||
Updated•10 years ago
|
Attachment #8578801 -
Attachment description: test.html → testcase 1
Reporter | ||
Comment 1•10 years ago
|
||
Here's a reference case for "testcase 1", with the same style that the testcase ends up at.
Here, "STOMP" is superimposed on top of "hello", showing the EXPECTED RESULTS for the testcase.
Reporter | ||
Updated•10 years ago
|
Attachment #8578805 -
Attachment description: reference case 1 (w/ dynamic tweak already made) → reference case 1 (no dynamic tweak; just using testcase's final style)
Reporter | ||
Comment 2•10 years ago
|
||
I can reproduce this in Firefox 28 (and probably older builds as well; I just happen to have that one handy).
So, not a regression (or not a recent one, at least).
This is the underlying cause of bug 1143781, though. We've recently started hitting a version of this bug in the Gaia Calendar app, though, due to a recent flexbox optimization (bug 1142686) which removed some redundant reflows. (Those extra reflows effectively flushed out the effects of this bug, in the same way that you can flush out the effects by resizing the window with this bug's testcase loaded.)
Keywords: testcase
Comment 3•10 years ago
|
||
> I can reproduce this in Firefox 28
I can't. It seems to be a regression between 30 and 31 for me (on Linux64).
(perhaps your "28" auto-updated itself?)
Keywords: regression
Reporter | ||
Comment 4•10 years ago
|
||
Nope, my 28 is indeed 28 (on linux64 as well):
Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0
Though I've just noticed that this manifests a bit differently on 28 vs. trunk. On trunk, the testcase's tweaked layout fixes itself after I resize the window. On Firefox 28, it does not. (The reference case renders correctly, though.)
Reporter | ||
Comment 5•10 years ago
|
||
I downloaded a few more old releases -- the bug goes back at least as far as Firefox 15. (specifically, STOMP shifts down when the onload-triggered dynamic tweak happens; and it doesn't look like the reference case.)
The layout-fixup-on-window-resize part does seem to have been introduced between Firefox 30 & 31, too -- I suspect that's what mats was seeing in comment 3.
Reporter | ||
Comment 6•10 years ago
|
||
(The layout-fixup-on-window-resize change in Firefox 31 is an improvement, I think. It means with sufficiently-thorough reflowing, we *can* actually produce the same layout that we'd have produced if the dynamic tweak had been made up-front before we'd done any layout -- i.e. we can match the reference layout. whereas, before that point, we can't produce the reference layout, after the dynamic tweak has been made.)
Reporter | ||
Comment 7•10 years ago
|
||
The layout-fixup-on-window-resize behavior started in this range:
https://hg.mozilla.org/mozilla-central/pushloghtml?fromchange=dd50745d7f35&tochange=e71ed4135461
In that range, bug 984226 is the only thing to touch layout/style, and it's about sending a different type of hint on a style-change, so it seems likely to be responsible for this behavior-change.
Marking as "depends-on" that bug, since it didn't so much cause this bug, but rather made our behavior slightly better here, per comment 6.
Depends on: 984226
Reporter | ||
Comment 8•10 years ago
|
||
Here's a more-reduced testcase, without ::before and with fewer levels of nesting.
Reporter | ||
Comment 9•10 years ago
|
||
Reporter | ||
Comment 10•10 years ago
|
||
Some notes from debugging testcase 2 a bit:
tl;dr: it seems we're failing to update the y-position of the abspos child, when its abspos-containing-block changes.
Specifically:
(1) In the initial rendering (before the dynamic tweak), the frametree shows the span at y-position -900, the placeholder at position 900 within that (so, at the upper-left corner of the visible area), and the abspos element at position 0 within its abspos containing block (the viewport) -- also at the upper-left corner of the visible area.
Canvas(html)
Block(html)
Block(body) {0,0,31740,0}
Inline(span) {0,-900,0,1140}
Placeholder(div) {0,900,0,0}
AbsoluteList
Block(div) {0,0,7920,1140}
(2) After the dynamic tweak, the frame-rects' hardcoded y-positions are unchanged -- but the abspos element has been reparented. So its "0" y-position is now relative to the span, which is offscreen (at -900).
Block(body)
Inline(span) {0,-900,0,1140}
Placeholder(div) {0,900,0,0}
AbsoluteList <
Block(div) {0,0,7920,1140}
(3) After we force a reflow by resizing the window, the abspos block finally gets an updated y-position, which puts it back onscreen again:
Block(body)
Inline(span) {0,-900,0,1140}
Placeholder(div) {0,900,0,0}
AbsoluteList <
Block(div) {0,900,7920,1140}
(Frame trees heavily edited to remove irrelevant information.)
Reporter | ||
Comment 11•10 years ago
|
||
One more fact worth noting (which I just realized): the dynamic tweak makes us *recreate the span's content*.
I think the basic problem here is that we're reading the freshly-constructed inline-frame's position, *before we've left nsInlineFrame::Reflow()* -- so we think it's at position 0 instead of -900, and proceed to position the abspos element with that faulty information.
Specifically: nsInlineFrame calls ReflowAbsoluteFrames, which (several layers down) instantiates a nsHTMLReflowState for the abspos frame. And while we're instantiating that reflow state, we hit this line:
http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsHTMLReflowState.cpp?rev=203acd3b8d71#1376
...which gets the offset of our nsInlineFrame, with respect to the containing block (the <body>). And during our first visit to nsInlineFrame::Reflow, this is 0, because our nsInlineFrame doesn't know its position yet. This cbOffset ends up determining the abspos frame's position through several layers (by setting "hypotheticalBox" up one stack-level, which sets ComputedPhysicalOffsets().top here:
http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsHTMLReflowState.cpp?rev=203acd3b8d71#1491
...which sets the "rect" used to position our abspos frame here:
http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsAbsoluteContainingBlock.cpp?rev=43845a1d2f21#448
Then after we've positioned our abspos frame, the inline frame finishes its reflow method and *receives its actual y-position* (so the interpretation of the abspos frame's y-position changes), but we don't reposition the abspos frame to correct for that.
Comment hidden (obsolete) |
Reporter | ||
Comment 13•10 years ago
|
||
[sorry, disregard comment 12; the difference I was observing there was just due to me misspelling "position" in the reference case. :) *facepalm*]
Attachment #8578918 -
Attachment is obsolete: true
Reporter | ||
Comment 14•10 years ago
|
||
Note: "reference case 2" *almost* hits the issue described in comment 11, but it's saved because in our initial layout, we do two reflows back-to-back, inside of nsHTMLScrollFrame::ReflowContents -- we make two calls to ReflowScrolledFrame. In the second of these reflows, we've already got the nsInlineFrame at the correct position, so we get correct information for "cbOffset.top" in the code described in comment 11.
The second ReflowScrolledFrame call is conditional on aState->mReflowedContentsWithVScrollbar, which is true in the initial layout, but is false on incremental layout like the one that happens after testcase 1's dynamic tweak. If I force us to skip the second ReflowScrolledFrame call, in the initial layout of "reference 2", then I see this same bug there.
Reporter | ||
Comment 15•10 years ago
|
||
Here's an even simpler testcase, with the span being "position:relative" all along, but just going from display:none to display:inline at 200ms. This triggers the bug. (the abspos child ends up offscreen, until you resize the window to force a second reflow -- which produces correct layout due to having now-valid cbOffset information, as described above).
Reporter | ||
Comment 16•10 years ago
|
||
This is only an issue for _inline-level & relpos_ elements that contain abspos content -- it's not an issue for *block-level* relpos elements (or inline-level & abspos elements, which are promoted to block-level).
This is because the "cbOffset" determination is the offset between the *containing block* and the *abspos-containing element* -- and for all these other cases, those will be the same frame, so the offset is always 0.
Reporter | ||
Updated•10 years ago
|
Summary: {inc} When an inline element gets dynamic "position:relative", its abspos children are mispositioned → {inc} "display:inline; position:relative" elements will misposition their abspos descendants, on first reflow
Reporter | ||
Updated•10 years ago
|
Summary: {inc} "display:inline; position:relative" elements will misposition their abspos descendants, on first reflow → {inc} "display:inline; position:relative" elements will misposition their abspos descendants, on first reflow after frame-construction
Reporter | ||
Updated•10 years ago
|
Summary: {inc} "display:inline; position:relative" elements will misposition their abspos descendants, on first reflow after frame-construction → {inc} "display:inline; position:relative" elements will misposition their abspos descendants, on first reflow. (Each position-change requires an extra reflow to update abspos elements.)
Reporter | ||
Comment 17•10 years ago
|
||
Here's one last (probably?) testcase, without any frame reconstruction. Here, I'm just dynamically tweaking the y-position of the <span> [via modifying its "vertical-align" value, in the presence of larger text that it initially is baseline-aligned with].
This also triggers the bug. (The abspos child is mispositioned until you force a reflow by resizing the window.)
This demonstrates that, in this situation with a relpos inline element, its abspos descendants are always trailing behind, in terms of the positioning information that they're using.
Reporter | ||
Updated•10 years ago
|
Summary: {inc} "display:inline; position:relative" elements will misposition their abspos descendants, on first reflow. (Each position-change requires an extra reflow to update abspos elements.) → {inc} "display:inline; position:relative" elements will misposition their abspos descendants, on any reflow where there position changes
Reporter | ||
Updated•10 years ago
|
Summary: {inc} "display:inline; position:relative" elements will misposition their abspos descendants, on any reflow where there position changes → {inc} "display:inline; position:relative" elements will misposition their abspos descendants, on any reflow where the inline element changes position
Updated•6 years ago
|
Updated•2 years ago
|
Severity: normal → S3
You need to log in
before you can comment on or make changes to this bug.
Description
•