LCOV - code coverage report
Current view: top level - layout/generic - StickyScrollContainer.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 3 168 1.8 %
Date: 2017-07-14 16:53:18 Functions: 2 15 13.3 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13