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 id25229
push userryanvm@gmail.com
push dateFri, 06 Sep 2013 20:49:59 +0000
treeherdermozilla-central@bdbdeda69f05 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron, dholbert
bugs886646
milestone26.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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