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)

defect

Tracking

()

People

(Reporter: dholbert, Unassigned)

References

Details

(Keywords: regression, testcase)

Attachments

(6 files, 1 obsolete file)

Attached file testcase 1 (deleted) —
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".
Attachment #8578801 - Attachment description: test.html → testcase 1
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.
Attachment #8578805 - Attachment description: reference case 1 (w/ dynamic tweak already made) → reference case 1 (no dynamic tweak; just using testcase's final style)
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
Blocks: 1143781
> 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
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.)
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.
(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.)
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
Attached file testcase 2 (deleted) —
Here's a more-reduced testcase, without ::before and with fewer levels of nesting.
Attached file reference case 2 (obsolete) (deleted) —
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.)
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.
Attached file reference case 2 (deleted) —
[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
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.
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).
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.
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
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
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.)
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.
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
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
Severity: normal → S3
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: