servo: Merge #18212 - Add support for position:sticky (from mrobinson:position-sticky); r=emilio
authorMartin Robinson <mrobinson@igalia.com>
Tue, 05 Sep 2017 15:36:47 -0500
changeset 428547 3be607112552ab93e30c07e6f46af3eec0ea6536
parent 428546 ba9d54c93db83a4d5370e3af3a14520e2b55bb62
child 428548 e36fd3453c2826871da5412fb102234d19ece473
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs18212
milestone57.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
servo: Merge #18212 - Add support for position:sticky (from mrobinson:position-sticky); r=emilio This leverages the position:sticky support in WebRender to bring basic support for position:sticky in Servo. There are still some issues with nested sticky flows as well as a few other corner cases. Tests are imported from WPT and can be removed once we update to the latest version. <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [x] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: f1fab036ab16910587c3b0d7813c78b49562f6d9
servo/components/gfx/display_list/mod.rs
servo/components/layout/block.rs
servo/components/layout/display_list_builder.rs
servo/components/layout/fragment.rs
servo/components/layout/query.rs
servo/components/layout/sequential.rs
servo/components/layout/table.rs
servo/components/layout/table_caption.rs
servo/components/layout/table_cell.rs
servo/components/layout/table_row.rs
servo/components/layout/table_rowgroup.rs
servo/components/layout/table_wrapper.rs
servo/components/layout/webrender_helpers.rs
servo/components/style/properties/longhand/box.mako.rs
--- a/servo/components/gfx/display_list/mod.rs
+++ b/servo/components/gfx/display_list/mod.rs
@@ -29,17 +29,18 @@ use std::collections::HashMap;
 use std::fmt;
 use std::sync::Arc;
 use style::computed_values::{border_style, image_rendering};
 use style::values::computed::Filter;
 use style_traits::cursor::Cursor;
 use text::TextRun;
 use text::glyph::ByteIndex;
 use webrender_api::{self, ClipAndScrollInfo, ClipId, ColorF, GradientStop, LocalClip};
-use webrender_api::{MixBlendMode, ScrollPolicy, ScrollSensitivity, TransformStyle};
+use webrender_api::{MixBlendMode, ScrollPolicy, ScrollSensitivity, StickyFrameInfo};
+use webrender_api::TransformStyle;
 
 pub use style::dom::OpaqueNode;
 
 /// The factor that we multiply the blur radius by in order to inflate the boundaries of display
 /// items that involve a blur. This ensures that the display item boundaries include all the ink.
 pub static BLUR_INFLATION_FACTOR: i32 = 3;
 
 #[derive(Deserialize, HeapSizeOf, Serialize)]
@@ -554,16 +555,17 @@ impl fmt::Debug for StackingContext {
                self.overflow,
                self.id)
     }
 }
 
 #[derive(Clone, Debug, Deserialize, HeapSizeOf, Serialize)]
 pub enum ScrollRootType {
     ScrollFrame(ScrollSensitivity),
+    StickyFrame(StickyFrameInfo),
     Clip,
 }
 
 /// Defines a stacking context.
 #[derive(Clone, Debug, Deserialize, HeapSizeOf, Serialize)]
 pub struct ScrollRoot {
     /// The WebRender clip id of this scroll root based on the source of this clip
     /// and information about the fragment.
--- a/servo/components/layout/block.rs
+++ b/servo/components/layout/block.rs
@@ -24,19 +24,19 @@
 //! available here:
 //!
 //!   http://dev.w3.org/csswg/css-sizing/
 
 #![deny(unsafe_code)]
 
 use app_units::{Au, MAX_AU};
 use context::LayoutContext;
-use display_list_builder::{BorderPaintingMode, DisplayListBuildState};
-use display_list_builder::BlockFlowDisplayListBuilding;
-use euclid::{Point2D, Size2D, Rect};
+use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode};
+use display_list_builder::{DisplayListBuildState, EstablishContainingBlock};
+use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
 use floats::{ClearType, FloatKind, Floats, PlacementInfo};
 use flow::{self, BaseFlow, EarlyAbsolutePositionInfo, Flow, FlowClass, ForceNonfloatedFlag};
 use flow::{BLOCK_POSITION_IS_STATIC, CLEARS_LEFT, CLEARS_RIGHT};
 use flow::{CONTAINS_TEXT_OR_REPLACED_FRAGMENTS, INLINE_POSITION_IS_STATIC};
 use flow::{IS_ABSOLUTELY_POSITIONED, FragmentationContext, MARGINS_CANNOT_COLLAPSE};
 use flow::{ImmutableFlowUtils, LateAbsolutePositionInfo, OpaqueFlow};
 use flow_list::FlowList;
 use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, Overflow};
@@ -49,17 +49,17 @@ use sequential;
 use serde::{Serialize, Serializer};
 use servo_geometry::max_rect;
 use std::cmp::{max, min};
 use std::fmt;
 use std::sync::Arc;
 use style::computed_values::{box_sizing, display, float, overflow_x};
 use style::computed_values::{position, text_align};
 use style::context::SharedStyleContext;
-use style::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode};
+use style::logical_geometry::{LogicalMargin, LogicalPoint, LogicalRect, LogicalSize, WritingMode};
 use style::properties::ComputedValues;
 use style::servo::restyle_damage::{BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW};
 use style::values::computed::{LengthOrPercentageOrNone, LengthOrPercentage};
 use style::values::computed::LengthOrPercentageOrAuto;
 use traversal::PreorderFlowTraversal;
 
 /// Information specific to floated blocks.
 #[derive(Clone, Serialize)]
@@ -638,17 +638,17 @@ impl BlockFlow {
         }
     }
 
     /// Return this flow's fragment.
     pub fn fragment(&mut self) -> &mut Fragment {
         &mut self.fragment
     }
 
-    pub fn stacking_relative_position(&self, coor: CoordinateSystem) -> Rect<Au> {
+    pub fn stacking_relative_border_box(&self, coor: CoordinateSystem) -> Rect<Au> {
         return self.fragment.stacking_relative_border_box(
             &self.base.stacking_relative_position,
             &self.base.early_absolute_position_info.relative_containing_block_size,
             self.base.early_absolute_position_info.relative_containing_block_mode,
             coor);
     }
 
     /// Return the size of the containing block for the given immediate absolute descendant of this
@@ -1785,16 +1785,30 @@ impl BlockFlow {
             self.flags.remove(HAS_SCROLLING_OVERFLOW);
         }
     }
 
     pub fn has_scrolling_overflow(&mut self) -> bool {
         self.flags.contains(HAS_SCROLLING_OVERFLOW)
     }
 
+    // Return offset from original position because of `position: sticky`.
+    pub fn sticky_position(&self) -> SideOffsets2D<MaybeAuto> {
+        let containing_block_size = &self.base.early_absolute_position_info
+                                              .relative_containing_block_size;
+        let writing_mode = self.base.early_absolute_position_info.relative_containing_block_mode;
+        let offsets = self.fragment.style().logical_position();
+        let as_margins = LogicalMargin::new(writing_mode,
+            MaybeAuto::from_style(offsets.block_start, containing_block_size.inline),
+            MaybeAuto::from_style(offsets.inline_end, containing_block_size.inline),
+            MaybeAuto::from_style(offsets.block_end, containing_block_size.inline),
+            MaybeAuto::from_style(offsets.inline_start, containing_block_size.inline));
+        as_margins.to_physical(writing_mode)
+    }
+
 }
 
 impl Flow for BlockFlow {
     fn class(&self) -> FlowClass {
         FlowClass::Block
     }
 
     fn as_mut_block(&mut self) -> &mut BlockFlow {
@@ -2132,17 +2146,17 @@ impl Flow for BlockFlow {
                     LengthOrPercentageOrAuto::Auto &&
                 self.fragment.style().logical_position().block_end ==
                 LengthOrPercentageOrAuto::Auto {
             self.base.position.start.b = block_position
         }
     }
 
     fn collect_stacking_contexts(&mut self, state: &mut DisplayListBuildState) {
-        self.collect_stacking_contexts_for_block(state);
+        self.collect_stacking_contexts_for_block(state, EstablishContainingBlock::Yes);
     }
 
     fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
         self.build_display_list_for_block(state, BorderPaintingMode::Separate);
     }
 
     fn repair_style(&mut self, new_style: &::ServoArc<ComputedValues>) {
         self.fragment.repair_style(new_style)
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -9,17 +9,18 @@
 //! paint.
 
 #![deny(unsafe_code)]
 
 use app_units::{AU_PER_PX, Au};
 use block::{BlockFlow, BlockStackingContextType};
 use canvas_traits::canvas::{CanvasMsg, FromLayoutMsg};
 use context::LayoutContext;
-use euclid::{Transform3D, Point2D, Vector2D, Rect, SideOffsets2D, Size2D, TypedSize2D};
+use euclid::{Point2D, Rect, SideOffsets2D, Size2D, Transform3D, TypedSize2D};
+use euclid::Vector2D;
 use flex::FlexFlow;
 use flow::{BaseFlow, Flow, IS_ABSOLUTELY_POSITIONED};
 use flow_ref::FlowRef;
 use fragment::{CanvasFragmentSource, CoordinateSystem, Fragment, ImageFragmentInfo, ScannedTextFragmentInfo};
 use fragment::{SpecificFragmentInfo, TruncatedFragmentInfo};
 use gfx::display_list;
 use gfx::display_list::{BLUR_INFLATION_FACTOR, BaseDisplayItem, BorderDetails, BorderDisplayItem};
 use gfx::display_list::{BorderRadii, BoxShadowClipMode, BoxShadowDisplayItem, ClippingRegion};
@@ -67,17 +68,18 @@ use style::values::generics::image::{Gra
 use style::values::generics::image::{Image, ShapeExtent};
 use style::values::generics::image::PaintWorklet;
 use style::values::specified::position::{X, Y};
 use style_traits::CSSPixel;
 use style_traits::ToCss;
 use style_traits::cursor::Cursor;
 use table_cell::CollapsedBordersForCell;
 use webrender_api::{ClipAndScrollInfo, ClipId, ColorF, ComplexClipRegion, GradientStop, LineStyle};
-use webrender_api::{LocalClip, RepeatMode, ScrollPolicy, ScrollSensitivity};
+use webrender_api::{LocalClip, RepeatMode, ScrollPolicy, ScrollSensitivity, StickyFrameInfo};
+use webrender_api::StickySideConstraint;
 use webrender_helpers::{ToBorderRadius, ToMixBlendMode, ToRectF, ToTransformStyle};
 
 trait ResolvePercentage {
     fn resolve(&self, length: u32) -> u32;
 }
 
 impl ResolvePercentage for NumberOrPercentage {
     fn resolve(&self, length: u32) -> u32 {
@@ -96,21 +98,21 @@ fn convert_repeat_mode(from: RepeatKeywo
     match from {
         RepeatKeyword::Stretch => RepeatMode::Stretch,
         RepeatKeyword::Repeat => RepeatMode::Repeat,
         RepeatKeyword::Round => RepeatMode::Round,
         RepeatKeyword::Space => RepeatMode::Space,
     }
 }
 
-fn establishes_containing_block_for_absolute(positioning: position::T) -> bool {
-    match positioning {
-        position::T::absolute | position::T::relative | position::T::fixed => true,
-        _ => false,
-    }
+fn establishes_containing_block_for_absolute(can_establish_containing_block: EstablishContainingBlock,
+                                             positioning: position::T)
+                                             -> bool {
+    can_establish_containing_block == EstablishContainingBlock::Yes &&
+    position::T::static_ != positioning
 }
 
 trait RgbColor {
     fn rgb(r: u8, g: u8, b: u8) -> Self;
 }
 
 impl RgbColor for ColorF {
     fn rgb(r: u8, g: u8, b: u8) -> Self {
@@ -187,16 +189,19 @@ pub struct DisplayListBuildState<'a> {
 
     /// A stack of clips used to cull display list entries that are outside the
     /// rendered region.
     pub clip_stack: Vec<Rect<Au>>,
 
     /// A stack of clips used to cull display list entries that are outside the
     /// rendered region, but only collected at containing block boundaries.
     pub containing_block_clip_stack: Vec<Rect<Au>>,
+
+    /// The flow parent's content box, used to calculate sticky constraints.
+    parent_stacking_relative_content_box: Rect<Au>,
 }
 
 impl<'a> DisplayListBuildState<'a> {
     pub fn new(layout_context: &'a LayoutContext) -> DisplayListBuildState<'a> {
         let root_clip_info = ClipAndScrollInfo::simple(layout_context.id.root_scroll_node());
         DisplayListBuildState {
             layout_context: layout_context,
             root_stacking_context: StackingContext::root(layout_context.id),
@@ -206,16 +211,17 @@ impl<'a> DisplayListBuildState<'a> {
             processing_scroll_root_element: false,
             current_stacking_context_id: StackingContextId::root(),
             current_real_stacking_context_id: StackingContextId::root(),
             current_clip_and_scroll_info: root_clip_info,
             containing_block_clip_and_scroll_info: root_clip_info,
             iframe_sizes: Vec::new(),
             clip_stack: Vec::new(),
             containing_block_clip_stack: Vec::new(),
+            parent_stacking_relative_content_box: Rect::zero(),
         }
     }
 
     fn add_display_item(&mut self, display_item: DisplayItem) {
         let items = self.items.entry(display_item.stacking_context_id()).or_insert(Vec::new());
         items.push(display_item);
     }
 
@@ -2249,27 +2255,39 @@ impl FragmentDisplayListBuilding for Fra
             PseudoElementType::Before(_) => FragmentType::BeforePseudoContent,
             PseudoElementType::After(_) => FragmentType::AfterPseudoContent,
             PseudoElementType::DetailsSummary(_) => FragmentType::FragmentBody,
             PseudoElementType::DetailsContent(_) => FragmentType::FragmentBody,
         }
     }
 }
 
+#[derive(Clone, Copy, PartialEq)]
+pub enum EstablishContainingBlock {
+    Yes,
+    No,
+}
+
 pub trait BlockFlowDisplayListBuilding {
-    fn collect_stacking_contexts_for_block(&mut self, state: &mut DisplayListBuildState);
+    fn collect_stacking_contexts_for_block(&mut self,
+                                           state: &mut DisplayListBuildState,
+                                           can_establish_containing_block: EstablishContainingBlock);
 
     fn transform_clip_to_coordinate_space(&mut self,
                                           state: &mut DisplayListBuildState,
                                           preserved_state: &mut PreservedDisplayListState);
     fn setup_clipping_for_block(&mut self,
                                 state: &mut DisplayListBuildState,
                                 preserved_state: &mut PreservedDisplayListState,
-                                stacking_context_type: BlockStackingContextType)
+                                stacking_context_type: BlockStackingContextType,
+                                can_establish_containing_block: EstablishContainingBlock)
                                 -> ClipAndScrollInfo;
+    fn setup_scroll_root_for_position(&mut self,
+                                      state: &mut DisplayListBuildState,
+                                      border_box: &Rect<Au>);
     fn setup_scroll_root_for_overflow(&mut self,
                                       state: &mut DisplayListBuildState,
                                       border_box: &Rect<Au>);
     fn setup_scroll_root_for_css_clip(&mut self,
                                       state: &mut DisplayListBuildState,
                                       preserved_state: &mut PreservedDisplayListState,
                                       stacking_relative_border_box: &Rect<Au>);
     fn create_pseudo_stacking_context_for_block(&mut self,
@@ -2292,41 +2310,44 @@ pub trait BlockFlowDisplayListBuilding {
 /// TODO(mrobinson): It would be nice to use RAII here to avoid having to call restore.
 pub struct PreservedDisplayListState {
     stacking_context_id: StackingContextId,
     real_stacking_context_id: StackingContextId,
     clip_and_scroll_info: ClipAndScrollInfo,
     containing_block_clip_and_scroll_info: ClipAndScrollInfo,
     clips_pushed: usize,
     containing_block_clips_pushed: usize,
+    stacking_relative_content_box: Rect<Au>,
 }
 
 impl PreservedDisplayListState {
     fn new(state: &mut DisplayListBuildState) -> PreservedDisplayListState {
         PreservedDisplayListState {
             stacking_context_id: state.current_stacking_context_id,
             real_stacking_context_id: state.current_real_stacking_context_id,
             clip_and_scroll_info: state.current_clip_and_scroll_info,
             containing_block_clip_and_scroll_info: state.containing_block_clip_and_scroll_info,
             clips_pushed: 0,
             containing_block_clips_pushed: 0,
+            stacking_relative_content_box: state.parent_stacking_relative_content_box,
         }
     }
 
     fn switch_to_containing_block_clip(&mut self, state: &mut DisplayListBuildState) {
         let clip = state.containing_block_clip_stack.last().cloned().unwrap_or_else(max_rect);
         state.clip_stack.push(clip);
         self.clips_pushed += 1;
     }
 
     fn restore(self, state: &mut DisplayListBuildState) {
         state.current_stacking_context_id = self.stacking_context_id;
         state.current_real_stacking_context_id = self.real_stacking_context_id;
         state.current_clip_and_scroll_info = self.clip_and_scroll_info;
         state.containing_block_clip_and_scroll_info = self.containing_block_clip_and_scroll_info;
+        state.parent_stacking_relative_content_box = self.stacking_relative_content_box;
 
         let truncate_length = state.clip_stack.len() - self.clips_pushed;
         state.clip_stack.truncate(truncate_length);
 
         let truncate_length = state.containing_block_clip_stack.len() -
                               self.containing_block_clips_pushed;
         state.containing_block_clip_stack.truncate(truncate_length);
     }
@@ -2354,17 +2375,17 @@ impl PreservedDisplayListState {
 
 impl BlockFlowDisplayListBuilding for BlockFlow {
     fn transform_clip_to_coordinate_space(&mut self,
                                           state: &mut DisplayListBuildState,
                                           preserved_state: &mut PreservedDisplayListState) {
         if state.clip_stack.is_empty() {
             return;
         }
-        let border_box = self.stacking_relative_position(CoordinateSystem::Parent);
+        let border_box = self.stacking_relative_border_box(CoordinateSystem::Parent);
         let transform = match self.fragment.transform_matrix(&border_box) {
             Some(transform) => transform,
             None => return,
         };
 
         let perspective = self.fragment.perspective_matrix(&border_box)
                                        .unwrap_or_else(Transform3D::identity);
         let transform = transform.pre_mul(&perspective).inverse();
@@ -2407,17 +2428,19 @@ impl BlockFlowDisplayListBuilding for Bl
         }
 
         if let Some(clip) = state.containing_block_clip_stack.last().cloned() {
             state.containing_block_clip_stack.push(transform_clip(&clip));
             preserved_state.containing_block_clips_pushed += 1;
         }
     }
 
-    fn collect_stacking_contexts_for_block(&mut self, state: &mut DisplayListBuildState) {
+    fn collect_stacking_contexts_for_block(&mut self,
+                                           state: &mut DisplayListBuildState,
+                                           can_establish_containing_block: EstablishContainingBlock) {
         let mut preserved_state = PreservedDisplayListState::new(state);
 
         let block_stacking_context_type = self.block_stacking_context_type();
         self.base.stacking_context_id = match block_stacking_context_type {
             BlockStackingContextType::NonstackingContext => state.current_stacking_context_id,
             BlockStackingContextType::PseudoStackingContext |
             BlockStackingContextType::StackingContext => self.fragment.stacking_context_id(),
         };
@@ -2427,18 +2450,23 @@ impl BlockFlowDisplayListBuilding for Bl
             state.current_real_stacking_context_id = self.base.stacking_context_id;
         }
 
         // We are getting the id of the scroll root that contains us here, not the id of
         // any scroll root that we create. If we create a scroll root, its id will be
         // stored in state.current_clip_and_scroll_info. If we create a stacking context,
         // we don't want it to be contained by its own scroll root.
         let containing_clip_and_scroll_info =
-            self.setup_clipping_for_block(state, &mut preserved_state, block_stacking_context_type);
-        if establishes_containing_block_for_absolute(self.positioning()) {
+            self.setup_clipping_for_block(state,
+                                          &mut preserved_state,
+                                          block_stacking_context_type,
+                                          can_establish_containing_block);
+
+        if establishes_containing_block_for_absolute(can_establish_containing_block,
+                                                     self.positioning()) {
             state.containing_block_clip_and_scroll_info = state.current_clip_and_scroll_info;
         }
 
         match block_stacking_context_type {
             BlockStackingContextType::NonstackingContext => {
                 self.base.collect_stacking_contexts_for_children(state);
             }
             BlockStackingContextType::PseudoStackingContext => {
@@ -2454,17 +2482,18 @@ impl BlockFlowDisplayListBuilding for Bl
         }
 
         preserved_state.restore(state);
     }
 
     fn setup_clipping_for_block(&mut self,
                                 state: &mut DisplayListBuildState,
                                 preserved_state: &mut PreservedDisplayListState,
-                                stacking_context_type: BlockStackingContextType)
+                                stacking_context_type: BlockStackingContextType,
+                                can_establish_containing_block: EstablishContainingBlock)
                                 -> ClipAndScrollInfo {
         // If this block is absolutely positioned, we should be clipped and positioned by
         // the scroll root of our nearest ancestor that establishes a containing block.
         let containing_clip_and_scroll_info = match self.positioning() {
             position::T::absolute => {
                 preserved_state.switch_to_containing_block_clip(state);
                 state.current_clip_and_scroll_info = state.containing_block_clip_and_scroll_info;
                 state.containing_block_clip_and_scroll_info
@@ -2472,45 +2501,118 @@ impl BlockFlowDisplayListBuilding for Bl
             position::T::fixed => {
                 preserved_state.push_clip(state, &max_rect(), position::T::fixed);
                 state.current_clip_and_scroll_info
             }
             _ => state.current_clip_and_scroll_info,
         };
         self.base.clip_and_scroll_info = Some(containing_clip_and_scroll_info);
 
-        let coordinate_system = if self.fragment.establishes_stacking_context() {
-            CoordinateSystem::Own
+        let stacking_relative_border_box = if self.fragment.establishes_stacking_context() {
+            self.stacking_relative_border_box(CoordinateSystem::Own)
         } else {
-            CoordinateSystem::Parent
+            self.stacking_relative_border_box(CoordinateSystem::Parent)
         };
 
-        let stacking_relative_border_box = self.fragment.stacking_relative_border_box(
-            &self.base.stacking_relative_position,
-            &self.base.early_absolute_position_info.relative_containing_block_size,
-            self.base.early_absolute_position_info.relative_containing_block_mode,
-            coordinate_system);
-
         if stacking_context_type == BlockStackingContextType::StackingContext {
             self.transform_clip_to_coordinate_space(state, preserved_state);
         }
 
+        self.setup_scroll_root_for_position(state, &stacking_relative_border_box);
         self.setup_scroll_root_for_overflow(state, &stacking_relative_border_box);
         self.setup_scroll_root_for_css_clip(state, preserved_state, &stacking_relative_border_box);
         self.base.clip = state.clip_stack.last().cloned().unwrap_or_else(max_rect);
 
+        // We keep track of our position so that any stickily positioned elements can
+        // properly determine the extent of their movement relative to scrolling containers.
+        if can_establish_containing_block == EstablishContainingBlock::Yes {
+            let border_box = if self.fragment.establishes_stacking_context() {
+                stacking_relative_border_box
+            } else {
+                self.stacking_relative_border_box(CoordinateSystem::Own)
+            };
+            state.parent_stacking_relative_content_box =
+               self.fragment.stacking_relative_content_box(&border_box)
+        }
+
         match self.positioning() {
             position::T::absolute | position::T::relative | position::T::fixed =>
                 state.containing_block_clip_and_scroll_info = state.current_clip_and_scroll_info,
             _ => {}
         }
 
         containing_clip_and_scroll_info
     }
 
+    fn setup_scroll_root_for_position(&mut self,
+                                      state: &mut DisplayListBuildState,
+                                      border_box: &Rect<Au>) {
+        if self.positioning() != position::T::sticky {
+            return;
+        }
+
+        let sticky_position = self.sticky_position();
+        if sticky_position.left == MaybeAuto::Auto && sticky_position.right == MaybeAuto::Auto &&
+           sticky_position.top == MaybeAuto::Auto && sticky_position.bottom == MaybeAuto::Auto {
+            return;
+        }
+
+        // Since position: sticky elements always establish a stacking context, we will
+        // have previously calculated our border box in our own coordinate system. In
+        // order to properly calculate max offsets we need to compare our size and
+        // position in our parent's coordinate system.
+        let border_box_in_parent = self.stacking_relative_border_box(CoordinateSystem::Parent);
+        let margins = self.fragment.margin.to_physical(
+            self.base.early_absolute_position_info.relative_containing_block_mode);
+
+        // Position:sticky elements are always restricted based on the size and position of
+        // their containing block, which for sticky items is like relative and statically
+        // positioned items: just the parent block.
+        let constraint_rect = state.parent_stacking_relative_content_box;
+
+        let to_max_offset = |constraint_edge: Au, moving_edge: Au| -> f32 {
+            (constraint_edge - moving_edge).to_f32_px()
+        };
+
+        let to_sticky_info = |margin: MaybeAuto, max_offset: f32| -> Option<StickySideConstraint> {
+            match margin {
+                MaybeAuto::Auto => None,
+                MaybeAuto::Specified(value) =>
+                    Some(StickySideConstraint { margin: value.to_f32_px(), max_offset }),
+            }
+        };
+
+        let sticky_frame_info = StickyFrameInfo::new(
+             to_sticky_info(sticky_position.top,
+                            to_max_offset(constraint_rect.max_y(), border_box_in_parent.max_y())),
+             to_sticky_info(sticky_position.right,
+                            to_max_offset(constraint_rect.min_x(), border_box_in_parent.min_x() - margins.left)),
+             to_sticky_info(sticky_position.bottom,
+                            to_max_offset(constraint_rect.min_y(), border_box_in_parent.min_y() - margins.top)),
+             to_sticky_info(sticky_position.left,
+                            to_max_offset(constraint_rect.max_x(), border_box_in_parent.max_x())));
+
+        let new_scroll_root_id = ClipId::new(self.fragment.unique_id(IdType::OverflowClip),
+                                             state.layout_context.id.to_webrender());
+        let parent_id = self.clip_and_scroll_info(state.layout_context.id).scroll_node_id;
+        state.add_scroll_root(
+            ScrollRoot {
+                id: new_scroll_root_id,
+                parent_id: parent_id,
+                clip: ClippingRegion::from_rect(border_box),
+                content_rect: Rect::zero(),
+                root_type: ScrollRootType::StickyFrame(sticky_frame_info),
+            },
+        );
+
+        let new_clip_and_scroll_info = ClipAndScrollInfo::simple(new_scroll_root_id);
+        self.base.clip_and_scroll_info = Some(new_clip_and_scroll_info);
+        state.current_clip_and_scroll_info = new_clip_and_scroll_info;
+    }
+
     fn setup_scroll_root_for_overflow(&mut self,
                                       state: &mut DisplayListBuildState,
                                       border_box: &Rect<Au>) {
         if !self.overflow_style_may_require_scroll_root() {
             return;
         }
 
         let content_box = self.fragment.stacking_relative_content_box(&border_box);
@@ -2732,17 +2834,18 @@ pub trait InlineFlowDisplayListBuilding 
 impl InlineFlowDisplayListBuilding for InlineFlow {
     fn collect_stacking_contexts_for_inline(&mut self, state: &mut DisplayListBuildState) {
         self.base.stacking_context_id = state.current_stacking_context_id;
         self.base.clip_and_scroll_info = Some(state.current_clip_and_scroll_info);
         self.base.clip = state.clip_stack.last().cloned().unwrap_or_else(max_rect);
 
         for fragment in self.fragments.fragments.iter_mut() {
             let previous_cb_clip_scroll_info = state.containing_block_clip_and_scroll_info;
-            if establishes_containing_block_for_absolute(fragment.style.get_box().position) {
+            if establishes_containing_block_for_absolute(EstablishContainingBlock::Yes,
+                                                         fragment.style.get_box().position) {
                 state.containing_block_clip_and_scroll_info = state.current_clip_and_scroll_info;
             }
 
             match fragment.specific {
                 SpecificFragmentInfo::InlineBlock(ref mut block_flow) => {
                     let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref);
                     block_flow.collect_stacking_contexts(state);
                 }
--- a/servo/components/layout/fragment.rs
+++ b/servo/components/layout/fragment.rs
@@ -2513,18 +2513,19 @@ impl Fragment {
             return true;
         }
 
         if self.style().get_box().transform_style == transform_style::T::preserve_3d ||
            self.style().overrides_transform_style() {
             return true
         }
 
-        // Fixed position blocks always create stacking contexts.
-        if self.style.get_box().position == position::T::fixed {
+        // Fixed position and sticky position always create stacking contexts.
+        if self.style().get_box().position == position::T::fixed ||
+           self.style().get_box().position == position::T::sticky  {
             return true
         }
 
         // Statically positioned fragments don't establish stacking contexts if the previous
         // conditions are not fulfilled. Furthermore, z-index doesn't apply to statically
         // positioned fragments.
         if self.style().get_box().position == position::T::static_ {
             return false;
--- a/servo/components/layout/query.rs
+++ b/servo/components/layout/query.rs
@@ -593,16 +593,17 @@ impl FragmentBorderBoxIterator for Paren
                                          &fragment.specific) {
                 // Spec says it's valid if any of these are true:
                 //  1) Is the body element
                 //  2) Is static position *and* is a table or table cell
                 //  3) Is not static position
                 (true, _, _) |
                 (false, computed_values::position::T::static_, &SpecificFragmentInfo::Table) |
                 (false, computed_values::position::T::static_, &SpecificFragmentInfo::TableCell) |
+                (false, computed_values::position::T::sticky, _) |
                 (false, computed_values::position::T::absolute, _) |
                 (false, computed_values::position::T::relative, _) |
                 (false, computed_values::position::T::fixed, _) => true,
 
                 // Otherwise, it's not a valid parent
                 (false, computed_values::position::T::static_, _) => false,
             };
 
@@ -761,17 +762,17 @@ where
 
         PropertyId::Custom(ref name) => {
             return style.computed_value_to_string(PropertyDeclarationId::Custom(name))
         }
     };
 
     let positioned = match style.get_box().position {
         position::computed_value::T::relative |
-        /*position::computed_value::T::sticky |*/
+        position::computed_value::T::sticky |
         position::computed_value::T::fixed |
         position::computed_value::T::absolute => true,
         _ => false
     };
 
     //TODO: determine whether requested property applies to the element.
     //      eg. width does not apply to non-replaced inline elements.
     // Existing browsers disagree about when left/top/right/bottom apply
--- a/servo/components/layout/sequential.rs
+++ b/servo/components/layout/sequential.rs
@@ -88,17 +88,17 @@ pub fn iterate_through_flow_tree_fragmen
 
         for kid in flow::mut_base(flow).child_iter_mut() {
             let mut stacking_context_position = *stacking_context_position;
             if kid.is_block_flow() && kid.as_block().fragment.establishes_stacking_context() {
                 stacking_context_position = Point2D::new(kid.as_block().fragment.margin.inline_start, Au(0)) +
                                             flow::base(kid).stacking_relative_position +
                                             stacking_context_position.to_vector();
                 let relative_position = kid.as_block()
-                    .stacking_relative_position(CoordinateSystem::Own);
+                    .stacking_relative_border_box(CoordinateSystem::Own);
                 if let Some(matrix) = kid.as_block()
                        .fragment
                        .transform_matrix(&relative_position) {
                     let transform_matrix = matrix.transform_point2d(&Point2D::zero());
                     stacking_context_position = stacking_context_position +
                                                 Vector2D::new(Au::from_f32_px(transform_matrix.x),
                                                               Au::from_f32_px(transform_matrix.y))
                 }
--- a/servo/components/layout/table.rs
+++ b/servo/components/layout/table.rs
@@ -5,17 +5,18 @@
 //! CSS table formatting contexts.
 
 #![deny(unsafe_code)]
 
 use app_units::Au;
 use block::{BlockFlow, CandidateBSizeIterator, ISizeAndMarginsComputer};
 use block::{ISizeConstraintInput, ISizeConstraintSolution};
 use context::LayoutContext;
-use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode, DisplayListBuildState};
+use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode};
+use display_list_builder::{DisplayListBuildState, EstablishContainingBlock};
 use euclid::Point2D;
 use flow;
 use flow::{BaseFlow, EarlyAbsolutePositionInfo, Flow, FlowClass, ImmutableFlowUtils, OpaqueFlow};
 use flow_list::MutFlowListIterator;
 use fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
 use gfx_traits::print_tree::PrintTree;
 use layout_debug;
 use model::{IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto};
@@ -498,17 +499,17 @@ impl Flow for TableFlow {
             border_collapse::T::separate => BorderPaintingMode::Separate,
             border_collapse::T::collapse => BorderPaintingMode::Hidden,
         };
 
         self.block_flow.build_display_list_for_block(state, border_painting_mode);
     }
 
     fn collect_stacking_contexts(&mut self, state: &mut DisplayListBuildState) {
-        self.block_flow.collect_stacking_contexts(state);
+        self.block_flow.collect_stacking_contexts_for_block(state, EstablishContainingBlock::Yes);
     }
 
     fn repair_style(&mut self, new_style: &::ServoArc<ComputedValues>) {
         self.block_flow.repair_style(new_style)
     }
 
     fn compute_overflow(&self) -> Overflow {
         self.block_flow.compute_overflow()
--- a/servo/components/layout/table_caption.rs
+++ b/servo/components/layout/table_caption.rs
@@ -4,17 +4,18 @@
 
 //! CSS table formatting contexts.
 
 #![deny(unsafe_code)]
 
 use app_units::Au;
 use block::BlockFlow;
 use context::LayoutContext;
-use display_list_builder::DisplayListBuildState;
+use display_list_builder::{BlockFlowDisplayListBuilding, DisplayListBuildState};
+use display_list_builder::EstablishContainingBlock;
 use euclid::Point2D;
 use flow::{Flow, FlowClass, OpaqueFlow};
 use fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
 use gfx_traits::print_tree::PrintTree;
 use std::fmt;
 use style::logical_geometry::LogicalSize;
 use style::properties::ComputedValues;
 
@@ -75,17 +76,17 @@ impl Flow for TableCaptionFlow {
     }
 
     fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
         debug!("build_display_list_table_caption: same process as block flow");
         self.block_flow.build_display_list(state);
     }
 
     fn collect_stacking_contexts(&mut self, state: &mut DisplayListBuildState) {
-        self.block_flow.collect_stacking_contexts(state);
+        self.block_flow.collect_stacking_contexts_for_block(state, EstablishContainingBlock::No);
     }
 
     fn repair_style(&mut self, new_style: &::ServoArc<ComputedValues>) {
         self.block_flow.repair_style(new_style)
     }
 
     fn compute_overflow(&self) -> Overflow {
         self.block_flow.compute_overflow()
--- a/servo/components/layout/table_cell.rs
+++ b/servo/components/layout/table_cell.rs
@@ -4,17 +4,18 @@
 
 //! CSS table formatting contexts.
 
 #![deny(unsafe_code)]
 
 use app_units::Au;
 use block::{BlockFlow, ISizeAndMarginsComputer, MarginsMayCollapseFlag};
 use context::LayoutContext;
-use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode, DisplayListBuildState};
+use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode};
+use display_list_builder::{DisplayListBuildState, EstablishContainingBlock};
 use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
 use flow::{self, Flow, FlowClass, IS_ABSOLUTELY_POSITIONED, OpaqueFlow};
 use fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
 use gfx_traits::print_tree::PrintTree;
 use layout_debug;
 use model::MaybeAuto;
 use script_layout_interface::wrapper_traits::ThreadSafeLayoutNode;
 use std::fmt;
@@ -256,17 +257,17 @@ impl Flow for TableCellFlow {
             border_collapse::T::separate => BorderPaintingMode::Separate,
             border_collapse::T::collapse => BorderPaintingMode::Collapse(&self.collapsed_borders),
         };
 
         self.block_flow.build_display_list_for_block(state, border_painting_mode)
     }
 
     fn collect_stacking_contexts(&mut self, state: &mut DisplayListBuildState) {
-        self.block_flow.collect_stacking_contexts(state);
+        self.block_flow.collect_stacking_contexts_for_block(state, EstablishContainingBlock::No);
     }
 
     fn repair_style(&mut self, new_style: &::ServoArc<ComputedValues>) {
         self.block_flow.repair_style(new_style)
     }
 
     fn compute_overflow(&self) -> Overflow {
         self.block_flow.compute_overflow()
--- a/servo/components/layout/table_row.rs
+++ b/servo/components/layout/table_row.rs
@@ -4,17 +4,18 @@
 
 //! CSS table formatting contexts.
 
 #![deny(unsafe_code)]
 
 use app_units::Au;
 use block::{BlockFlow, ISizeAndMarginsComputer};
 use context::LayoutContext;
-use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode, DisplayListBuildState};
+use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode};
+use display_list_builder::{DisplayListBuildState, EstablishContainingBlock};
 use euclid::Point2D;
 use flow::{self, EarlyAbsolutePositionInfo, Flow, FlowClass, ImmutableFlowUtils, OpaqueFlow};
 use flow_list::MutFlowListIterator;
 use fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
 use gfx_traits::print_tree::PrintTree;
 use layout_debug;
 use model::MaybeAuto;
 use serde::{Serialize, Serializer};
@@ -473,17 +474,17 @@ impl Flow for TableRowFlow {
             border_collapse::T::separate => BorderPaintingMode::Separate,
             border_collapse::T::collapse => BorderPaintingMode::Hidden,
         };
 
         self.block_flow.build_display_list_for_block(state, border_painting_mode);
     }
 
     fn collect_stacking_contexts(&mut self, state: &mut DisplayListBuildState) {
-        self.block_flow.collect_stacking_contexts(state);
+        self.block_flow.collect_stacking_contexts_for_block(state, EstablishContainingBlock::No);
     }
 
     fn repair_style(&mut self, new_style: &::ServoArc<ComputedValues>) {
         self.block_flow.repair_style(new_style)
     }
 
     fn compute_overflow(&self) -> Overflow {
         self.block_flow.compute_overflow()
--- a/servo/components/layout/table_rowgroup.rs
+++ b/servo/components/layout/table_rowgroup.rs
@@ -4,17 +4,18 @@
 
 //! CSS table formatting contexts.
 
 #![deny(unsafe_code)]
 
 use app_units::Au;
 use block::{BlockFlow, ISizeAndMarginsComputer};
 use context::LayoutContext;
-use display_list_builder::DisplayListBuildState;
+use display_list_builder::{BlockFlowDisplayListBuilding, DisplayListBuildState};
+use display_list_builder::EstablishContainingBlock;
 use euclid::Point2D;
 use flow::{Flow, FlowClass, OpaqueFlow};
 use fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
 use gfx_traits::print_tree::PrintTree;
 use layout_debug;
 use serde::{Serialize, Serializer};
 use std::fmt;
 use std::iter::{IntoIterator, Iterator, Peekable};
@@ -178,17 +179,17 @@ impl Flow for TableRowGroupFlow {
     }
 
     fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
         debug!("build_display_list_table_rowgroup: same process as block flow");
         self.block_flow.build_display_list(state);
     }
 
     fn collect_stacking_contexts(&mut self, state: &mut DisplayListBuildState) {
-        self.block_flow.collect_stacking_contexts(state);
+        self.block_flow.collect_stacking_contexts_for_block(state, EstablishContainingBlock::No);
     }
 
     fn repair_style(&mut self, new_style: &::ServoArc<ComputedValues>) {
         self.block_flow.repair_style(new_style)
     }
 
     fn compute_overflow(&self) -> Overflow {
         self.block_flow.compute_overflow()
--- a/servo/components/layout/table_wrapper.rs
+++ b/servo/components/layout/table_wrapper.rs
@@ -12,17 +12,18 @@
 //! Hereafter this document is referred to as INTRINSIC.
 
 #![deny(unsafe_code)]
 
 use app_units::Au;
 use block::{AbsoluteNonReplaced, BlockFlow, FloatNonReplaced, ISizeAndMarginsComputer, ISizeConstraintInput};
 use block::{ISizeConstraintSolution, MarginsMayCollapseFlag};
 use context::LayoutContext;
-use display_list_builder::DisplayListBuildState;
+use display_list_builder::{BlockFlowDisplayListBuilding, DisplayListBuildState};
+use display_list_builder::EstablishContainingBlock;
 use euclid::Point2D;
 use floats::FloatKind;
 use flow::{Flow, FlowClass, ImmutableFlowUtils, INLINE_POSITION_IS_STATIC, OpaqueFlow};
 use fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
 use gfx_traits::print_tree::PrintTree;
 use model::MaybeAuto;
 use std::cmp::{max, min};
 use std::fmt;
@@ -452,17 +453,17 @@ impl Flow for TableWrapperFlow {
         self.block_flow.generated_containing_block_size(flow)
     }
 
     fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
         self.block_flow.build_display_list(state);
     }
 
     fn collect_stacking_contexts(&mut self, state: &mut DisplayListBuildState) {
-        self.block_flow.collect_stacking_contexts(state);
+        self.block_flow.collect_stacking_contexts_for_block(state, EstablishContainingBlock::No);
     }
 
     fn repair_style(&mut self, new_style: &::ServoArc<ComputedValues>) {
         self.block_flow.repair_style(new_style)
     }
 
     fn compute_overflow(&self) -> Overflow {
         self.block_flow.compute_overflow()
--- a/servo/components/layout/webrender_helpers.rs
+++ b/servo/components/layout/webrender_helpers.rs
@@ -487,31 +487,35 @@ impl WebRenderDisplayItemConverter for D
                                               stacking_context.mix_blend_mode,
                                               stacking_context.filters.to_filter_ops());
             }
             DisplayItem::PopStackingContext(_) => builder.pop_stacking_context(),
             DisplayItem::DefineClip(ref item) => {
                 builder.push_clip_id(item.scroll_root.parent_id);
 
                 let our_id = item.scroll_root.id;
+                let item_rect = item.scroll_root.clip.main.to_rectf();
                 let webrender_id = match item.scroll_root.root_type {
                    ScrollRootType::Clip => {
                         builder.define_clip(Some(our_id),
-                                            item.scroll_root.clip.main.to_rectf(),
+                                            item_rect,
                                             item.scroll_root.clip.get_complex_clips(),
                                             None)
                     }
                     ScrollRootType::ScrollFrame(scroll_sensitivity) => {
                         builder.define_scroll_frame(Some(our_id),
                                                     item.scroll_root.content_rect.to_rectf(),
                                                     item.scroll_root.clip.main.to_rectf(),
                                                     item.scroll_root.clip.get_complex_clips(),
                                                     None,
                                                     scroll_sensitivity)
                     }
+                    ScrollRootType::StickyFrame(sticky_frame_info) => {
+                        builder.define_sticky_frame(Some(our_id), item_rect, sticky_frame_info)
+                    }
                 };
                 debug_assert!(our_id == webrender_id);
 
                 builder.pop_clip_id();
             }
         }
     }
 }
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -198,18 +198,17 @@
 </%helpers:longhand>
 
 ${helpers.single_keyword("-moz-top-layer", "none top",
                          gecko_constant_prefix="NS_STYLE_TOP_LAYER",
                          gecko_ffi_name="mTopLayer", need_clone=True,
                          products="gecko", animation_value_type="none", internal=True,
                          spec="Internal (not web-exposed)")}
 
-${helpers.single_keyword("position", "static absolute relative fixed",
-                         extra_gecko_values="sticky",
+${helpers.single_keyword("position", "static absolute relative fixed sticky",
                          animation_value_type="discrete",
                          flags="CREATES_STACKING_CONTEXT ABSPOS_CB",
                          spec="https://drafts.csswg.org/css-position/#position-property")}
 
 <%helpers:single_keyword_computed name="float"
                                   values="none left right"
                                   // https://drafts.csswg.org/css-logical-props/#float-clear
                                   extra_specified="inline-start inline-end"