layout/generic/StickyScrollContainer.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial