layout/generic/StickyScrollContainer.cpp

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:e9fc33991ee0
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /**
8 * compute sticky positioning, both during reflow and when the scrolling
9 * container scrolls
10 */
11
12 #include "StickyScrollContainer.h"
13 #include "nsIFrame.h"
14 #include "nsIScrollableFrame.h"
15 #include "nsLayoutUtils.h"
16 #include "RestyleTracker.h"
17
18 using namespace mozilla::css;
19
20 namespace mozilla {
21
22 void DestroyStickyScrollContainer(void* aPropertyValue)
23 {
24 delete static_cast<StickyScrollContainer*>(aPropertyValue);
25 }
26
27 NS_DECLARE_FRAME_PROPERTY(StickyScrollContainerProperty,
28 DestroyStickyScrollContainer)
29
30 StickyScrollContainer::StickyScrollContainer(nsIScrollableFrame* aScrollFrame)
31 : mScrollFrame(aScrollFrame)
32 , mScrollPosition()
33 {
34 mScrollFrame->AddScrollPositionListener(this);
35 }
36
37 StickyScrollContainer::~StickyScrollContainer()
38 {
39 mScrollFrame->RemoveScrollPositionListener(this);
40 }
41
42 // static
43 StickyScrollContainer*
44 StickyScrollContainer::GetStickyScrollContainerForFrame(nsIFrame* aFrame)
45 {
46 nsIScrollableFrame* scrollFrame =
47 nsLayoutUtils::GetNearestScrollableFrame(aFrame->GetParent(),
48 nsLayoutUtils::SCROLLABLE_SAME_DOC |
49 nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
50 if (!scrollFrame) {
51 // We might not find any, for instance in the case of
52 // <html style="position: fixed">
53 return nullptr;
54 }
55 FrameProperties props = static_cast<nsIFrame*>(do_QueryFrame(scrollFrame))->
56 Properties();
57 StickyScrollContainer* s = static_cast<StickyScrollContainer*>
58 (props.Get(StickyScrollContainerProperty()));
59 if (!s) {
60 s = new StickyScrollContainer(scrollFrame);
61 props.Set(StickyScrollContainerProperty(), s);
62 }
63 return s;
64 }
65
66 // static
67 void
68 StickyScrollContainer::NotifyReparentedFrameAcrossScrollFrameBoundary(nsIFrame* aFrame,
69 nsIFrame* aOldParent)
70 {
71 nsIScrollableFrame* oldScrollFrame =
72 nsLayoutUtils::GetNearestScrollableFrame(aOldParent,
73 nsLayoutUtils::SCROLLABLE_SAME_DOC |
74 nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
75 if (!oldScrollFrame) {
76 // XXX maybe aFrame has sticky descendants that can be sticky now, but
77 // we aren't going to handle that.
78 return;
79 }
80 FrameProperties props = static_cast<nsIFrame*>(do_QueryFrame(oldScrollFrame))->
81 Properties();
82 StickyScrollContainer* oldSSC = static_cast<StickyScrollContainer*>
83 (props.Get(StickyScrollContainerProperty()));
84 if (!oldSSC) {
85 // aOldParent had no sticky descendants, so aFrame doesn't have any sticky
86 // descendants, and we're done here.
87 return;
88 }
89
90 auto i = oldSSC->mFrames.Length();
91 while (i-- > 0) {
92 nsIFrame* f = oldSSC->mFrames[i];
93 StickyScrollContainer* newSSC = GetStickyScrollContainerForFrame(f);
94 if (newSSC != oldSSC) {
95 oldSSC->RemoveFrame(f);
96 if (newSSC) {
97 newSSC->AddFrame(f);
98 }
99 }
100 }
101 }
102
103 // static
104 StickyScrollContainer*
105 StickyScrollContainer::GetStickyScrollContainerForScrollFrame(nsIFrame* aFrame)
106 {
107 FrameProperties props = aFrame->Properties();
108 return static_cast<StickyScrollContainer*>
109 (props.Get(StickyScrollContainerProperty()));
110 }
111
112 static nscoord
113 ComputeStickySideOffset(Side aSide, const nsStyleSides& aOffset,
114 nscoord aPercentBasis)
115 {
116 if (eStyleUnit_Auto == aOffset.GetUnit(aSide)) {
117 return NS_AUTOOFFSET;
118 } else {
119 return nsLayoutUtils::ComputeCBDependentValue(aPercentBasis,
120 aOffset.Get(aSide));
121 }
122 }
123
124 // static
125 void
126 StickyScrollContainer::ComputeStickyOffsets(nsIFrame* aFrame)
127 {
128 nsIScrollableFrame* scrollableFrame =
129 nsLayoutUtils::GetNearestScrollableFrame(aFrame->GetParent(),
130 nsLayoutUtils::SCROLLABLE_SAME_DOC |
131 nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
132
133 if (!scrollableFrame) {
134 // Bail.
135 return;
136 }
137
138 nsSize scrollContainerSize = scrollableFrame->GetScrolledFrame()->
139 GetContentRectRelativeToSelf().Size();
140
141 nsMargin computedOffsets;
142 const nsStylePosition* position = aFrame->StylePosition();
143
144 computedOffsets.left = ComputeStickySideOffset(eSideLeft, position->mOffset,
145 scrollContainerSize.width);
146 computedOffsets.right = ComputeStickySideOffset(eSideRight, position->mOffset,
147 scrollContainerSize.width);
148 computedOffsets.top = ComputeStickySideOffset(eSideTop, position->mOffset,
149 scrollContainerSize.height);
150 computedOffsets.bottom = ComputeStickySideOffset(eSideBottom, position->mOffset,
151 scrollContainerSize.height);
152
153 // Store the offset
154 FrameProperties props = aFrame->Properties();
155 nsMargin* offsets = static_cast<nsMargin*>
156 (props.Get(nsIFrame::ComputedOffsetProperty()));
157 if (offsets) {
158 *offsets = computedOffsets;
159 } else {
160 props.Set(nsIFrame::ComputedOffsetProperty(),
161 new nsMargin(computedOffsets));
162 }
163 }
164
165 void
166 StickyScrollContainer::ComputeStickyLimits(nsIFrame* aFrame, nsRect* aStick,
167 nsRect* aContain) const
168 {
169 NS_ASSERTION(nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame),
170 "Can't sticky position individual continuations");
171
172 aStick->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
173 aContain->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
174
175 const nsMargin* computedOffsets = static_cast<nsMargin*>(
176 aFrame->Properties().Get(nsIFrame::ComputedOffsetProperty()));
177 if (!computedOffsets) {
178 // We haven't reflowed the scroll frame yet, so offsets haven't been
179 // computed. Bail.
180 return;
181 }
182
183 nsIFrame* scrolledFrame = mScrollFrame->GetScrolledFrame();
184 nsIFrame* cbFrame = aFrame->GetContainingBlock();
185 NS_ASSERTION(cbFrame == scrolledFrame ||
186 nsLayoutUtils::IsProperAncestorFrame(scrolledFrame, cbFrame),
187 "Scroll frame should be an ancestor of the containing block");
188
189 nsRect rect =
190 nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame->GetParent());
191
192 // Containing block limits for the position of aFrame relative to its parent.
193 // The margin box of the sticky element stays within the content box of the
194 // contaning-block element.
195 if (cbFrame != scrolledFrame) {
196 *aContain = nsLayoutUtils::
197 GetAllInFlowRectsUnion(cbFrame, aFrame->GetParent(),
198 nsLayoutUtils::RECTS_USE_CONTENT_BOX);
199 nsRect marginRect = nsLayoutUtils::
200 GetAllInFlowRectsUnion(aFrame, aFrame->GetParent(),
201 nsLayoutUtils::RECTS_USE_MARGIN_BOX);
202
203 // Deflate aContain by the difference between the union of aFrame's
204 // continuations' margin boxes and the union of their border boxes, so that
205 // by keeping aFrame within aContain, we keep the union of the margin boxes
206 // within the containing block's content box.
207 aContain->Deflate(marginRect - rect);
208
209 // Deflate aContain by the border-box size, to form a constraint on the
210 // upper-left corner of aFrame and continuations.
211 aContain->Deflate(nsMargin(0, rect.width, rect.height, 0));
212 }
213
214 nsMargin sfPadding = scrolledFrame->GetUsedPadding();
215 nsPoint sfOffset = aFrame->GetParent()->GetOffsetTo(scrolledFrame);
216
217 // Top
218 if (computedOffsets->top != NS_AUTOOFFSET) {
219 aStick->SetTopEdge(mScrollPosition.y + sfPadding.top +
220 computedOffsets->top - sfOffset.y);
221 }
222
223 nsSize sfSize = scrolledFrame->GetContentRectRelativeToSelf().Size();
224
225 // Bottom
226 if (computedOffsets->bottom != NS_AUTOOFFSET &&
227 (computedOffsets->top == NS_AUTOOFFSET ||
228 rect.height <= sfSize.height - computedOffsets->TopBottom())) {
229 aStick->SetBottomEdge(mScrollPosition.y + sfPadding.top + sfSize.height -
230 computedOffsets->bottom - rect.height - sfOffset.y);
231 }
232
233 uint8_t direction = cbFrame->StyleVisibility()->mDirection;
234
235 // Left
236 if (computedOffsets->left != NS_AUTOOFFSET &&
237 (computedOffsets->right == NS_AUTOOFFSET ||
238 direction == NS_STYLE_DIRECTION_LTR ||
239 rect.width <= sfSize.width - computedOffsets->LeftRight())) {
240 aStick->SetLeftEdge(mScrollPosition.x + sfPadding.left +
241 computedOffsets->left - sfOffset.x);
242 }
243
244 // Right
245 if (computedOffsets->right != NS_AUTOOFFSET &&
246 (computedOffsets->left == NS_AUTOOFFSET ||
247 direction == NS_STYLE_DIRECTION_RTL ||
248 rect.width <= sfSize.width - computedOffsets->LeftRight())) {
249 aStick->SetRightEdge(mScrollPosition.x + sfPadding.left + sfSize.width -
250 computedOffsets->right - rect.width - sfOffset.x);
251 }
252
253 // These limits are for the bounding box of aFrame's continuations. Convert
254 // to limits for aFrame itself.
255 nsPoint frameOffset = aFrame->GetPosition() - rect.TopLeft();
256 aStick->MoveBy(frameOffset);
257 aContain->MoveBy(frameOffset);
258 }
259
260 nsPoint
261 StickyScrollContainer::ComputePosition(nsIFrame* aFrame) const
262 {
263 nsRect stick;
264 nsRect contain;
265 ComputeStickyLimits(aFrame, &stick, &contain);
266
267 nsPoint position = aFrame->GetNormalPosition();
268
269 // For each sticky direction (top, bottom, left, right), move the frame along
270 // the appropriate axis, based on the scroll position, but limit this to keep
271 // the element's margin box within the containing block.
272 position.y = std::max(position.y, std::min(stick.y, contain.YMost()));
273 position.y = std::min(position.y, std::max(stick.YMost(), contain.y));
274 position.x = std::max(position.x, std::min(stick.x, contain.XMost()));
275 position.x = std::min(position.x, std::max(stick.XMost(), contain.x));
276
277 return position;
278 }
279
280 void
281 StickyScrollContainer::GetScrollRanges(nsIFrame* aFrame, nsRect* aOuter,
282 nsRect* aInner) const
283 {
284 // We need to use the first in flow; ComputeStickyLimits requires
285 // this, at the very least because its call to
286 // nsLayoutUtils::GetAllInFlowRectsUnion requires it.
287 nsIFrame *firstCont =
288 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
289
290 nsRect stick;
291 nsRect contain;
292 ComputeStickyLimits(firstCont, &stick, &contain);
293
294 aOuter->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
295 aInner->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
296
297 const nsPoint normalPosition = aFrame->GetNormalPosition();
298
299 // Bottom and top
300 if (stick.YMost() != nscoord_MAX/2) {
301 aOuter->SetTopEdge(contain.y - stick.YMost());
302 aInner->SetTopEdge(normalPosition.y - stick.YMost());
303 }
304
305 if (stick.y != nscoord_MIN/2) {
306 aInner->SetBottomEdge(normalPosition.y - stick.y);
307 aOuter->SetBottomEdge(contain.YMost() - stick.y);
308 }
309
310 // Right and left
311 if (stick.XMost() != nscoord_MAX/2) {
312 aOuter->SetLeftEdge(contain.x - stick.XMost());
313 aInner->SetLeftEdge(normalPosition.x - stick.XMost());
314 }
315
316 if (stick.x != nscoord_MIN/2) {
317 aInner->SetRightEdge(normalPosition.x - stick.x);
318 aOuter->SetRightEdge(contain.XMost() - stick.x);
319 }
320 }
321
322 void
323 StickyScrollContainer::PositionContinuations(nsIFrame* aFrame)
324 {
325 NS_ASSERTION(nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame),
326 "Should be starting from the first continuation");
327 nsPoint translation = ComputePosition(aFrame) - aFrame->GetNormalPosition();
328
329 // Move all continuation frames by the same amount.
330 for (nsIFrame* cont = aFrame; cont;
331 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
332 cont->SetPosition(cont->GetNormalPosition() + translation);
333 }
334 }
335
336 void
337 StickyScrollContainer::UpdatePositions(nsPoint aScrollPosition,
338 nsIFrame* aSubtreeRoot)
339 {
340 #ifdef DEBUG
341 {
342 nsIFrame* scrollFrameAsFrame = do_QueryFrame(mScrollFrame);
343 NS_ASSERTION(!aSubtreeRoot || aSubtreeRoot == scrollFrameAsFrame,
344 "If reflowing, should be reflowing the scroll frame");
345 }
346 #endif
347 mScrollPosition = aScrollPosition;
348
349 OverflowChangedTracker oct;
350 oct.SetSubtreeRoot(aSubtreeRoot);
351 for (nsTArray<nsIFrame*>::size_type i = 0; i < mFrames.Length(); i++) {
352 nsIFrame* f = mFrames[i];
353 if (!nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(f)) {
354 // This frame was added in nsFrame::Init before we knew it wasn't
355 // the first ib-split-sibling.
356 mFrames.RemoveElementAt(i);
357 --i;
358 continue;
359 }
360
361 if (aSubtreeRoot) {
362 // Reflowing the scroll frame, so recompute offsets.
363 ComputeStickyOffsets(f);
364 }
365 // mFrames will only contain first continuations, because we filter in
366 // nsIFrame::Init.
367 PositionContinuations(f);
368
369 f = f->GetParent();
370 if (f != aSubtreeRoot) {
371 for (nsIFrame* cont = f; cont;
372 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
373 oct.AddFrame(cont, OverflowChangedTracker::CHILDREN_CHANGED);
374 }
375 }
376 }
377 oct.Flush();
378 }
379
380 void
381 StickyScrollContainer::ScrollPositionWillChange(nscoord aX, nscoord aY)
382 {
383 }
384
385 void
386 StickyScrollContainer::ScrollPositionDidChange(nscoord aX, nscoord aY)
387 {
388 UpdatePositions(nsPoint(aX, aY), nullptr);
389 }
390
391 } // namespace mozilla

mercurial