1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/generic/StickyScrollContainer.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,391 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 sts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/** 1.11 + * compute sticky positioning, both during reflow and when the scrolling 1.12 + * container scrolls 1.13 + */ 1.14 + 1.15 +#include "StickyScrollContainer.h" 1.16 +#include "nsIFrame.h" 1.17 +#include "nsIScrollableFrame.h" 1.18 +#include "nsLayoutUtils.h" 1.19 +#include "RestyleTracker.h" 1.20 + 1.21 +using namespace mozilla::css; 1.22 + 1.23 +namespace mozilla { 1.24 + 1.25 +void DestroyStickyScrollContainer(void* aPropertyValue) 1.26 +{ 1.27 + delete static_cast<StickyScrollContainer*>(aPropertyValue); 1.28 +} 1.29 + 1.30 +NS_DECLARE_FRAME_PROPERTY(StickyScrollContainerProperty, 1.31 + DestroyStickyScrollContainer) 1.32 + 1.33 +StickyScrollContainer::StickyScrollContainer(nsIScrollableFrame* aScrollFrame) 1.34 + : mScrollFrame(aScrollFrame) 1.35 + , mScrollPosition() 1.36 +{ 1.37 + mScrollFrame->AddScrollPositionListener(this); 1.38 +} 1.39 + 1.40 +StickyScrollContainer::~StickyScrollContainer() 1.41 +{ 1.42 + mScrollFrame->RemoveScrollPositionListener(this); 1.43 +} 1.44 + 1.45 +// static 1.46 +StickyScrollContainer* 1.47 +StickyScrollContainer::GetStickyScrollContainerForFrame(nsIFrame* aFrame) 1.48 +{ 1.49 + nsIScrollableFrame* scrollFrame = 1.50 + nsLayoutUtils::GetNearestScrollableFrame(aFrame->GetParent(), 1.51 + nsLayoutUtils::SCROLLABLE_SAME_DOC | 1.52 + nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); 1.53 + if (!scrollFrame) { 1.54 + // We might not find any, for instance in the case of 1.55 + // <html style="position: fixed"> 1.56 + return nullptr; 1.57 + } 1.58 + FrameProperties props = static_cast<nsIFrame*>(do_QueryFrame(scrollFrame))-> 1.59 + Properties(); 1.60 + StickyScrollContainer* s = static_cast<StickyScrollContainer*> 1.61 + (props.Get(StickyScrollContainerProperty())); 1.62 + if (!s) { 1.63 + s = new StickyScrollContainer(scrollFrame); 1.64 + props.Set(StickyScrollContainerProperty(), s); 1.65 + } 1.66 + return s; 1.67 +} 1.68 + 1.69 +// static 1.70 +void 1.71 +StickyScrollContainer::NotifyReparentedFrameAcrossScrollFrameBoundary(nsIFrame* aFrame, 1.72 + nsIFrame* aOldParent) 1.73 +{ 1.74 + nsIScrollableFrame* oldScrollFrame = 1.75 + nsLayoutUtils::GetNearestScrollableFrame(aOldParent, 1.76 + nsLayoutUtils::SCROLLABLE_SAME_DOC | 1.77 + nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); 1.78 + if (!oldScrollFrame) { 1.79 + // XXX maybe aFrame has sticky descendants that can be sticky now, but 1.80 + // we aren't going to handle that. 1.81 + return; 1.82 + } 1.83 + FrameProperties props = static_cast<nsIFrame*>(do_QueryFrame(oldScrollFrame))-> 1.84 + Properties(); 1.85 + StickyScrollContainer* oldSSC = static_cast<StickyScrollContainer*> 1.86 + (props.Get(StickyScrollContainerProperty())); 1.87 + if (!oldSSC) { 1.88 + // aOldParent had no sticky descendants, so aFrame doesn't have any sticky 1.89 + // descendants, and we're done here. 1.90 + return; 1.91 + } 1.92 + 1.93 + auto i = oldSSC->mFrames.Length(); 1.94 + while (i-- > 0) { 1.95 + nsIFrame* f = oldSSC->mFrames[i]; 1.96 + StickyScrollContainer* newSSC = GetStickyScrollContainerForFrame(f); 1.97 + if (newSSC != oldSSC) { 1.98 + oldSSC->RemoveFrame(f); 1.99 + if (newSSC) { 1.100 + newSSC->AddFrame(f); 1.101 + } 1.102 + } 1.103 + } 1.104 +} 1.105 + 1.106 +// static 1.107 +StickyScrollContainer* 1.108 +StickyScrollContainer::GetStickyScrollContainerForScrollFrame(nsIFrame* aFrame) 1.109 +{ 1.110 + FrameProperties props = aFrame->Properties(); 1.111 + return static_cast<StickyScrollContainer*> 1.112 + (props.Get(StickyScrollContainerProperty())); 1.113 +} 1.114 + 1.115 +static nscoord 1.116 +ComputeStickySideOffset(Side aSide, const nsStyleSides& aOffset, 1.117 + nscoord aPercentBasis) 1.118 +{ 1.119 + if (eStyleUnit_Auto == aOffset.GetUnit(aSide)) { 1.120 + return NS_AUTOOFFSET; 1.121 + } else { 1.122 + return nsLayoutUtils::ComputeCBDependentValue(aPercentBasis, 1.123 + aOffset.Get(aSide)); 1.124 + } 1.125 +} 1.126 + 1.127 +// static 1.128 +void 1.129 +StickyScrollContainer::ComputeStickyOffsets(nsIFrame* aFrame) 1.130 +{ 1.131 + nsIScrollableFrame* scrollableFrame = 1.132 + nsLayoutUtils::GetNearestScrollableFrame(aFrame->GetParent(), 1.133 + nsLayoutUtils::SCROLLABLE_SAME_DOC | 1.134 + nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); 1.135 + 1.136 + if (!scrollableFrame) { 1.137 + // Bail. 1.138 + return; 1.139 + } 1.140 + 1.141 + nsSize scrollContainerSize = scrollableFrame->GetScrolledFrame()-> 1.142 + GetContentRectRelativeToSelf().Size(); 1.143 + 1.144 + nsMargin computedOffsets; 1.145 + const nsStylePosition* position = aFrame->StylePosition(); 1.146 + 1.147 + computedOffsets.left = ComputeStickySideOffset(eSideLeft, position->mOffset, 1.148 + scrollContainerSize.width); 1.149 + computedOffsets.right = ComputeStickySideOffset(eSideRight, position->mOffset, 1.150 + scrollContainerSize.width); 1.151 + computedOffsets.top = ComputeStickySideOffset(eSideTop, position->mOffset, 1.152 + scrollContainerSize.height); 1.153 + computedOffsets.bottom = ComputeStickySideOffset(eSideBottom, position->mOffset, 1.154 + scrollContainerSize.height); 1.155 + 1.156 + // Store the offset 1.157 + FrameProperties props = aFrame->Properties(); 1.158 + nsMargin* offsets = static_cast<nsMargin*> 1.159 + (props.Get(nsIFrame::ComputedOffsetProperty())); 1.160 + if (offsets) { 1.161 + *offsets = computedOffsets; 1.162 + } else { 1.163 + props.Set(nsIFrame::ComputedOffsetProperty(), 1.164 + new nsMargin(computedOffsets)); 1.165 + } 1.166 +} 1.167 + 1.168 +void 1.169 +StickyScrollContainer::ComputeStickyLimits(nsIFrame* aFrame, nsRect* aStick, 1.170 + nsRect* aContain) const 1.171 +{ 1.172 + NS_ASSERTION(nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame), 1.173 + "Can't sticky position individual continuations"); 1.174 + 1.175 + aStick->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX); 1.176 + aContain->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX); 1.177 + 1.178 + const nsMargin* computedOffsets = static_cast<nsMargin*>( 1.179 + aFrame->Properties().Get(nsIFrame::ComputedOffsetProperty())); 1.180 + if (!computedOffsets) { 1.181 + // We haven't reflowed the scroll frame yet, so offsets haven't been 1.182 + // computed. Bail. 1.183 + return; 1.184 + } 1.185 + 1.186 + nsIFrame* scrolledFrame = mScrollFrame->GetScrolledFrame(); 1.187 + nsIFrame* cbFrame = aFrame->GetContainingBlock(); 1.188 + NS_ASSERTION(cbFrame == scrolledFrame || 1.189 + nsLayoutUtils::IsProperAncestorFrame(scrolledFrame, cbFrame), 1.190 + "Scroll frame should be an ancestor of the containing block"); 1.191 + 1.192 + nsRect rect = 1.193 + nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame->GetParent()); 1.194 + 1.195 + // Containing block limits for the position of aFrame relative to its parent. 1.196 + // The margin box of the sticky element stays within the content box of the 1.197 + // contaning-block element. 1.198 + if (cbFrame != scrolledFrame) { 1.199 + *aContain = nsLayoutUtils:: 1.200 + GetAllInFlowRectsUnion(cbFrame, aFrame->GetParent(), 1.201 + nsLayoutUtils::RECTS_USE_CONTENT_BOX); 1.202 + nsRect marginRect = nsLayoutUtils:: 1.203 + GetAllInFlowRectsUnion(aFrame, aFrame->GetParent(), 1.204 + nsLayoutUtils::RECTS_USE_MARGIN_BOX); 1.205 + 1.206 + // Deflate aContain by the difference between the union of aFrame's 1.207 + // continuations' margin boxes and the union of their border boxes, so that 1.208 + // by keeping aFrame within aContain, we keep the union of the margin boxes 1.209 + // within the containing block's content box. 1.210 + aContain->Deflate(marginRect - rect); 1.211 + 1.212 + // Deflate aContain by the border-box size, to form a constraint on the 1.213 + // upper-left corner of aFrame and continuations. 1.214 + aContain->Deflate(nsMargin(0, rect.width, rect.height, 0)); 1.215 + } 1.216 + 1.217 + nsMargin sfPadding = scrolledFrame->GetUsedPadding(); 1.218 + nsPoint sfOffset = aFrame->GetParent()->GetOffsetTo(scrolledFrame); 1.219 + 1.220 + // Top 1.221 + if (computedOffsets->top != NS_AUTOOFFSET) { 1.222 + aStick->SetTopEdge(mScrollPosition.y + sfPadding.top + 1.223 + computedOffsets->top - sfOffset.y); 1.224 + } 1.225 + 1.226 + nsSize sfSize = scrolledFrame->GetContentRectRelativeToSelf().Size(); 1.227 + 1.228 + // Bottom 1.229 + if (computedOffsets->bottom != NS_AUTOOFFSET && 1.230 + (computedOffsets->top == NS_AUTOOFFSET || 1.231 + rect.height <= sfSize.height - computedOffsets->TopBottom())) { 1.232 + aStick->SetBottomEdge(mScrollPosition.y + sfPadding.top + sfSize.height - 1.233 + computedOffsets->bottom - rect.height - sfOffset.y); 1.234 + } 1.235 + 1.236 + uint8_t direction = cbFrame->StyleVisibility()->mDirection; 1.237 + 1.238 + // Left 1.239 + if (computedOffsets->left != NS_AUTOOFFSET && 1.240 + (computedOffsets->right == NS_AUTOOFFSET || 1.241 + direction == NS_STYLE_DIRECTION_LTR || 1.242 + rect.width <= sfSize.width - computedOffsets->LeftRight())) { 1.243 + aStick->SetLeftEdge(mScrollPosition.x + sfPadding.left + 1.244 + computedOffsets->left - sfOffset.x); 1.245 + } 1.246 + 1.247 + // Right 1.248 + if (computedOffsets->right != NS_AUTOOFFSET && 1.249 + (computedOffsets->left == NS_AUTOOFFSET || 1.250 + direction == NS_STYLE_DIRECTION_RTL || 1.251 + rect.width <= sfSize.width - computedOffsets->LeftRight())) { 1.252 + aStick->SetRightEdge(mScrollPosition.x + sfPadding.left + sfSize.width - 1.253 + computedOffsets->right - rect.width - sfOffset.x); 1.254 + } 1.255 + 1.256 + // These limits are for the bounding box of aFrame's continuations. Convert 1.257 + // to limits for aFrame itself. 1.258 + nsPoint frameOffset = aFrame->GetPosition() - rect.TopLeft(); 1.259 + aStick->MoveBy(frameOffset); 1.260 + aContain->MoveBy(frameOffset); 1.261 +} 1.262 + 1.263 +nsPoint 1.264 +StickyScrollContainer::ComputePosition(nsIFrame* aFrame) const 1.265 +{ 1.266 + nsRect stick; 1.267 + nsRect contain; 1.268 + ComputeStickyLimits(aFrame, &stick, &contain); 1.269 + 1.270 + nsPoint position = aFrame->GetNormalPosition(); 1.271 + 1.272 + // For each sticky direction (top, bottom, left, right), move the frame along 1.273 + // the appropriate axis, based on the scroll position, but limit this to keep 1.274 + // the element's margin box within the containing block. 1.275 + position.y = std::max(position.y, std::min(stick.y, contain.YMost())); 1.276 + position.y = std::min(position.y, std::max(stick.YMost(), contain.y)); 1.277 + position.x = std::max(position.x, std::min(stick.x, contain.XMost())); 1.278 + position.x = std::min(position.x, std::max(stick.XMost(), contain.x)); 1.279 + 1.280 + return position; 1.281 +} 1.282 + 1.283 +void 1.284 +StickyScrollContainer::GetScrollRanges(nsIFrame* aFrame, nsRect* aOuter, 1.285 + nsRect* aInner) const 1.286 +{ 1.287 + // We need to use the first in flow; ComputeStickyLimits requires 1.288 + // this, at the very least because its call to 1.289 + // nsLayoutUtils::GetAllInFlowRectsUnion requires it. 1.290 + nsIFrame *firstCont = 1.291 + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); 1.292 + 1.293 + nsRect stick; 1.294 + nsRect contain; 1.295 + ComputeStickyLimits(firstCont, &stick, &contain); 1.296 + 1.297 + aOuter->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX); 1.298 + aInner->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX); 1.299 + 1.300 + const nsPoint normalPosition = aFrame->GetNormalPosition(); 1.301 + 1.302 + // Bottom and top 1.303 + if (stick.YMost() != nscoord_MAX/2) { 1.304 + aOuter->SetTopEdge(contain.y - stick.YMost()); 1.305 + aInner->SetTopEdge(normalPosition.y - stick.YMost()); 1.306 + } 1.307 + 1.308 + if (stick.y != nscoord_MIN/2) { 1.309 + aInner->SetBottomEdge(normalPosition.y - stick.y); 1.310 + aOuter->SetBottomEdge(contain.YMost() - stick.y); 1.311 + } 1.312 + 1.313 + // Right and left 1.314 + if (stick.XMost() != nscoord_MAX/2) { 1.315 + aOuter->SetLeftEdge(contain.x - stick.XMost()); 1.316 + aInner->SetLeftEdge(normalPosition.x - stick.XMost()); 1.317 + } 1.318 + 1.319 + if (stick.x != nscoord_MIN/2) { 1.320 + aInner->SetRightEdge(normalPosition.x - stick.x); 1.321 + aOuter->SetRightEdge(contain.XMost() - stick.x); 1.322 + } 1.323 +} 1.324 + 1.325 +void 1.326 +StickyScrollContainer::PositionContinuations(nsIFrame* aFrame) 1.327 +{ 1.328 + NS_ASSERTION(nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame), 1.329 + "Should be starting from the first continuation"); 1.330 + nsPoint translation = ComputePosition(aFrame) - aFrame->GetNormalPosition(); 1.331 + 1.332 + // Move all continuation frames by the same amount. 1.333 + for (nsIFrame* cont = aFrame; cont; 1.334 + cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { 1.335 + cont->SetPosition(cont->GetNormalPosition() + translation); 1.336 + } 1.337 +} 1.338 + 1.339 +void 1.340 +StickyScrollContainer::UpdatePositions(nsPoint aScrollPosition, 1.341 + nsIFrame* aSubtreeRoot) 1.342 +{ 1.343 +#ifdef DEBUG 1.344 + { 1.345 + nsIFrame* scrollFrameAsFrame = do_QueryFrame(mScrollFrame); 1.346 + NS_ASSERTION(!aSubtreeRoot || aSubtreeRoot == scrollFrameAsFrame, 1.347 + "If reflowing, should be reflowing the scroll frame"); 1.348 + } 1.349 +#endif 1.350 + mScrollPosition = aScrollPosition; 1.351 + 1.352 + OverflowChangedTracker oct; 1.353 + oct.SetSubtreeRoot(aSubtreeRoot); 1.354 + for (nsTArray<nsIFrame*>::size_type i = 0; i < mFrames.Length(); i++) { 1.355 + nsIFrame* f = mFrames[i]; 1.356 + if (!nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(f)) { 1.357 + // This frame was added in nsFrame::Init before we knew it wasn't 1.358 + // the first ib-split-sibling. 1.359 + mFrames.RemoveElementAt(i); 1.360 + --i; 1.361 + continue; 1.362 + } 1.363 + 1.364 + if (aSubtreeRoot) { 1.365 + // Reflowing the scroll frame, so recompute offsets. 1.366 + ComputeStickyOffsets(f); 1.367 + } 1.368 + // mFrames will only contain first continuations, because we filter in 1.369 + // nsIFrame::Init. 1.370 + PositionContinuations(f); 1.371 + 1.372 + f = f->GetParent(); 1.373 + if (f != aSubtreeRoot) { 1.374 + for (nsIFrame* cont = f; cont; 1.375 + cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { 1.376 + oct.AddFrame(cont, OverflowChangedTracker::CHILDREN_CHANGED); 1.377 + } 1.378 + } 1.379 + } 1.380 + oct.Flush(); 1.381 +} 1.382 + 1.383 +void 1.384 +StickyScrollContainer::ScrollPositionWillChange(nscoord aX, nscoord aY) 1.385 +{ 1.386 +} 1.387 + 1.388 +void 1.389 +StickyScrollContainer::ScrollPositionDidChange(nscoord aX, nscoord aY) 1.390 +{ 1.391 + UpdatePositions(nsPoint(aX, aY), nullptr); 1.392 +} 1.393 + 1.394 +} // namespace mozilla