Bug 886646 - Part 6: Implement sticky positioning, calculated on reflow and scroll. r=dbaron, r=dholbert
authorCorey Ford <cford@mozilla.com>
Fri, 06 Sep 2013 09:35:16 -0400
changeset 145943 63a55ac51f7fff7d34027ccd0533e0dc94bb3e0d
parent 145942 05c2da42c84ef4e7cee5f836710670a774760996
child 145944 bb399c38e3b684d56ad595f9ca1bbb59c0e09555
push idunknown
push userunknown
push dateunknown
reviewersdbaron, dholbert
bugs886646
milestone26.0a1
Bug 886646 - Part 6: Implement sticky positioning, calculated on reflow and scroll. r=dbaron, r=dholbert
layout/base/RestyleManager.cpp
layout/base/RestyleTracker.h
layout/generic/StickyScrollContainer.cpp
layout/generic/StickyScrollContainer.h
layout/generic/moz.build
layout/generic/nsFrame.cpp
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsGfxScrollFrame.h
layout/generic/nsHTMLReflowState.cpp
layout/generic/nsIFrame.h
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -24,16 +24,17 @@
 #include "nsSVGIntegrationUtils.h"
 #include "nsCSSAnonBoxes.h"
 #include "nsContainerFrame.h"
 #include "nsPlaceholderFrame.h"
 #include "nsBlockFrame.h"
 #include "nsViewportFrame.h"
 #include "nsSVGTextFrame2.h"
 #include "nsSVGTextPathFrame.h"
+#include "StickyScrollContainer.h"
 #include "nsIRootBox.h"
 #include "nsIDOMMutationEvent.h"
 #include "nsContentUtils.h"
 
 #ifdef ACCESSIBILITY
 #include "nsAccessibilityService.h"
 #endif
 
@@ -321,17 +322,17 @@ RestyleManager::RecomputePosition(nsIFra
   // Changes to the offsets of a non-positioned element can safely be ignored.
   if (display->mPosition == NS_STYLE_POSITION_STATIC) {
     return true;
   }
 
   aFrame->SchedulePaint();
 
   // For relative positioning, we can simply update the frame rect
-  if (display->mPosition == NS_STYLE_POSITION_RELATIVE) {
+  if (display->IsRelativelyPositionedStyle()) {
     switch (display->mDisplay) {
       case NS_STYLE_DISPLAY_TABLE_CAPTION:
       case NS_STYLE_DISPLAY_TABLE_CELL:
       case NS_STYLE_DISPLAY_TABLE_ROW:
       case NS_STYLE_DISPLAY_TABLE_ROW_GROUP:
       case NS_STYLE_DISPLAY_TABLE_HEADER_GROUP:
       case NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP:
       case NS_STYLE_DISPLAY_TABLE_COLUMN:
@@ -340,27 +341,37 @@ RestyleManager::RecomputePosition(nsIFra
         // table elements.  If we apply offsets to things we haven't
         // previously offset, we'll get confused.  So bail.
         return true;
       default:
         break;
     }
 
     nsIFrame* cb = aFrame->GetContainingBlock();
-    const nsSize size = cb->GetContentRectRelativeToSelf().Size();
-    nsPoint position = aFrame->GetNormalPosition();
     nsMargin newOffsets;
 
     // Move the frame
-    nsHTMLReflowState::ComputeRelativeOffsets(
-        cb->StyleVisibility()->mDirection,
-        aFrame, size.width, size.height, newOffsets);
-    NS_ASSERTION(newOffsets.left == -newOffsets.right &&
-                 newOffsets.top == -newOffsets.bottom,
-                 "ComputeRelativeOffsets should return valid results");
+    if (display->mPosition == NS_STYLE_POSITION_STICKY) {
+      StickyScrollContainer::ComputeStickyOffsets(aFrame);
+    } else {
+      MOZ_ASSERT(NS_STYLE_POSITION_RELATIVE == display->mPosition,
+                 "Unexpected type of positioning");
+      const nsSize size = cb->GetContentRectRelativeToSelf().Size();
+
+      nsHTMLReflowState::ComputeRelativeOffsets(
+          cb->StyleVisibility()->mDirection,
+          aFrame, size.width, size.height, newOffsets);
+      NS_ASSERTION(newOffsets.left == -newOffsets.right &&
+                   newOffsets.top == -newOffsets.bottom,
+                   "ComputeRelativeOffsets should return valid results");
+    }
+
+    nsPoint position = aFrame->GetNormalPosition();
+
+    // This handles both relative and sticky positioning.
     nsHTMLReflowState::ApplyRelativePositioning(aFrame, newOffsets, &position);
     aFrame->SetPosition(position);
 
     return true;
   }
 
   // For absolute positioning, the width can potentially change if width is
   // auto and either of left or right are not.  The height can also potentially
--- a/layout/base/RestyleTracker.h
+++ b/layout/base/RestyleTracker.h
@@ -24,16 +24,20 @@ class RestyleManager;
  * Helper class that collects a list of frames that need
  * UpdateOverflow() called on them, and coalesces them
  * to avoid walking up the same ancestor tree multiple times.
  */
 class OverflowChangedTracker
 {
 public:
 
+  OverflowChangedTracker() :
+    mSubtreeRoot(nullptr)
+  {}
+
   ~OverflowChangedTracker()
   {
     NS_ASSERTION(mEntryList.empty(), "Need to flush before destroying!");
   }
 
   /**
    * Add a frame that has had a style change, and needs its
    * overflow updated.
@@ -63,16 +67,25 @@ public:
 
     uint32_t depth = aFrame->GetDepthInFrameTree();
     if (mEntryList.contains(Entry(aFrame, depth, false))) {
       delete mEntryList.remove(Entry(aFrame, depth, false));
     }
   }
 
   /**
+   * Set the subtree root to limit overflow updates. This must be set if and
+   * only if currently reflowing aSubtreeRoot, to ensure overflow changes will
+   * still propagate correctly.
+   */
+  void SetSubtreeRoot(const nsIFrame* aSubtreeRoot) {
+    mSubtreeRoot = aSubtreeRoot;
+  }
+
+  /**
    * Update the overflow of all added frames, and clear the entry list.
    *
    * Start from those deepest in the frame tree and works upwards. This stops 
    * us from processing the same frame twice.
    */
   void Flush() {
     while (!mEntryList.empty()) {
       Entry *entry = mEntryList.removeMin();
@@ -95,17 +108,17 @@ public:
 
       // If the overflow changed, then we want to also update the parent's
       // overflow. We always update the parent for initial frames.
       if (!updateParent) {
         updateParent = frame->UpdateOverflow() || entry->mInitial;
       }
       if (updateParent) {
         nsIFrame *parent = frame->GetParent();
-        if (parent) {
+        if (parent && parent != mSubtreeRoot) {
           if (!mEntryList.contains(Entry(parent, entry->mDepth - 1, false))) {
             mEntryList.insert(new Entry(parent, entry->mDepth - 1, false));
           }
         }
       }
       delete entry;
     }
   }
@@ -160,16 +173,19 @@ private:
      * True if the frame had the actual style change, and we
      * want to check for pre-transform overflow areas.
      */
     bool mInitial;
   };
 
   /* A list of frames to process, sorted by their depth in the frame tree */
   SplayTree<Entry, Entry> mEntryList;
+
+  /* Don't update overflow of this frame or its ancestors. */
+  const nsIFrame* mSubtreeRoot;
 };
 
 class RestyleTracker {
 public:
   typedef mozilla::dom::Element Element;
 
   RestyleTracker(uint32_t aRestyleBits) :
     mRestyleBits(aRestyleBits),
new file mode 100644
--- /dev/null
+++ b/layout/generic/StickyScrollContainer.cpp
@@ -0,0 +1,252 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * compute sticky positioning, both during reflow and when the scrolling
+ * container scrolls
+ */
+
+#include "StickyScrollContainer.h"
+#include "nsIFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include "RestyleTracker.h"
+
+using namespace mozilla::css;
+
+namespace mozilla {
+
+void DestroyStickyScrollContainer(void* aPropertyValue)
+{
+  delete static_cast<StickyScrollContainer*>(aPropertyValue);
+}
+
+NS_DECLARE_FRAME_PROPERTY(StickyScrollContainerProperty,
+                          DestroyStickyScrollContainer)
+
+StickyScrollContainer::StickyScrollContainer(nsIScrollableFrame* aScrollFrame)
+  : mScrollFrame(aScrollFrame)
+  , mScrollPosition()
+{
+  mScrollFrame->AddScrollPositionListener(this);
+}
+
+StickyScrollContainer::~StickyScrollContainer()
+{
+  mScrollFrame->RemoveScrollPositionListener(this);
+}
+
+// static
+StickyScrollContainer*
+StickyScrollContainer::StickyScrollContainerForFrame(nsIFrame* aFrame)
+{
+  nsIScrollableFrame* scrollFrame =
+    nsLayoutUtils::GetNearestScrollableFrame(aFrame->GetParent(),
+      nsLayoutUtils::SCROLLABLE_SAME_DOC |
+      nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+  NS_ASSERTION(scrollFrame, "Need a scrolling container");
+  FrameProperties props = static_cast<nsIFrame*>(do_QueryFrame(scrollFrame))->
+    Properties();
+  StickyScrollContainer* s = static_cast<StickyScrollContainer*>
+    (props.Get(StickyScrollContainerProperty()));
+  if (!s) {
+    s = new StickyScrollContainer(scrollFrame);
+    props.Set(StickyScrollContainerProperty(), s);
+  }
+  return s;
+}
+
+// static
+StickyScrollContainer*
+StickyScrollContainer::GetStickyScrollContainerForScrollFrame(nsIFrame* aFrame)
+{
+  FrameProperties props = aFrame->Properties();
+  return static_cast<StickyScrollContainer*>
+    (props.Get(StickyScrollContainerProperty()));
+}
+
+static nscoord
+ComputeStickySideOffset(Side aSide, const nsStyleSides& aOffset,
+                        nscoord aPercentBasis)
+{
+  if (eStyleUnit_Auto == aOffset.GetUnit(aSide)) {
+    return NS_AUTOOFFSET;
+  } else {
+    return nsLayoutUtils::ComputeCBDependentValue(aPercentBasis,
+                                                  aOffset.Get(aSide));
+  }
+}
+
+// static
+void
+StickyScrollContainer::ComputeStickyOffsets(nsIFrame* aFrame)
+{
+  nsIScrollableFrame* scrollableFrame =
+    nsLayoutUtils::GetNearestScrollableFrame(aFrame->GetParent(),
+      nsLayoutUtils::SCROLLABLE_SAME_DOC |
+      nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+
+  if (!scrollableFrame) {
+    // Not sure how this would happen, but bail if it does.
+    NS_ERROR("Couldn't find a scrollable frame");
+    return;
+  }
+
+  nsSize scrollContainerSize = scrollableFrame->GetScrolledFrame()->
+    GetContentRectRelativeToSelf().Size();
+
+  nsMargin computedOffsets;
+  const nsStylePosition* position = aFrame->StylePosition();
+
+  computedOffsets.left   = ComputeStickySideOffset(eSideLeft, position->mOffset,
+                                                   scrollContainerSize.width);
+  computedOffsets.right  = ComputeStickySideOffset(eSideRight, position->mOffset,
+                                                   scrollContainerSize.width);
+  computedOffsets.top    = ComputeStickySideOffset(eSideTop, position->mOffset,
+                                                   scrollContainerSize.height);
+  computedOffsets.bottom = ComputeStickySideOffset(eSideBottom, position->mOffset,
+                                                   scrollContainerSize.height);
+
+  // Store the offset
+  FrameProperties props = aFrame->Properties();
+  nsMargin* offsets = static_cast<nsMargin*>
+    (props.Get(nsIFrame::ComputedStickyOffsetProperty()));
+  if (offsets) {
+    *offsets = computedOffsets;
+  } else {
+    props.Set(nsIFrame::ComputedStickyOffsetProperty(),
+              new nsMargin(computedOffsets));
+  }
+}
+
+void
+StickyScrollContainer::ComputeStickyLimits(nsIFrame* aFrame, nsRect* aStick,
+                                           nsRect* aContain) const
+{
+  aStick->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
+  aContain->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
+
+  const nsMargin* computedOffsets = static_cast<nsMargin*>(
+    aFrame->Properties().Get(nsIFrame::ComputedStickyOffsetProperty()));
+  if (!computedOffsets) {
+    // We haven't reflowed the scroll frame yet, so offsets haven't been
+    // computed. Bail.
+    return;
+  }
+
+  nsIFrame* scrolledFrame = mScrollFrame->GetScrolledFrame();
+  nsIFrame* cbFrame = aFrame->GetContainingBlock();
+  NS_ASSERTION(cbFrame == scrolledFrame ||
+    nsLayoutUtils::IsProperAncestorFrame(scrolledFrame, cbFrame),
+    "Scroll frame should be an ancestor of the containing block");
+
+  nsRect rect = aFrame->GetRect();
+  nsMargin margin = aFrame->GetUsedMargin();
+
+  // Containing block limits
+  if (cbFrame != scrolledFrame) {
+    nsMargin cbBorderPadding = cbFrame->GetUsedBorderAndPadding();
+    aContain->SetRect(nsPoint(cbBorderPadding.left, cbBorderPadding.top) -
+                      aFrame->GetParent()->GetOffsetTo(cbFrame),
+                      cbFrame->GetContentRectRelativeToSelf().Size() -
+                      rect.Size());
+    aContain->Deflate(margin);
+  }
+
+  nsMargin sfPadding = scrolledFrame->GetUsedPadding();
+  nsPoint sfOffset = aFrame->GetParent()->GetOffsetTo(scrolledFrame);
+
+  // Top
+  if (computedOffsets->top != NS_AUTOOFFSET) {
+    aStick->SetTopEdge(mScrollPosition.y + sfPadding.top +
+                       computedOffsets->top - sfOffset.y);
+  }
+
+  nsSize sfSize = scrolledFrame->GetContentRectRelativeToSelf().Size();
+
+  // Bottom
+  if (computedOffsets->bottom != NS_AUTOOFFSET &&
+      (computedOffsets->top == NS_AUTOOFFSET ||
+       rect.height <= sfSize.height - computedOffsets->TopBottom())) {
+    aStick->SetBottomEdge(mScrollPosition.y + sfPadding.top + sfSize.height -
+                          computedOffsets->bottom - rect.height - sfOffset.y);
+  }
+
+  uint8_t direction = cbFrame->StyleVisibility()->mDirection;
+
+  // Left
+  if (computedOffsets->left != NS_AUTOOFFSET &&
+      (computedOffsets->right == NS_AUTOOFFSET ||
+       direction == NS_STYLE_DIRECTION_LTR ||
+       rect.width <= sfSize.width - computedOffsets->LeftRight())) {
+    aStick->SetLeftEdge(mScrollPosition.x + sfPadding.left +
+                        computedOffsets->left - sfOffset.x);
+  }
+
+  // Right
+  if (computedOffsets->right != NS_AUTOOFFSET &&
+      (computedOffsets->left == NS_AUTOOFFSET ||
+       direction == NS_STYLE_DIRECTION_RTL ||
+       rect.width <= sfSize.width - computedOffsets->LeftRight())) {
+    aStick->SetRightEdge(mScrollPosition.x + sfPadding.left + sfSize.width -
+                         computedOffsets->right - rect.width - sfOffset.x);
+  }
+}
+
+nsPoint
+StickyScrollContainer::ComputePosition(nsIFrame* aFrame) const
+{
+  nsRect stick;
+  nsRect contain;
+  ComputeStickyLimits(aFrame, &stick, &contain);
+
+  nsPoint position = aFrame->GetNormalPosition();
+
+  // For each sticky direction (top, bottom, left, right), move the frame along
+  // the appropriate axis, based on the scroll position, but limit this to keep
+  // the element's margin box within the containing block.
+  position.y = std::max(position.y, std::min(stick.y, contain.YMost()));
+  position.y = std::min(position.y, std::max(stick.YMost(), contain.y));
+  position.x = std::max(position.x, std::min(stick.x, contain.XMost()));
+  position.x = std::min(position.x, std::max(stick.XMost(), contain.x));
+
+  return position;
+}
+
+void
+StickyScrollContainer::UpdatePositions(nsPoint aScrollPosition,
+                                       nsIFrame* aSubtreeRoot)
+{
+  NS_ASSERTION(!aSubtreeRoot || aSubtreeRoot == do_QueryFrame(mScrollFrame),
+    "If reflowing, should be reflowing the scroll frame");
+  mScrollPosition = aScrollPosition;
+
+  OverflowChangedTracker oct;
+  oct.SetSubtreeRoot(aSubtreeRoot);
+  for (nsTArray<nsIFrame*>::size_type i = 0; i < mFrames.Length(); i++) {
+    nsIFrame* f = mFrames[i];
+    if (aSubtreeRoot) {
+      // Reflowing the scroll frame, so recompute offsets.
+      ComputeStickyOffsets(f);
+    }
+    f->SetPosition(ComputePosition(f));
+    oct.AddFrame(f);
+  }
+  oct.Flush();
+}
+
+void
+StickyScrollContainer::ScrollPositionWillChange(nscoord aX, nscoord aY)
+{
+}
+
+void
+StickyScrollContainer::ScrollPositionDidChange(nscoord aX, nscoord aY)
+{
+  UpdatePositions(nsPoint(aX, aY), nullptr);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/generic/StickyScrollContainer.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * compute sticky positioning, both during reflow and when the scrolling
+ * container scrolls
+ */
+
+#ifndef StickyScrollContainer_h
+#define StickyScrollContainer_h
+
+#include "nsPoint.h"
+#include "nsTArray.h"
+#include "nsIScrollPositionListener.h"
+
+class nsRect;
+class nsIFrame;
+class nsIScrollableFrame;
+
+namespace mozilla {
+
+class StickyScrollContainer MOZ_FINAL : public nsIScrollPositionListener
+{
+public:
+  /**
+   * Find the StickyScrollContainer associated with the scroll container of
+   * the given frame, creating it if necessary.
+   */
+  static StickyScrollContainer* StickyScrollContainerForFrame(nsIFrame* aFrame);
+
+  /**
+   * Find the StickyScrollContainer associated with the given scroll frame,
+   * if it exists.
+   */
+  static StickyScrollContainer* GetStickyScrollContainerForScrollFrame(nsIFrame* aScrollFrame);
+
+  void AddFrame(nsIFrame* aFrame) {
+    mFrames.AppendElement(aFrame);
+  }
+  void RemoveFrame(nsIFrame* aFrame) {
+    mFrames.RemoveElement(aFrame);
+  }
+
+  // Compute the offsets for a sticky position element
+  static void ComputeStickyOffsets(nsIFrame* aFrame);
+
+  /**
+   * Compute the position of a sticky positioned frame, based on information
+   * stored in its properties along with our scroll frame and scroll position.
+   */
+  nsPoint ComputePosition(nsIFrame* aFrame) const;
+
+  /**
+   * Compute and set the position of all sticky frames, given the current
+   * scroll position of the scroll frame. If not in reflow, aSubtreeRoot should
+   * be null; otherwise, overflow-area updates will be limited to not affect
+   * aSubtreeRoot or its ancestors.
+   */
+  void UpdatePositions(nsPoint aScrollPosition, nsIFrame* aSubtreeRoot);
+
+  // nsIScrollPositionListener
+  virtual void ScrollPositionWillChange(nscoord aX, nscoord aY) MOZ_OVERRIDE;
+  virtual void ScrollPositionDidChange(nscoord aX, nscoord aY) MOZ_OVERRIDE;
+
+private:
+  StickyScrollContainer(nsIScrollableFrame* aScrollFrame);
+  ~StickyScrollContainer();
+
+  /**
+   * Compute two rectangles that determine sticky positioning: |aStick|, based
+   * on the scroll container, and |aContain|, based on the containing block.
+   * Sticky positioning keeps the frame position (its upper-left corner) always
+   * within |aContain| and secondarily within |aStick|.
+   */
+  void ComputeStickyLimits(nsIFrame* aFrame, nsRect* aStick,
+                           nsRect* aContain) const;
+
+  friend void DestroyStickyScrollContainer(void* aPropertyValue);
+
+  nsIScrollableFrame* const mScrollFrame;
+  nsTArray<nsIFrame*> mFrames;
+  nsPoint mScrollPosition;
+};
+
+} // namespace mozilla
+
+#endif /* StickyScrollContainer_h */
--- a/layout/generic/moz.build
+++ b/layout/generic/moz.build
@@ -40,16 +40,17 @@ EXPORTS.mozilla += [
 
 EXPORTS.mozilla.layout += [
     'FrameChildList.h',
 ]
 
 CPP_SOURCES += [
     'FrameChildList.cpp',
     'ScrollbarActivity.cpp',
+    'StickyScrollContainer.cpp',
     'TextOverflow.cpp',
     'nsAbsoluteContainingBlock.cpp',
     'nsBRFrame.cpp',
     'nsBlockFrame.cpp',
     'nsBlockReflowContext.cpp',
     'nsBlockReflowState.cpp',
     'nsBulletFrame.cpp',
     'nsCanvasFrame.cpp',
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -68,16 +68,17 @@
 #include "nsChangeHint.h"
 #include "nsDeckFrame.h"
 #include "nsSubDocumentFrame.h"
 #include "nsSVGTextFrame2.h"
 
 #include "gfxContext.h"
 #include "nsRenderingContext.h"
 #include "nsAbsoluteContainingBlock.h"
+#include "StickyScrollContainer.h"
 #include "nsFontInflationData.h"
 #include "gfxASurface.h"
 #include "nsRegion.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/css/ImageLoader.h"
 #include "mozilla/gfx/Tools.h"
@@ -505,16 +506,19 @@ nsFrame::Init(nsIContent*      aContent,
                        NS_FRAME_IS_NONDISPLAY);
   }
   const nsStyleDisplay *disp = StyleDisplay();
   if (disp->HasTransform(this)) {
     // The frame gets reconstructed if we toggle the -moz-transform
     // property, so we can set this bit here and then ignore it.
     mState |= NS_FRAME_MAY_BE_TRANSFORMED;
   }
+  if (disp->mPosition == NS_STYLE_POSITION_STICKY) {
+    StickyScrollContainer::StickyScrollContainerForFrame(this)->AddFrame(this);
+  }
 
   if (nsLayoutUtils::FontSizeInflationEnabled(PresContext()) || !GetParent()
 #ifdef DEBUG
       // We have assertions that check inflation invariants even when
       // font size inflation is not enabled.
       || true
 #endif
       ) {
@@ -583,16 +587,21 @@ nsFrame::DestroyFrom(nsIFrame* aDestruct
     "destroy called on frame while scripts not blocked");
   NS_ASSERTION(!GetNextSibling() && !GetPrevSibling(),
                "Frames should be removed before destruction.");
   NS_ASSERTION(aDestructRoot, "Must specify destruct root");
   MOZ_ASSERT(!HasAbsolutelyPositionedChildren());
 
   nsSVGEffects::InvalidateDirectRenderingObservers(this);
 
+  if (StyleDisplay()->mPosition == NS_STYLE_POSITION_STICKY) {
+    StickyScrollContainer::StickyScrollContainerForFrame(this)->
+      RemoveFrame(this);
+  }
+
   // Get the view pointer now before the frame properties disappear
   // when we call NotifyDestroyingFrame()
   nsView* view = GetView();
   nsPresContext* presContext = PresContext();
 
   nsIPresShell *shell = presContext->GetPresShell();
   if (mState & NS_FRAME_OUT_OF_FLOW) {
     nsPlaceholderFrame* placeholder =
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -43,16 +43,17 @@
 #include "nsSubDocumentFrame.h"
 #include "nsSVGOuterSVGFrame.h"
 #include "mozilla/Attributes.h"
 #include "ScrollbarActivity.h"
 #include "nsRefreshDriver.h"
 #include "nsThemeConstants.h"
 #include "nsSVGIntegrationUtils.h"
 #include "nsIScrollPositionListener.h"
+#include "StickyScrollContainer.h"
 #include <algorithm>
 #include <cstdlib> // for std::abs(int/long)
 #include <cmath> // for std::abs(float/double)
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layout;
 
@@ -822,16 +823,17 @@ nsHTMLScrollFrame::Reflow(nsPresContext*
     state.mComputedBorder.TopBottom();
 
   aDesiredSize.SetOverflowAreasToDesiredBounds();
   if (mInner.IsIgnoringViewportClipping()) {
     aDesiredSize.mOverflowAreas.UnionWith(
       state.mContentsOverflowAreas + mInner.mScrolledFrame->GetPosition());
   }
 
+  mInner.UpdateSticky();
   FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowState, aStatus);
 
   if (!InInitialReflow() && !mInner.mHadNonInitialReflow) {
     mInner.mHadNonInitialReflow = true;
   }
 
   if (mInner.mIsRoot && !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
     mInner.PostScrolledAreaEvent();
@@ -3539,16 +3541,18 @@ nsXULScrollFrame::Layout(nsBoxLayoutStat
     // Make sure we'll try scrolling to restored position
     PresContext()->PresShell()->PostReflowCallback(&mInner);
     mInner.mPostedReflowCallback = true;
   }
   if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
     mInner.mHadNonInitialReflow = true;
   }
 
+  mInner.UpdateSticky();
+
   // Set up overflow areas for block frames for the benefit of
   // text-overflow.
   nsIFrame* f = mInner.mScrolledFrame->GetContentInsertionFrame();
   if (nsLayoutUtils::GetAsBlock(f)) {
     nsRect origRect = f->GetRect();
     nsRect clippedRect = origRect;
     clippedRect.MoveBy(mInner.mScrollPort.TopLeft());
     clippedRect.IntersectRect(clippedRect, mInner.mScrollPort);
@@ -3709,16 +3713,27 @@ nsGfxScrollFrameInner::UpdateOverflow()
     mOuter->PresContext()->PresShell()->FrameNeedsReflow(
       mOuter, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
     return false;  // reflowing will update overflow
   }
   return mOuter->nsContainerFrame::UpdateOverflow();
 }
 
 void
+nsGfxScrollFrameInner::UpdateSticky()
+{
+  StickyScrollContainer* ssc = StickyScrollContainer::
+    GetStickyScrollContainerForScrollFrame(mOuter);
+  if (ssc) {
+    nsIScrollableFrame* scrollFrame = do_QueryFrame(mOuter);
+    ssc->UpdatePositions(scrollFrame->GetScrollPosition(), mOuter);
+  }
+}
+
+void
 nsGfxScrollFrameInner::AdjustScrollbarRectForResizer(
                          nsIFrame* aFrame, nsPresContext* aPresContext,
                          nsRect& aRect, bool aHasResizer, bool aVertical)
 {
   if ((aVertical ? aRect.width : aRect.height) == 0)
     return;
 
   // if a content resizer is present, use its size. Otherwise, check if the
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -271,16 +271,18 @@ public:
   bool IsScrollingActive() const { return mScrollingActive || ShouldBuildLayer(); }
   void ResetScrollPositionForLayerPixelAlignment()
   {
     mScrollPosForLayerPixelAlignment = GetScrollPosition();
   }
 
   bool UpdateOverflow();
 
+  void UpdateSticky();
+
   // adjust the scrollbar rectangle aRect to account for any visible resizer.
   // aHasResizer specifies if there is a content resizer, however this method
   // will also check if a widget resizer is present as well.
   void AdjustScrollbarRectForResizer(nsIFrame* aFrame, nsPresContext* aPresContext,
                                      nsRect& aRect, bool aHasResizer, bool aVertical);
   // returns true if a resizer should be visible
   bool HasResizer() { return mResizerBox && !mCollapsedResizer; }
   void LayoutScrollbars(nsBoxLayoutState& aState,
--- a/layout/generic/nsHTMLReflowState.cpp
+++ b/layout/generic/nsHTMLReflowState.cpp
@@ -18,25 +18,27 @@
 #include "nsFlexContainerFrame.h"
 #include "nsImageFrame.h"
 #include "nsTableFrame.h"
 #include "nsTableCellFrame.h"
 #include "nsIPercentHeightObserver.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/Preferences.h"
 #include "nsFontInflationData.h"
+#include "StickyScrollContainer.h"
 #include <algorithm>
 
 #ifdef DEBUG
 #undef NOISY_VERTICAL_ALIGN
 #else
 #undef NOISY_VERTICAL_ALIGN
 #endif
 
 using namespace mozilla;
+using namespace mozilla::css;
 using namespace mozilla::layout;
 
 enum eNormalLineHeightControl {
   eUninitialized = -1,
   eNoExternalLeading = 0,   // does not include external leading 
   eIncludeExternalLeading,  // use whatever value font vendor provides
   eCompensateLeading        // compensate leading if leading provided by font vendor is not enough
 };
@@ -837,16 +839,19 @@ nsHTMLReflowState::ApplyRelativePosition
     *normalPosition = *aPosition;
   } else {
     props.Set(nsIFrame::NormalPositionProperty(), new nsPoint(*aPosition));
   }
 
   const nsStyleDisplay* display = aFrame->StyleDisplay();
   if (NS_STYLE_POSITION_RELATIVE == display->mPosition) {
     *aPosition += nsPoint(aComputedOffsets.left, aComputedOffsets.top);
+  } else if (NS_STYLE_POSITION_STICKY == display->mPosition) {
+    *aPosition = StickyScrollContainer::StickyScrollContainerForFrame(aFrame)->
+      ComputePosition(aFrame);
   }
 }
 
 nsIFrame*
 nsHTMLReflowState::GetHypotheticalBoxContainer(nsIFrame* aFrame,
                                                nscoord& aCBLeftEdge,
                                                nscoord& aCBWidth)
 {
@@ -1935,18 +1940,21 @@ nsHTMLReflowState::InitConstraints(nsPre
           // default to interpreting the height like 'auto'
           heightUnit = eStyleUnit_Auto;
         }
       }
     }
 
     // Compute our offsets if the element is relatively positioned.  We need
     // the correct containing block width and height here, which is why we need
-    // to do it after all the quirks-n-such above.
-    if (mStyleDisplay->IsRelativelyPositioned(frame)) {
+    // to do it after all the quirks-n-such above. (If the element is sticky
+    // positioned, we need to wait until the scroll container knows its size,
+    // so we compute offsets from StickyScrollContainer::UpdatePositions.)
+    if (mStyleDisplay->IsRelativelyPositioned(frame) &&
+        NS_STYLE_POSITION_RELATIVE == mStyleDisplay->mPosition) {
       uint8_t direction = NS_STYLE_DIRECTION_LTR;
       if (cbrs && NS_STYLE_DIRECTION_RTL == cbrs->mStyleVisibility->mDirection) {
         direction = NS_STYLE_DIRECTION_RTL;
       }
       ComputeRelativeOffsets(direction, frame, aContainingBlockWidth,
           aContainingBlockHeight, mComputedOffsets);
     } else {
       // Initialize offsets to 0
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -922,16 +922,17 @@ public:
     static NS_PROPERTY_DESCRIPTOR_CONST FramePropertyDescriptor descriptor = { nullptr, dtor }; \
     return &descriptor;                                                                        \
   }
 
   NS_DECLARE_FRAME_PROPERTY(IBSplitSpecialSibling, nullptr)
   NS_DECLARE_FRAME_PROPERTY(IBSplitSpecialPrevSibling, nullptr)
 
   NS_DECLARE_FRAME_PROPERTY(NormalPositionProperty, DestroyPoint)
+  NS_DECLARE_FRAME_PROPERTY(ComputedStickyOffsetProperty, DestroyMargin)
 
   NS_DECLARE_FRAME_PROPERTY(OutlineInnerRectProperty, DestroyRect)
   NS_DECLARE_FRAME_PROPERTY(PreEffectsBBoxProperty, DestroyRect)
   NS_DECLARE_FRAME_PROPERTY(PreTransformOverflowAreasProperty,
                             DestroyOverflowAreas)
 
   // The initial overflow area passed to FinishAndStoreOverflow. This is only set
   // on frames that Preserve3D(), and when at least one of the overflow areas