Closed
Bug 980247
Opened 11 years ago
Closed 11 years ago
position:sticky elements with multiple continuations duplicate offsets on later continuations when parent is reflowed without reflowing the continuations
Categories
(Core :: Layout, defect)
Core
Layout
Tracking
()
RESOLVED
FIXED
mozilla31
People
(Reporter: alin, Assigned: alin)
References
Details
Attachments
(2 files, 9 obsolete files)
1.35 KB,
patch
|
coyotebush
:
review+
|
Details | Diff | Splinter Review |
5.06 KB,
patch
|
Details | Diff | Splinter Review |
I thought the sticky problem only happened on B2G OOP. But the bug exists on desktop as well. As the attached, the symptom happens when launching(including maiming/scrolling/minimizing) the window. The layout rendering was disorder. But it is normal until reloading the page. Bug 973851 Comment 9, 10 are related discussion to the bug. I elaborate the symptom more detailed.
summarize what I found, a normal layout showed up when reloading the html, the flow as below:
nsBlockFrame::ReflowDirtyLines(...) {
...
for ( ; line != line_end; ++line, aState.AdvanceToNextLine()) {
...
if (selfDirty) // selfDirty -> true
line->MarkDirty();
...
if (line->IsDirty() && ...) { // line->IsDirty() -> true
ReflowLine(...); // it will call children's Reflow and PlaceLine
} else {
}
}
}
So the current children's position is calculated correctly. And then the
StickyScrollContainer::PositionContinuations iterates the children to add
the translate against the css(#sticky.top).
But launching the html, the abnormal has the following flow in ReflowDirtyLines:
Eventually the 'selfDirty' was false. Some children's line dirty flag were set from somewhere rather than 'selfDirty'; other children's line dirty flag were not set. So the children without dirty flag wouldn't go into the ReflowLine to reset the right position.
Hence, the StickyScrollContainer::PositionContinuations will calculate the wrong sticky position to children without dirty flag, wrong calculation as below:
StickyScrollContainer::PositionContinuations(nsIFrame* aFrame)
{
...
for (nsIFrame* cont = aFrame; cont;
cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
cont->SetPosition(cont->GetPosition() + translation);
}
}
Get the frame' position and add the translate, and then set back again.
If the frame's position wasn't reset, the sticky position will be incremental.
If a iframe was forced to set dirty, the symptom was gone on desktop and b2g reftest. it seems like:
//http://dxr.mozilla.org/mozilla-central/source/layout/generic/nsBlockFrame.cpp?from=nsBlockFrame.cpp&case=true#1901
line->MarkDirty(); // added
if (line->IsDirty() && (line->HasFloats() || !willReflowAgain)) {
...
}
It feels like a dirty flag wasn't set to sticky case.
Marking the frame dirty and forcing an extra reflow seems like the wrong solution. Instead, we should make the StickyScrollContainer use GetNormalPosition() instead of GetPosition(). (It's possible the normal position isn't currently stored correctly on continuations; if it isn't, we'd have to fix that as well.)
Blocks: 916315
Summary: position: wrong rendering on sticky → position:sticky elements with multiple continuations duplicate offsets on later continuations when parent is reflowed without reflowing the continuations
Comment 2•11 years ago
|
||
Yes, it looks like PositionContinuations should just be written in terms of GetNormalPosition(). I think nsHTMLReflowState::ApplyRelativePositioning is called on continuations, so the stored normal position should be okay?
Comment 3•11 years ago
|
||
(In reply to David Baron [:dbaron] (needinfo? me) (UTC-8) from comment #1)
> Marking the frame dirty and forcing an extra reflow seems like the wrong
> solution. Instead, we should make the StickyScrollContainer use
> GetNormalPosition() instead of GetPosition(). (It's possible the normal
> position isn't currently stored correctly on continuations; if it isn't,
> we'd have to fix that as well.)
Change the following GetPosition to GetNormalPosition could fix this issue.
http://dxr.mozilla.org/mozilla-central/source/layout/generic/StickyScrollContainer.cpp#337
But I found there are two more GetPosition() inside StickyScrollContainer.
http://dxr.mozilla.org/mozilla-central/source/layout/generic/StickyScrollContainer.cpp#260
http://dxr.mozilla.org/mozilla-central/source/layout/generic/StickyScrollContainer.cpp#332
Comment 4•11 years ago
|
||
(In reply to peter chang[:pchang][:peter] from comment #3)
> (In reply to David Baron [:dbaron] (needinfo? me) (UTC-8) from comment #1)
> > Marking the frame dirty and forcing an extra reflow seems like the wrong
> > solution. Instead, we should make the StickyScrollContainer use
> > GetNormalPosition() instead of GetPosition(). (It's possible the normal
> > position isn't currently stored correctly on continuations; if it isn't,
> > we'd have to fix that as well.)
>
> Change the following GetPosition to GetNormalPosition could fix this issue.
> http://dxr.mozilla.org/mozilla-central/source/layout/generic/
> StickyScrollContainer.cpp#337
>
> But I found there are two more GetPosition() inside StickyScrollContainer.
> http://dxr.mozilla.org/mozilla-central/source/layout/generic/
> StickyScrollContainer.cpp#260
> http://dxr.mozilla.org/mozilla-central/source/layout/generic/
> StickyScrollContainer.cpp#332
I think we could keep this GetPosition because it tries to calculate the translation between GetPosition and GetNormalPosition
Comment 5•11 years ago
|
||
The two instances of GetPosition in PositionContinuations would need to change at the same time.
The one at the end of ComputeStickyLimits shouldn't change, because 'rect' is computed by GetAllInFlowRectsUnion in terms of the frames' actual positions, not normal positions.
Assignee | ||
Comment 6•11 years ago
|
||
By my understanding, GetNormalPosition is to keep original relative position to parent. But GetPosition may be affected by SetPosition, which means the position information sometimes can't reflect right position.
Is my understanding wrong?
Attachment #8387559 -
Flags: review?(dbaron)
Comment 7•11 years ago
|
||
Comment on attachment 8387559 [details] [diff] [review]
sticky.patch
Review of attachment 8387559 [details] [diff] [review]:
-----------------------------------------------------------------
::: layout/generic/StickyScrollContainer.cpp
@@ +256,5 @@
> }
>
> // These limits are for the bounding box of aFrame's continuations. Convert
> // to limits for aFrame itself.
> + nsPoint frameOffset = aFrame->GetNormalPosition() - rect.TopLeft();
No, I'm pretty certain this line shouldn't change. Indeed, I hope some of our existing reftests break if you change this.
GetPosition() returns the actual current position of the frame. GetNormalPosition() returns the position minus any effects of relative/sticky positioning.
Comment 8•11 years ago
|
||
(The problem here isn't that the result of GetPosition() is wrong per se, just that adding the same *incremental* sticky positioning change to all continuations isn't always correct.)
Assignee | ||
Comment 9•11 years ago
|
||
Got it.
Thanks.
Assignee | ||
Comment 10•11 years ago
|
||
Got it, thanks.
Assignee | ||
Comment 11•11 years ago
|
||
Updated•11 years ago
|
Attachment #8387559 -
Attachment is obsolete: true
Attachment #8387559 -
Flags: review?(dbaron)
Comment 12•11 years ago
|
||
Comment on attachment 8388426 [details] [diff] [review]
sticky.patch
This looks like the right change to me. It should have a better commit message that describes the fix (instead of the problem). We'll also want at least one test (probably a reftest in layout/reftests/position-sticky), which could be based on your original testcase, and can optionally be in a separate patch.
Anyway, I assume you meant to request dbaron's review again on this.
Attachment #8388426 -
Flags: review?(dbaron)
Assignee | ||
Comment 13•11 years ago
|
||
I found the patch solved partial problem. Another problem is StickyScrollContainer::ComputeStickyLimits.
The calculation of marginRect is wrong. Some cases would hit the condition like original reftest(position-sticky/block-in-inline-3.html).
By tracing the flow:
ComputeStickyLimits =>
nsLayoutUtils::GetAllInFlowRectsUnion(...,nsLayoutUtils::RECTS_USE_MARGIN_BOX) =>
...
nsLayoutUtils::GetAllInFlowBoxes =>
while (aFrame) {
AddBoxesForFrame(aFrame, aCallback); // last frame position differs from normal position
}
AddBoxesForFrame =>
aCallback->AddBox(aFrame) =>
outer->GetOffsetTo =>
nsIFrame::GetOffsetTo =>
for (f = this; f != aOther && f; f = f->GetParent(), i++) {
offset += f->GetPosition(); // problem here!
}
Wrong marginRect union is affectd by the wrong offset of last frame.
In normal case(reloading html), the return value is the same between GetPosition and GetNormalPosition.
In abnornal case, GetPosition is equal to GetNormalPosition.
I'm not sure the how GetPosition differs from GetNormalPosition.
Using GetNormalPosition can't solve the problem as well.
Flags: needinfo?(corey)
Assignee | ||
Comment 14•11 years ago
|
||
(In reply to Abel Lin(alin, abel) from comment #13)
> I found the patch solved partial problem. Another problem is
> StickyScrollContainer::ComputeStickyLimits.
> The calculation of marginRect is wrong. Some cases would hit the condition
> like original reftest(position-sticky/block-in-inline-3.html).
>
> By tracing the flow:
> ComputeStickyLimits =>
> nsLayoutUtils::GetAllInFlowRectsUnion(...,nsLayoutUtils::
> RECTS_USE_MARGIN_BOX) =>
> ...
> nsLayoutUtils::GetAllInFlowBoxes =>
> while (aFrame) {
> AddBoxesForFrame(aFrame, aCallback); // last frame position differs
> from normal position
> }
> AddBoxesForFrame =>
> aCallback->AddBox(aFrame) =>
> outer->GetOffsetTo =>
> nsIFrame::GetOffsetTo =>
> for (f = this; f != aOther && f; f = f->GetParent(), i++) {
> offset += f->GetPosition(); // problem here!
> }
>
> Wrong marginRect union is affectd by the wrong offset of last frame.
> In normal case(reloading html), the return value is the same between
> GetPosition and GetNormalPosition.
> In abnornal case, GetPosition is equal to GetNormalPosition.
>
> I'm not sure the how GetPosition differs from GetNormalPosition.
> Using GetNormalPosition can't solve the problem as well.
correct:
is equal to => is NOT equal to
Comment 15•11 years ago
|
||
It's not clear to me that there's anything wrong with computing the continuation union areas in terms of GetPosition(). As long as the continuations are in the right place relative to each other to begin with, it shouldn't matter, right?
Do you have a test case that would be fixed by changing the union calculations (or is even the original test case still broken)?
Flags: needinfo?(corey)
Assignee | ||
Comment 16•11 years ago
|
||
(In reply to Corey Ford [:corey] from comment #15)
> It's not clear to me that there's anything wrong with computing the
> continuation union areas in terms of GetPosition(). As long as the
> continuations are in the right place relative to each other to begin with,
> it shouldn't matter, right?
Right. But the wrong calculation is translation, not position relative to each other.
However, translation calculation is from ComputeStickyLimits.
>
> Do you have a test case that would be fixed by changing the union
> calculations (or is even the original test case still broken)?
The original test case is still broken.
I am intended to look into how the continuation union is calculated.
I think few folks is aware the test case is broken because the test case is launched from begging, the result is ok. But after launching, if the html is zoomed/scrolled, the result is wrong. The patch solved the incremental problem, but the union calculation is still wrong.
Assignee | ||
Comment 17•11 years ago
|
||
Assignee | ||
Comment 18•11 years ago
|
||
Assignee | ||
Comment 19•11 years ago
|
||
abnormal.mp4 -> sticky.patch
normal.mp4 -> sticky.patch + stick.patch2(unofficial)
The 2nd patch shows the wrong union calculation is in nsIFrame::GetOffsetTo.
So i add a new function NormalPositionGetOffsetTo to get the normal position,
only in StickyScrollContainer::ComputeStickyLimit.
The patch is so ugly, but it can help me to explain how the union is wrong.
Comment 20•11 years ago
|
||
Okay, I see that the first patch seems to fix your test case from comment 0, but doesn't fully fix the similar situation with the block-in-inline-3.html reftest. The second patch seems to help with that, but breaks some other reftests.
Changing the GetPosition() call at the end of ComputeStickyLimits to GetNormalPosition(), which I would have expected to be necessary along with changing the union computation, doesn't make that better. I don't completely understand what's going on in block-in-inline-3.html, though, so I'll poke at it some more.
Comment 21•11 years ago
|
||
After a while in a debugger, I'm not sure I've learned anything new and useful, but I'll try to summarize what I see going on: on alternate reflows (as your video showed), the sticky element in block-in-inline-3.html moves up and down. This happens because the heights of rect and marginRect change (for me, alternating between 3960 and 6000), affecting the containing-block logic. In turn, that's because the result of GetPosition() on the last continuation ("after block") alternates. That makes sense (since it's moving up and down!), so the question is why GetPosition() on the other two continuations doesn't vary, even after having set it in a previous call to PositionContinuations().
Comment 22•11 years ago
|
||
Come to think of it, though, this isn't the same as the original problem of duplicated offsets, so should probably be out of scope for this bug. Could we focus on landing the first patch (including one or more tests) and continue this discussion about block-in-inline-3.html in another bug?
Updated•11 years ago
|
Attachment #8389760 -
Attachment is patch: true
Attachment #8389760 -
Attachment mime type: text/x-patch → text/plain
Assignee | ||
Comment 23•11 years ago
|
||
(In reply to Corey Ford [:corey] from comment #22)
> Come to think of it, though, this isn't the same as the original problem of
> duplicated offsets, so should probably be out of scope for this bug. Could
> we focus on landing the first patch (including one or more tests) and
> continue this discussion about block-in-inline-3.html in another bug?
will do.
Updated•11 years ago
|
Assignee: nobody → alin
Comment on attachment 8388426 [details] [diff] [review]
sticky.patch
How do we ever get in a situation where the different continuations of a sticky element have different (GetPosition() - GetNormalPosition()) from each other? And if we do, don't we end up with a bug if PositionContinuations doesn't get called?
Using GetNormalPosition() here seems reasonable, but now that I think about it more it feels like it's covering up a problem rather than fixing it. Is the problem that nsHTMLReflowState::ApplyRelativePositioning is doing its work in a case where it shouldn't because we've decided to split the frame but haven't actually split it yet? If that's actually the problem, this might be a reasonable workaround, although it would probably be best to also update the comments in nsHTMLReflowState::ApplyRelativePositioning.
>Bug 980247 - position:sticky elements with multiple continuations duplicate offsets on later continuations when parent is reflowed without reflowing the continuations
This could use a better commit message, as corey said. Perhaps:
Bug 980247 - Use offsets from GetNormalPosition() when updating continuations of position:sticky elements instead of assuming that they're all currently offset by the same amount.
>* * *
>try: -b do -p all -u all -t none
Remove the try syntax
r=dbaron with that, but you should also add a reftest for what this is fixing, and this shouldn't land without the test
Attachment #8388426 -
Flags: review?(dbaron) → review+
Comment 25•11 years ago
|
||
(In reply to David Baron [:dbaron] (needinfo? me) (UTC-7) from comment #24)
> Using GetNormalPosition() here seems reasonable, but now that I think about
> it more it feels like it's covering up a problem rather than fixing it.
Hmm, you might be right. No matter which way we write PositionContinuations, we shouldn't be trying to push the sticky element 20px farther down each time... unless we forgot that we pushed the first continuation down already, which is consistent with my debugging results.
So now I see how these two situations quite possibly have the same root cause that we should fix.
Assignee | ||
Comment 26•11 years ago
|
||
The test is to verify the continuations.
Assignee | ||
Comment 27•11 years ago
|
||
Attachment #8388426 -
Attachment is obsolete: true
Assignee | ||
Comment 28•11 years ago
|
||
Attachment #8390412 -
Attachment is obsolete: true
Attachment #8390584 -
Flags: review?(dbaron)
Updated•11 years ago
|
Attachment #8390584 -
Attachment is patch: true
Comment 29•11 years ago
|
||
Comment on attachment 8390416 [details] [diff] [review]
sticky.patch
Carrying forward dbaron's r+ on the C++ patch.
Attachment #8390416 -
Flags: review+
(In reply to Abel Lin(alin, abel) from comment #28)
> Created attachment 8390584 [details] [diff] [review]
> sticky.reftest
>
> https://tbpl.mozilla.org/?tree=Try&rev=96ebbc4472a7
Looks like the tests you pushed to try were attachment 8390412 [details] rather than attachment 8390584 [details] [diff] [review]. (Which is good, given the diffs between them.)
Comment on attachment 8390584 [details] [diff] [review]
sticky.reftest
>Bug 980247 - Use offsets from GetNormalPosition() when updating continuations of position:sticky elements instead of assuming that they're all currently offset by the same amount
Please use a commit message related to what this patch is doing, so something like:
Bug 980247 - Add a reftest for ...
>diff --git a/layout/reftests/position-sticky/block-in-inline-continuations-ref.html b/layout/reftests/position-sticky/block-in-inline-continuations-ref.html
>new file mode 100644
>--- /dev/null
>+++ b/layout/reftests/position-sticky/block-in-inline-continuations-ref.html
Since you appear to have copied this from block-in-inline-3, could you record it as an hg copy?
>+ #scroll {== block-in-inline-continuations.html block-in-inline-continuations-ref.html
You need to fix this (it was introduced between the two patches).
>+ <meta name="assert" content="Inline elements split and they contain blocks should always have all parts moved the same offset from their normal position when scrolling(Bug 980247)">
Put a space before the "(".
Comment on attachment 8390584 [details] [diff] [review]
sticky.reftest
>+ font: 20px/1;
I think it's probably better to restore use of the Ahem font if you can. (Both in this 'font' declaration, and the link to ahem.css.) Doing that reduces the risk of creating a test that only tests what is intended on some platforms, since it avoids most of the variation in font metrics between platforms.
But you'll need to retest after that change that the test still fails before the patch and passes with the patch.
In the test file, please put the class="reftest-wait" on the root element in markup rather than setting it in script.
>+ document.documentElement.removeAttribute("class");
>+ doScroll(20);
It seems better to write these in the other order.
>+ }, false);
>+ </script>
>+</body>
>+</html>
>+
>+
Please restore the indentation of </body> and don't add two blank lines at the end.
r=dbaron with that and comment 31, assuming that you've tested that the test fails without the patch and passes with it. (In general, you should test tests both before and after the patch, and you should say that you've done so when uploading them so your reviewer knows you've done so.)
Attachment #8390584 -
Flags: review?(dbaron) → review+
Assignee | ||
Comment 33•11 years ago
|
||
I maybe messed my reftest before. So I didn't know how it passed on my local side.
It means that the scrolling in patch reftest can't detect the problem of incrmental offset.
The operation of resizing window can repro it. So I use a approach to do it.
Using iframe can be resized. The new sticky.reftest used the appraoch. So please review it again.
Test result:
// only sticky.reftest, verified the sticky position problem
https://tbpl.mozilla.org/?tree=Try&rev=85682dcd8b55
// sticky.reftest + sticky.patch, all pass
https://tbpl.mozilla.org/?tree=Try&rev=fc7d4d6f2fb7
Attachment #8390584 -
Attachment is obsolete: true
Attachment #8391144 -
Flags: review?(dbaron)
Comment on attachment 8391144 [details]
sticky.reftest
>diff --git a/layout/reftests/position-sticky/block-in-inline-continuations-inner.html b/layout/reftests/position-sticky/block-in-inline-continuations-inner.html
>+ <title>CSS Test: Sticky Positioning - continuations have no effect when zooming</title>
Maybe change this from "CSS Test" to "iframe for CSS Test"
>+ <link rel="match" href="block-in-inline-continuations-ref.html">
>+ <meta name="assert" content="Inline elements split and contain blocks should always have all parts moved the same offset from their normal position">
Don't have these on the inner iframe.
>+
>+
>+
Best to avoid these blank lines at the start and end of some of the files.
>diff --git a/layout/reftests/position-sticky/block-in-inline-continuations-ref-inner.html b/layout/reftests/position-sticky/block-in-inline-continuations-ref-inner.html
>+ <title>CSS Test: Sticky Positioning - continuations have no effect when zooming</title>
"iframe for CSS Test Reference: ..."
>diff --git a/layout/reftests/position-sticky/block-in-inline-continuations-ref.html b/layout/reftests/position-sticky/block-in-inline-continuations-ref.html
>+ <title>CSS Test: Sticky Positioning - continuations have no effect when zooming</title>
"CSS Test Reference: ..."
r=dbaron with those minor changes, and thanks for fixing the reftest
(In reply to Abel Lin(alin, abel) from comment #33)
> Test result:
>
> // only sticky.reftest, verified the sticky position problem
> https://tbpl.mozilla.org/?tree=Try&rev=85682dcd8b55
>
> // sticky.reftest + sticky.patch, all pass
> https://tbpl.mozilla.org/?tree=Try&rev=fc7d4d6f2fb7
You didn't need to run such huge try runs for these. It would have been fine to run just reftests on a platform or two, and to do only the test + patch run on try. When I talked about testing the test both with and without the patch in comment 32, I meant that testing locally would be fine; there was no need for a try run to satisfy that request.
Attachment #8391144 -
Flags: review?(dbaron) → review+
Assignee | ||
Comment 35•11 years ago
|
||
Attachment #8391144 -
Attachment is obsolete: true
Assignee | ||
Updated•11 years ago
|
Keywords: checkin-needed
Comment 36•11 years ago
|
||
It's not clear what all needs landing here? Please make sure obsolete/unnecessary patches are marked as such and any tests that need landing are included as well.
Keywords: checkin-needed
Assignee | ||
Updated•11 years ago
|
Attachment #8386663 -
Attachment is obsolete: true
Assignee | ||
Updated•11 years ago
|
Attachment #8389757 -
Attachment is obsolete: true
Assignee | ||
Updated•11 years ago
|
Attachment #8389759 -
Attachment is obsolete: true
Assignee | ||
Updated•11 years ago
|
Attachment #8389760 -
Attachment is obsolete: true
Updated•11 years ago
|
Keywords: checkin-needed
Attachment #8393281 -
Attachment is patch: true
Comment 37•11 years ago
|
||
https://hg.mozilla.org/integration/mozilla-inbound/rev/becd60bbd383
https://hg.mozilla.org/integration/mozilla-inbound/rev/52755193f692
Flags: in-testsuite+
Keywords: checkin-needed
Comment 38•11 years ago
|
||
Fix a brilliant rebase error on my part:
https://hg.mozilla.org/integration/mozilla-inbound/rev/e6598c5d66e7
Comment 39•11 years ago
|
||
https://hg.mozilla.org/mozilla-central/rev/becd60bbd383
https://hg.mozilla.org/mozilla-central/rev/52755193f692
https://hg.mozilla.org/mozilla-central/rev/e6598c5d66e7
Status: NEW → RESOLVED
Closed: 11 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla31
You need to log in
before you can comment on or make changes to this bug.
Description
•