Bug 1474300 - Update webrender to commit e600bfe68efac6416ce2e8091d7344744771f6db. r?Gankro draft
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 12 Jul 2018 10:34:35 -0400
changeset 817363 73ded8b4bfef814aca91fdc10ee394da009d8b0b
parent 817312 fe17acc6e291e54463db3ea82697c714ae5a4b27
child 817364 c8ee3c1202de4c82553ac0976b212a2f91105bc4
push id116041
push userkgupta@mozilla.com
push dateThu, 12 Jul 2018 14:37:26 +0000
reviewersGankro
bugs1474300
milestone63.0a1
Bug 1474300 - Update webrender to commit e600bfe68efac6416ce2e8091d7344744771f6db. r?Gankro MozReview-Commit-ID: 2sxOBvDqDCc
gfx/webrender/Cargo.toml
gfx/webrender/src/batch.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_node.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/gpu_types.rs
gfx/webrender/src/hit_test.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/platform/macos/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/spatial_node.rs
gfx/webrender/src/tiling.rs
gfx/webrender/tests/angle_shader_validation.rs
gfx/webrender_api/Cargo.toml
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_bindings/Cargo.toml
gfx/webrender_bindings/revision.txt
gfx/wrench/Cargo.toml
gfx/wrench/src/angle.rs
gfx/wrench/src/egl.rs
gfx/wrench/src/main.rs
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -66,11 +66,11 @@ mozangle = "0.1"
 
 [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
 freetype = { version = "0.4", default-features = false }
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
-core-foundation = "0.5"
-core-graphics = "0.13"
-core-text = { version = "9.2.0", default-features = false }
+core-foundation = "0.6"
+core-graphics = "0.14"
+core-text = { version = "10", default-features = false }
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -470,18 +470,18 @@ impl AlphaBatchBuilder {
         };
 
         // Even though most of the time a splitter isn't used or needed,
         // they are cheap to construct so we will always pass one down.
         let mut splitter = BspSplitter::new();
 
         // Add each run in this picture to the batch.
         for run in &pic.runs {
-            let scroll_node = &ctx.clip_scroll_tree.nodes[run.clip_and_scroll.scroll_node_id.0];
-            let transform_id = ctx.transforms.get_id(scroll_node.transform_index);
+            let transform_id =
+                ctx.transforms.get_id(run.clip_and_scroll.scroll_node_id.transform_index());
             self.add_run_to_batch(
                 run,
                 transform_id,
                 ctx,
                 gpu_cache,
                 render_tasks,
                 task_id,
                 task_address,
@@ -665,17 +665,17 @@ impl AlphaBatchBuilder {
                         // If this picture is participating in a 3D rendering context,
                         // then don't add it to any batches here. Instead, create a polygon
                         // for it and add it to the current plane splitter.
                         if picture.is_in_3d_context {
                             // Push into parent plane splitter.
                             debug_assert!(picture.surface.is_some());
 
                             let real_xf = &ctx.clip_scroll_tree
-                                .nodes[picture.reference_frame_index.0]
+                                .spatial_nodes[picture.reference_frame_index.0]
                                 .world_content_transform
                                 .into();
                             let polygon = make_polygon(
                                 picture.real_local_rect,
                                 real_xf,
                                 prim_index.0,
                             );
 
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -2,21 +2,21 @@
  * 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/. */
 
 use api::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
 use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, LocalClip};
 use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle};
 use border::{ensure_no_corner_overlap};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
-use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId, TransformIndex};
+use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId};
 use ellipse::Ellipse;
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
-use gpu_types::{BoxShadowStretchMode};
+use gpu_types::{BoxShadowStretchMode, TransformIndex};
 use prim_store::{ClipData, ImageMaskData};
 use render_task::to_cache_size;
 use resource_cache::{ImageRequest, ResourceCache};
 use util::{LayoutToWorldFastTransform, MaxRect, calculate_screen_bounding_rect};
 use util::{extract_inner_rect_safe, pack_as_float};
 use std::sync::Arc;
 
 #[derive(Debug)]
@@ -252,38 +252,60 @@ impl ClipSource {
                     ..*info
                 })
             }
             _ => {
                 panic!("bug: other clip sources not expected here yet");
             }
         }
     }
+
+    pub fn is_rect(&self) -> bool {
+        match *self {
+            ClipSource::Rectangle(..) => true,
+            _ => false,
+        }
+    }
+
+    pub fn is_image_or_line_decoration_clip(&self) -> bool {
+        match *self {
+            ClipSource::Image(..) | ClipSource::LineDecoration(..) => true,
+            _ => false,
+        }
+    }
 }
 
 #[derive(Debug)]
 pub struct ClipSources {
     pub clips: Vec<(ClipSource, GpuCacheHandle)>,
     pub local_inner_rect: LayoutRect,
-    pub local_outer_rect: Option<LayoutRect>
+    pub local_outer_rect: Option<LayoutRect>,
+    pub only_rectangular_clips: bool,
+    pub has_image_or_line_decoration_clip: bool,
 }
 
 impl ClipSources {
     pub fn new(clips: Vec<ClipSource>) -> Self {
         let (local_inner_rect, local_outer_rect) = Self::calculate_inner_and_outer_rects(&clips);
 
+        let has_image_or_line_decoration_clip =
+            clips.iter().any(|clip| clip.is_image_or_line_decoration_clip());
+        let only_rectangular_clips =
+            !has_image_or_line_decoration_clip && clips.iter().all(|clip| clip.is_rect());
         let clips = clips
             .into_iter()
             .map(|clip| (clip, GpuCacheHandle::new()))
             .collect();
 
         ClipSources {
             clips,
             local_inner_rect,
             local_outer_rect,
+            only_rectangular_clips,
+            has_image_or_line_decoration_clip,
         }
     }
 
     pub fn clips(&self) -> &[(ClipSource, GpuCacheHandle)] {
         &self.clips
     }
 
     fn calculate_inner_and_outer_rects(clips: &Vec<ClipSource>) -> (LayoutRect, Option<LayoutRect>) {
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/clip_node.rs
@@ -0,0 +1,100 @@
+/* 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/. */
+
+use api::DevicePixelScale;
+use clip::{ClipChain, ClipChainNode, ClipSourcesHandle, ClipStore, ClipWorkItem};
+use clip_scroll_tree::{ClipChainIndex, SpatialNodeIndex};
+use gpu_cache::GpuCache;
+use resource_cache::ResourceCache;
+use spatial_node::SpatialNode;
+
+#[derive(Debug)]
+pub struct ClipNode {
+    /// The node that determines how this clip node is positioned.
+    pub spatial_node: SpatialNodeIndex,
+
+    /// A handle to this clip nodes clips in the ClipStore.
+    pub handle: Option<ClipSourcesHandle>,
+
+    /// An index to a ClipChain defined by this ClipNode's hiearchy in the display
+    /// list.
+    pub clip_chain_index: ClipChainIndex,
+
+    /// The index of the parent ClipChain of this node's hiearchical ClipChain.
+    pub parent_clip_chain_index: ClipChainIndex,
+
+    /// A copy of the ClipChainNode this node would produce. We need to keep a copy,
+    /// because the ClipChain may not contain our node if is optimized out, but API
+    /// defined ClipChains will still need to access it.
+    pub clip_chain_node: Option<ClipChainNode>,
+}
+
+impl ClipNode {
+    const EMPTY: ClipNode = ClipNode {
+        spatial_node: SpatialNodeIndex(0),
+        handle: None,
+        clip_chain_index: ClipChainIndex(0),
+        parent_clip_chain_index: ClipChainIndex(0),
+        clip_chain_node: None,
+    };
+
+    pub fn empty() -> ClipNode {
+        ClipNode::EMPTY
+    }
+
+    pub fn update(
+        &mut self,
+        spatial_node: &SpatialNode,
+        device_pixel_scale: DevicePixelScale,
+        clip_store: &mut ClipStore,
+        resource_cache: &mut ResourceCache,
+        gpu_cache: &mut GpuCache,
+        clip_chains: &mut [ClipChain],
+    ) {
+        let (clip_sources, weak_handle) = match self.handle {
+            Some(ref handle) => (clip_store.get_mut(handle), handle.weak()),
+            None => {
+                warn!("Tried to process an empty clip node");
+                return;
+            }
+        };
+        clip_sources.update(gpu_cache, resource_cache, device_pixel_scale);
+
+        let (screen_inner_rect, screen_outer_rect) = clip_sources.get_screen_bounds(
+            &spatial_node.world_content_transform,
+            device_pixel_scale,
+            None,
+        );
+
+        // All clipping SpatialNodes should have outer rectangles, because they never
+        // use the BorderCorner clip type and they always have at last one non-ClipOut
+        // Rectangle ClipSource.
+        let screen_outer_rect = screen_outer_rect
+            .expect("Clipping node didn't have outer rect.");
+        let local_outer_rect = clip_sources.local_outer_rect
+            .expect("Clipping node didn't have outer rect.");
+
+        let new_node = ClipChainNode {
+            work_item: ClipWorkItem {
+                transform_index: self.spatial_node.transform_index(),
+                clip_sources: weak_handle,
+                coordinate_system_id: spatial_node.coordinate_system_id,
+            },
+            local_clip_rect: spatial_node
+                .coordinate_system_relative_transform
+                .transform_rect(&local_outer_rect),
+            screen_outer_rect,
+            screen_inner_rect,
+            prev: None,
+        };
+
+        let mut clip_chain =
+            clip_chains[self.parent_clip_chain_index.0]
+            .new_with_added_node(&new_node);
+
+        self.clip_chain_node = Some(new_node);
+        clip_chain.parent_index = Some(self.parent_clip_chain_index);
+        clip_chains[self.clip_chain_index.0] = clip_chain;
+    }
+}
deleted file mode 100644
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ /dev/null
@@ -1,844 +0,0 @@
-/* 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/. */
-
-use api::{DevicePixelScale, ExternalScrollId, LayoutPixel, LayoutPoint, LayoutRect, LayoutSize};
-use api::{LayoutVector2D, LayoutTransform, PipelineId, PropertyBinding};
-use api::{ScrollClamping, ScrollLocation, ScrollSensitivity, StickyOffsetBounds};
-use clip::{ClipChain, ClipChainNode, ClipSourcesHandle, ClipStore, ClipWorkItem};
-use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
-use clip_scroll_tree::{TransformUpdateState, TransformIndex};
-use euclid::SideOffsets2D;
-use gpu_cache::GpuCache;
-use gpu_types::{TransformData, TransformPalette};
-use resource_cache::ResourceCache;
-use scene::SceneProperties;
-use util::{LayoutToWorldFastTransform, LayoutFastTransform};
-use util::{TransformedRectKind};
-
-#[derive(Debug)]
-pub struct StickyFrameInfo {
-    pub frame_rect: LayoutRect,
-    pub margins: SideOffsets2D<Option<f32>>,
-    pub vertical_offset_bounds: StickyOffsetBounds,
-    pub horizontal_offset_bounds: StickyOffsetBounds,
-    pub previously_applied_offset: LayoutVector2D,
-    pub current_offset: LayoutVector2D,
-}
-
-impl StickyFrameInfo {
-    pub fn new(
-        frame_rect: LayoutRect,
-        margins: SideOffsets2D<Option<f32>>,
-        vertical_offset_bounds: StickyOffsetBounds,
-        horizontal_offset_bounds: StickyOffsetBounds,
-        previously_applied_offset: LayoutVector2D
-    ) -> StickyFrameInfo {
-        StickyFrameInfo {
-            frame_rect,
-            margins,
-            vertical_offset_bounds,
-            horizontal_offset_bounds,
-            previously_applied_offset,
-            current_offset: LayoutVector2D::zero(),
-        }
-    }
-}
-
-#[derive(Debug)]
-pub enum SpatialNodeKind {
-    /// A special kind of node that adjusts its position based on the position
-    /// of its parent node and a given set of sticky positioning offset bounds.
-    /// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here:
-    /// https://www.w3.org/TR/css-position-3/#sticky-pos
-    StickyFrame(StickyFrameInfo),
-
-    /// Transforms it's content, but doesn't clip it. Can also be adjusted
-    /// by scroll events or setting scroll offsets.
-    ScrollFrame(ScrollFrameInfo),
-
-    /// A reference frame establishes a new coordinate space in the tree.
-    ReferenceFrame(ReferenceFrameInfo),
-}
-
-#[derive(Debug)]
-pub enum NodeType {
-    Spatial {
-        kind: SpatialNodeKind,
-    },
-
-    /// Other nodes just do clipping, but no transformation.
-    Clip {
-        handle: ClipSourcesHandle,
-        clip_chain_index: ClipChainIndex,
-
-        /// A copy of the ClipChainNode this node would produce. We need to keep a copy,
-        /// because the ClipChain may not contain our node if is optimized out, but API
-        /// defined ClipChains will still need to access it.
-        clip_chain_node: Option<ClipChainNode>,
-    },
-
-    /// An empty node, used to pad the ClipScrollTree's array of nodes so that
-    /// we can immediately use each assigned ClipScrollNodeIndex. After display
-    /// list flattening this node type should never be used.
-    Empty,
-}
-
-impl NodeType {
-    fn is_reference_frame(&self) -> bool {
-        match *self {
-            NodeType::Spatial { kind: SpatialNodeKind::ReferenceFrame(_), .. } => true,
-            _ => false,
-        }
-    }
-}
-
-/// Contains information common among all types of ClipScrollTree nodes.
-#[derive(Debug)]
-pub struct ClipScrollNode {
-    /// The transformation for this viewport in world coordinates is the transformation for
-    /// our parent reference frame, plus any accumulated scrolling offsets from nodes
-    /// between our reference frame and this node. For reference frames, we also include
-    /// whatever local transformation this reference frame provides.
-    pub world_viewport_transform: LayoutToWorldFastTransform,
-
-    /// World transform for content transformed by this node.
-    pub world_content_transform: LayoutToWorldFastTransform,
-
-    /// The current transform kind of world_content_transform.
-    pub transform_kind: TransformedRectKind,
-
-    /// Pipeline that this layer belongs to
-    pub pipeline_id: PipelineId,
-
-    /// Parent layer. If this is None, we are the root node.
-    pub parent: Option<ClipScrollNodeIndex>,
-
-    /// Child layers
-    pub children: Vec<ClipScrollNodeIndex>,
-
-    /// The type of this node and any data associated with that node type.
-    pub node_type: NodeType,
-
-    /// True if this node is transformed by an invertible transform.  If not, display items
-    /// transformed by this node will not be displayed and display items not transformed by this
-    /// node will not be clipped by clips that are transformed by this node.
-    pub invertible: bool,
-
-    /// The axis-aligned coordinate system id of this node.
-    pub coordinate_system_id: CoordinateSystemId,
-
-    /// The transformation from the coordinate system which established our compatible coordinate
-    /// system (same coordinate system id) and us. This can change via scroll offsets and via new
-    /// reference frame transforms.
-    pub coordinate_system_relative_transform: LayoutFastTransform,
-
-    /// The index of the spatial node that provides positioning information for this node.
-    /// For reference frames, scroll and sticky frames it is a unique identfier.
-    /// For clip nodes, this is the nearest ancestor spatial node.
-    pub transform_index: TransformIndex,
-}
-
-impl ClipScrollNode {
-    pub fn new(
-        pipeline_id: PipelineId,
-        parent_index: Option<ClipScrollNodeIndex>,
-        node_type: NodeType,
-        transform_index: TransformIndex,
-    ) -> Self {
-        ClipScrollNode {
-            world_viewport_transform: LayoutToWorldFastTransform::identity(),
-            world_content_transform: LayoutToWorldFastTransform::identity(),
-            transform_kind: TransformedRectKind::AxisAligned,
-            parent: parent_index,
-            children: Vec::new(),
-            pipeline_id,
-            node_type,
-            invertible: true,
-            coordinate_system_id: CoordinateSystemId(0),
-            coordinate_system_relative_transform: LayoutFastTransform::identity(),
-            transform_index,
-        }
-    }
-
-    pub fn empty() -> ClipScrollNode {
-        Self::new(
-            PipelineId::dummy(),
-            None,
-            NodeType::Empty,
-            TransformIndex(0),
-        )
-    }
-
-    pub fn new_scroll_frame(
-        pipeline_id: PipelineId,
-        parent_index: ClipScrollNodeIndex,
-        external_id: Option<ExternalScrollId>,
-        frame_rect: &LayoutRect,
-        content_size: &LayoutSize,
-        scroll_sensitivity: ScrollSensitivity,
-        transform_index: TransformIndex,
-    ) -> Self {
-        let node_type = NodeType::Spatial {
-            kind: SpatialNodeKind::ScrollFrame(ScrollFrameInfo::new(
-                *frame_rect,
-                scroll_sensitivity,
-                LayoutSize::new(
-                    (content_size.width - frame_rect.size.width).max(0.0),
-                    (content_size.height - frame_rect.size.height).max(0.0)
-                ),
-                external_id,
-            )),
-        };
-
-        Self::new(
-            pipeline_id,
-            Some(parent_index),
-            node_type,
-            transform_index,
-        )
-    }
-
-    pub fn new_reference_frame(
-        parent_index: Option<ClipScrollNodeIndex>,
-        source_transform: Option<PropertyBinding<LayoutTransform>>,
-        source_perspective: Option<LayoutTransform>,
-        origin_in_parent_reference_frame: LayoutVector2D,
-        pipeline_id: PipelineId,
-        transform_index: TransformIndex,
-    ) -> Self {
-        let identity = LayoutTransform::identity();
-        let source_perspective = source_perspective.map_or_else(
-            LayoutFastTransform::identity, |perspective| perspective.into());
-        let info = ReferenceFrameInfo {
-            resolved_transform: LayoutFastTransform::identity(),
-            source_transform: source_transform.unwrap_or(PropertyBinding::Value(identity)),
-            source_perspective,
-            origin_in_parent_reference_frame,
-            invertible: true,
-        };
-        Self::new(
-            pipeline_id,
-            parent_index,
-            NodeType::Spatial {
-                kind: SpatialNodeKind::ReferenceFrame(info),
-            },
-            transform_index,
-        )
-    }
-
-    pub fn new_sticky_frame(
-        parent_index: ClipScrollNodeIndex,
-        sticky_frame_info: StickyFrameInfo,
-        pipeline_id: PipelineId,
-        transform_index: TransformIndex,
-    ) -> Self {
-        let node_type = NodeType::Spatial {
-            kind: SpatialNodeKind::StickyFrame(sticky_frame_info),
-        };
-        Self::new(
-            pipeline_id,
-            Some(parent_index),
-            node_type,
-            transform_index,
-        )
-    }
-
-
-    pub fn add_child(&mut self, child: ClipScrollNodeIndex) {
-        self.children.push(child);
-    }
-
-    pub fn apply_old_scrolling_state(&mut self, old_scroll_info: &ScrollFrameInfo) {
-        match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(ref mut scrolling), .. } => {
-                *scrolling = scrolling.combine_with_old_scroll_info(old_scroll_info);
-            }
-            _ if old_scroll_info.offset != LayoutVector2D::zero() => {
-                warn!("Tried to scroll a non-scroll node.")
-            }
-            _ => {}
-        }
-    }
-
-    pub fn set_scroll_origin(&mut self, origin: &LayoutPoint, clamp: ScrollClamping) -> bool {
-        let scrollable_size = self.scrollable_size();
-        let scrollable_width = scrollable_size.width;
-        let scrollable_height = scrollable_size.height;
-
-        let scrolling = match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(ref mut scrolling), .. } => scrolling,
-            _ => {
-                warn!("Tried to scroll a non-scroll node.");
-                return false;
-            }
-        };
-
-        let new_offset = match clamp {
-            ScrollClamping::ToContentBounds => {
-                if scrollable_height <= 0. && scrollable_width <= 0. {
-                    return false;
-                }
-
-                let origin = LayoutPoint::new(origin.x.max(0.0), origin.y.max(0.0));
-                LayoutVector2D::new(
-                    (-origin.x).max(-scrollable_width).min(0.0).round(),
-                    (-origin.y).max(-scrollable_height).min(0.0).round(),
-                )
-            }
-            ScrollClamping::NoClamping => LayoutPoint::zero() - *origin,
-        };
-
-        if new_offset == scrolling.offset {
-            return false;
-        }
-
-        scrolling.offset = new_offset;
-        true
-    }
-
-    pub fn mark_uninvertible(&mut self) {
-        self.invertible = false;
-        self.world_content_transform = LayoutToWorldFastTransform::identity();
-        self.world_viewport_transform = LayoutToWorldFastTransform::identity();
-    }
-
-    pub fn push_gpu_data(
-        &mut self,
-        transform_palette: &mut TransformPalette,
-    ) {
-        if let NodeType::Spatial { .. } = self.node_type {
-            if !self.invertible {
-                transform_palette.set(self.transform_index, TransformData::invalid());
-                return;
-            }
-
-            let inv_transform = match self.world_content_transform.inverse() {
-                Some(inverted) => inverted.to_transform(),
-                None => {
-                    transform_palette.set(self.transform_index, TransformData::invalid());
-                    return;
-                }
-            };
-
-            let data = TransformData {
-                transform: self.world_content_transform.into(),
-                inv_transform,
-            };
-
-            // Write the data that will be made available to the GPU for this node.
-            transform_palette.set(self.transform_index, data);
-        }
-    }
-
-    pub fn update(
-        &mut self,
-        state: &mut TransformUpdateState,
-        next_coordinate_system_id: &mut CoordinateSystemId,
-        device_pixel_scale: DevicePixelScale,
-        clip_store: &mut ClipStore,
-        resource_cache: &mut ResourceCache,
-        gpu_cache: &mut GpuCache,
-        scene_properties: &SceneProperties,
-        clip_chains: &mut Vec<ClipChain>,
-    ) {
-        // If any of our parents was not rendered, we are not rendered either and can just
-        // quit here.
-        if !state.invertible {
-            self.mark_uninvertible();
-            return;
-        }
-
-        self.update_transform(state, next_coordinate_system_id, scene_properties);
-
-        self.transform_kind = if self.world_content_transform.preserves_2d_axis_alignment() {
-            TransformedRectKind::AxisAligned
-        } else {
-            TransformedRectKind::Complex
-        };
-
-        // If this node is a reference frame, we check if it has a non-invertible matrix.
-        // For non-reference-frames we assume that they will produce only additional
-        // translations which should be invertible.
-        match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ReferenceFrame(info), .. } if !info.invertible => {
-                self.mark_uninvertible();
-                return;
-            }
-            _ => self.invertible = true,
-        }
-
-        self.update_clip_work_item(
-            state,
-            device_pixel_scale,
-            clip_store,
-            resource_cache,
-            gpu_cache,
-            clip_chains,
-        );
-    }
-
-    pub fn update_clip_work_item(
-        &mut self,
-        state: &mut TransformUpdateState,
-        device_pixel_scale: DevicePixelScale,
-        clip_store: &mut ClipStore,
-        resource_cache: &mut ResourceCache,
-        gpu_cache: &mut GpuCache,
-        clip_chains: &mut [ClipChain],
-    ) {
-        let (clip_sources_handle, clip_chain_index, stored_clip_chain_node) = match self.node_type {
-            NodeType::Clip { ref handle, clip_chain_index, ref mut clip_chain_node, .. } =>
-                (handle, clip_chain_index, clip_chain_node),
-            _ => {
-                self.invertible = true;
-                return;
-            }
-        };
-
-        let clip_sources = clip_store.get_mut(clip_sources_handle);
-        clip_sources.update(
-            gpu_cache,
-            resource_cache,
-            device_pixel_scale,
-        );
-
-        let (screen_inner_rect, screen_outer_rect) = clip_sources.get_screen_bounds(
-            &self.world_viewport_transform,
-            device_pixel_scale,
-            None,
-        );
-
-        // All clipping ClipScrollNodes should have outer rectangles, because they never
-        // use the BorderCorner clip type and they always have at last one non-ClipOut
-        // Rectangle ClipSource.
-        let screen_outer_rect = screen_outer_rect
-            .expect("Clipping node didn't have outer rect.");
-        let local_outer_rect = clip_sources.local_outer_rect
-            .expect("Clipping node didn't have outer rect.");
-
-        let new_node = ClipChainNode {
-            work_item: ClipWorkItem {
-                transform_index: self.transform_index,
-                clip_sources: clip_sources_handle.weak(),
-                coordinate_system_id: state.current_coordinate_system_id,
-            },
-            local_clip_rect:
-                self.coordinate_system_relative_transform.transform_rect(&local_outer_rect),
-            screen_outer_rect,
-            screen_inner_rect,
-            prev: None,
-        };
-
-        let mut clip_chain =
-            clip_chains[state.parent_clip_chain_index.0].new_with_added_node(&new_node);
-
-        *stored_clip_chain_node = Some(new_node);
-        clip_chain.parent_index = Some(state.parent_clip_chain_index);
-        clip_chains[clip_chain_index.0] = clip_chain;
-        state.parent_clip_chain_index = clip_chain_index;
-    }
-
-    pub fn update_transform(
-        &mut self,
-        state: &mut TransformUpdateState,
-        next_coordinate_system_id: &mut CoordinateSystemId,
-        scene_properties: &SceneProperties,
-    ) {
-        if self.node_type.is_reference_frame() {
-            self.update_transform_for_reference_frame(
-                state,
-                next_coordinate_system_id,
-                scene_properties
-            );
-            return;
-        }
-
-        // We calculate this here to avoid a double-borrow later.
-        let sticky_offset = self.calculate_sticky_offset(
-            &state.nearest_scrolling_ancestor_offset,
-            &state.nearest_scrolling_ancestor_viewport,
-        );
-
-        // The transformation for the bounds of our viewport is the parent reference frame
-        // transform, plus any accumulated scroll offset from our parents, plus any offset
-        // provided by our own sticky positioning.
-        let accumulated_offset = state.parent_accumulated_scroll_offset + sticky_offset;
-        self.world_viewport_transform = if accumulated_offset != LayoutVector2D::zero() {
-            state.parent_reference_frame_transform.pre_translate(&accumulated_offset)
-        } else {
-            state.parent_reference_frame_transform
-        };
-
-        // The transformation for any content inside of us is the viewport transformation, plus
-        // whatever scrolling offset we supply as well.
-        let scroll_offset = self.scroll_offset();
-        self.world_content_transform = if scroll_offset != LayoutVector2D::zero() {
-            self.world_viewport_transform.pre_translate(&scroll_offset)
-        } else {
-            self.world_viewport_transform
-        };
-
-        let added_offset = state.parent_accumulated_scroll_offset + sticky_offset + scroll_offset;
-        self.coordinate_system_relative_transform =
-            state.coordinate_system_relative_transform.offset(added_offset);
-
-        if let NodeType::Spatial { kind: SpatialNodeKind::StickyFrame(ref mut info), .. } = self.node_type {
-            info.current_offset = sticky_offset;
-        }
-
-        self.coordinate_system_id = state.current_coordinate_system_id;
-    }
-
-    pub fn update_transform_for_reference_frame(
-        &mut self,
-        state: &mut TransformUpdateState,
-        next_coordinate_system_id: &mut CoordinateSystemId,
-        scene_properties: &SceneProperties,
-    ) {
-        let info = match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ReferenceFrame(ref mut info), .. } => info,
-            _ => unreachable!("Called update_transform_for_reference_frame on non-ReferenceFrame"),
-        };
-
-        // Resolve the transform against any property bindings.
-        let source_transform = scene_properties.resolve_layout_transform(&info.source_transform);
-        info.resolved_transform =
-            LayoutFastTransform::with_vector(info.origin_in_parent_reference_frame)
-            .pre_mul(&source_transform.into())
-            .pre_mul(&info.source_perspective);
-
-        // The transformation for this viewport in world coordinates is the transformation for
-        // our parent reference frame, plus any accumulated scrolling offsets from nodes
-        // between our reference frame and this node. Finally, we also include
-        // whatever local transformation this reference frame provides.
-        let relative_transform = info.resolved_transform
-            .post_translate(state.parent_accumulated_scroll_offset)
-            .to_transform()
-            .with_destination::<LayoutPixel>();
-        self.world_viewport_transform =
-            state.parent_reference_frame_transform.pre_mul(&relative_transform.into());
-        self.world_content_transform = self.world_viewport_transform;
-
-        info.invertible = self.world_viewport_transform.is_invertible();
-        if !info.invertible {
-            return;
-        }
-
-        // Try to update our compatible coordinate system transform. If we cannot, start a new
-        // incompatible coordinate system.
-        match state.coordinate_system_relative_transform.update(relative_transform) {
-            Some(offset) => self.coordinate_system_relative_transform = offset,
-            None => {
-                self.coordinate_system_relative_transform = LayoutFastTransform::identity();
-                state.current_coordinate_system_id = *next_coordinate_system_id;
-                next_coordinate_system_id.advance();
-            }
-        }
-
-        self.coordinate_system_id = state.current_coordinate_system_id;
-    }
-
-    fn calculate_sticky_offset(
-        &self,
-        viewport_scroll_offset: &LayoutVector2D,
-        viewport_rect: &LayoutRect,
-    ) -> LayoutVector2D {
-        let info = match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::StickyFrame(ref info), .. } => info,
-            _ => return LayoutVector2D::zero(),
-        };
-
-        if info.margins.top.is_none() && info.margins.bottom.is_none() &&
-            info.margins.left.is_none() && info.margins.right.is_none() {
-            return LayoutVector2D::zero();
-        }
-
-        // The viewport and margins of the item establishes the maximum amount that it can
-        // be offset in order to keep it on screen. Since we care about the relationship
-        // between the scrolled content and unscrolled viewport we adjust the viewport's
-        // position by the scroll offset in order to work with their relative positions on the
-        // page.
-        let sticky_rect = info.frame_rect.translate(viewport_scroll_offset);
-
-        let mut sticky_offset = LayoutVector2D::zero();
-        if let Some(margin) = info.margins.top {
-            let top_viewport_edge = viewport_rect.min_y() + margin;
-            if sticky_rect.min_y() < top_viewport_edge {
-                // If the sticky rect is positioned above the top edge of the viewport (plus margin)
-                // we move it down so that it is fully inside the viewport.
-                sticky_offset.y = top_viewport_edge - sticky_rect.min_y();
-            } else if info.previously_applied_offset.y > 0.0 &&
-                sticky_rect.min_y() > top_viewport_edge {
-                // However, if the sticky rect is positioned *below* the top edge of the viewport
-                // and there is already some offset applied to the sticky rect's position, then
-                // we need to move it up so that it remains at the correct position. This
-                // makes sticky_offset.y negative and effectively reduces the amount of the
-                // offset that was already applied. We limit the reduction so that it can, at most,
-                // cancel out the already-applied offset, but should never end up adjusting the
-                // position the other way.
-                sticky_offset.y = top_viewport_edge - sticky_rect.min_y();
-                sticky_offset.y = sticky_offset.y.max(-info.previously_applied_offset.y);
-            }
-            debug_assert!(sticky_offset.y + info.previously_applied_offset.y >= 0.0);
-        }
-
-        // If we don't have a sticky-top offset (sticky_offset.y + info.previously_applied_offset.y
-        // == 0), or if we have a previously-applied bottom offset (previously_applied_offset.y < 0)
-        // then we check for handling the bottom margin case.
-        if sticky_offset.y + info.previously_applied_offset.y <= 0.0 {
-            if let Some(margin) = info.margins.bottom {
-                // Same as the above case, but inverted for bottom-sticky items. Here
-                // we adjust items upwards, resulting in a negative sticky_offset.y,
-                // or reduce the already-present upward adjustment, resulting in a positive
-                // sticky_offset.y.
-                let bottom_viewport_edge = viewport_rect.max_y() - margin;
-                if sticky_rect.max_y() > bottom_viewport_edge {
-                    sticky_offset.y = bottom_viewport_edge - sticky_rect.max_y();
-                } else if info.previously_applied_offset.y < 0.0 &&
-                    sticky_rect.max_y() < bottom_viewport_edge {
-                    sticky_offset.y = bottom_viewport_edge - sticky_rect.max_y();
-                    sticky_offset.y = sticky_offset.y.min(-info.previously_applied_offset.y);
-                }
-                debug_assert!(sticky_offset.y + info.previously_applied_offset.y <= 0.0);
-            }
-        }
-
-        // Same as above, but for the x-axis.
-        if let Some(margin) = info.margins.left {
-            let left_viewport_edge = viewport_rect.min_x() + margin;
-            if sticky_rect.min_x() < left_viewport_edge {
-                sticky_offset.x = left_viewport_edge - sticky_rect.min_x();
-            } else if info.previously_applied_offset.x > 0.0 &&
-                sticky_rect.min_x() > left_viewport_edge {
-                sticky_offset.x = left_viewport_edge - sticky_rect.min_x();
-                sticky_offset.x = sticky_offset.x.max(-info.previously_applied_offset.x);
-            }
-            debug_assert!(sticky_offset.x + info.previously_applied_offset.x >= 0.0);
-        }
-
-        if sticky_offset.x + info.previously_applied_offset.x <= 0.0 {
-            if let Some(margin) = info.margins.right {
-                let right_viewport_edge = viewport_rect.max_x() - margin;
-                if sticky_rect.max_x() > right_viewport_edge {
-                    sticky_offset.x = right_viewport_edge - sticky_rect.max_x();
-                } else if info.previously_applied_offset.x < 0.0 &&
-                    sticky_rect.max_x() < right_viewport_edge {
-                    sticky_offset.x = right_viewport_edge - sticky_rect.max_x();
-                    sticky_offset.x = sticky_offset.x.min(-info.previously_applied_offset.x);
-                }
-                debug_assert!(sticky_offset.x + info.previously_applied_offset.x <= 0.0);
-            }
-        }
-
-        // The total "sticky offset" (which is the sum that was already applied by
-        // the calling code, stored in info.previously_applied_offset, and the extra amount we
-        // computed as a result of scrolling, stored in sticky_offset) needs to be
-        // clamped to the provided bounds.
-        let clamp_adjusted = |value: f32, adjust: f32, bounds: &StickyOffsetBounds| {
-            (value + adjust).max(bounds.min).min(bounds.max) - adjust
-        };
-        sticky_offset.y = clamp_adjusted(sticky_offset.y,
-                                         info.previously_applied_offset.y,
-                                         &info.vertical_offset_bounds);
-        sticky_offset.x = clamp_adjusted(sticky_offset.x,
-                                         info.previously_applied_offset.x,
-                                         &info.horizontal_offset_bounds);
-
-        sticky_offset
-    }
-
-    pub fn prepare_state_for_children(&self, state: &mut TransformUpdateState) {
-        if !self.invertible {
-            state.invertible = false;
-            return;
-        }
-
-        // The transformation we are passing is the transformation of the parent
-        // reference frame and the offset is the accumulated offset of all the nodes
-        // between us and the parent reference frame. If we are a reference frame,
-        // we need to reset both these values.
-        match self.node_type {
-            NodeType::Spatial { ref kind, .. } => {
-                match *kind {
-                    SpatialNodeKind::StickyFrame(ref info) => {
-                        // We don't translate the combined rect by the sticky offset, because sticky
-                        // offsets actually adjust the node position itself, whereas scroll offsets
-                        // only apply to contents inside the node.
-                        state.parent_accumulated_scroll_offset =
-                            info.current_offset + state.parent_accumulated_scroll_offset;
-                    }
-                    SpatialNodeKind::ScrollFrame(ref scrolling) => {
-                        state.parent_accumulated_scroll_offset =
-                            scrolling.offset + state.parent_accumulated_scroll_offset;
-                        state.nearest_scrolling_ancestor_offset = scrolling.offset;
-                        state.nearest_scrolling_ancestor_viewport = scrolling.viewport_rect;
-                    }
-                    SpatialNodeKind::ReferenceFrame(ref info) => {
-                        state.parent_reference_frame_transform = self.world_viewport_transform;
-                        state.parent_accumulated_scroll_offset = LayoutVector2D::zero();
-                        state.coordinate_system_relative_transform =
-                            self.coordinate_system_relative_transform.clone();
-                        let translation = -info.origin_in_parent_reference_frame;
-                        state.nearest_scrolling_ancestor_viewport =
-                            state.nearest_scrolling_ancestor_viewport
-                               .translate(&translation);
-                    }
-                }
-            }
-            NodeType::Clip{ .. } => { }
-            NodeType::Empty => unreachable!("Empty node remaining in ClipScrollTree."),
-        }
-    }
-
-    pub fn scrollable_size(&self) -> LayoutSize {
-        match self.node_type {
-           NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(state), .. } => state.scrollable_size,
-            _ => LayoutSize::zero(),
-        }
-    }
-
-    pub fn scroll(&mut self, scroll_location: ScrollLocation) -> bool {
-        let scrolling = match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(ref mut scrolling), .. } => scrolling,
-            _ => return false,
-        };
-
-        let delta = match scroll_location {
-            ScrollLocation::Delta(delta) => delta,
-            ScrollLocation::Start => {
-                if scrolling.offset.y.round() >= 0.0 {
-                    // Nothing to do on this layer.
-                    return false;
-                }
-
-                scrolling.offset.y = 0.0;
-                return true;
-            }
-            ScrollLocation::End => {
-                let end_pos = -scrolling.scrollable_size.height;
-                if scrolling.offset.y.round() <= end_pos {
-                    // Nothing to do on this layer.
-                    return false;
-                }
-
-                scrolling.offset.y = end_pos;
-                return true;
-            }
-        };
-
-        let scrollable_width = scrolling.scrollable_size.width;
-        let scrollable_height = scrolling.scrollable_size.height;
-        let original_layer_scroll_offset = scrolling.offset;
-
-        if scrollable_width > 0. {
-            scrolling.offset.x = (scrolling.offset.x + delta.x)
-                .min(0.0)
-                .max(-scrollable_width)
-                .round();
-        }
-
-        if scrollable_height > 0. {
-            scrolling.offset.y = (scrolling.offset.y + delta.y)
-                .min(0.0)
-                .max(-scrollable_height)
-                .round();
-        }
-
-        scrolling.offset != original_layer_scroll_offset
-    }
-
-    pub fn scroll_offset(&self) -> LayoutVector2D {
-        match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(ref scrolling), .. } => scrolling.offset,
-            _ => LayoutVector2D::zero(),
-        }
-    }
-
-    pub fn matches_external_id(&self, external_id: ExternalScrollId) -> bool {
-        match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(info), .. } if info.external_id == Some(external_id) => true,
-            _ => false,
-        }
-    }
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct ScrollFrameInfo {
-    /// The rectangle of the viewport of this scroll frame. This is important for
-    /// positioning of items inside child StickyFrames.
-    pub viewport_rect: LayoutRect,
-
-    pub offset: LayoutVector2D,
-    pub scroll_sensitivity: ScrollSensitivity,
-
-    /// Amount that this ScrollFrame can scroll in both directions.
-    pub scrollable_size: LayoutSize,
-
-    /// An external id to identify this scroll frame to API clients. This
-    /// allows setting scroll positions via the API without relying on ClipsIds
-    /// which may change between frames.
-    pub external_id: Option<ExternalScrollId>,
-
-}
-
-/// Manages scrolling offset.
-impl ScrollFrameInfo {
-    pub fn new(
-        viewport_rect: LayoutRect,
-        scroll_sensitivity: ScrollSensitivity,
-        scrollable_size: LayoutSize,
-        external_id: Option<ExternalScrollId>,
-    ) -> ScrollFrameInfo {
-        ScrollFrameInfo {
-            viewport_rect,
-            offset: LayoutVector2D::zero(),
-            scroll_sensitivity,
-            scrollable_size,
-            external_id,
-        }
-    }
-
-    pub fn sensitive_to_input_events(&self) -> bool {
-        match self.scroll_sensitivity {
-            ScrollSensitivity::ScriptAndInputEvents => true,
-            ScrollSensitivity::Script => false,
-        }
-    }
-
-    pub fn combine_with_old_scroll_info(
-        self,
-        old_scroll_info: &ScrollFrameInfo
-    ) -> ScrollFrameInfo {
-        ScrollFrameInfo {
-            viewport_rect: self.viewport_rect,
-            offset: old_scroll_info.offset,
-            scroll_sensitivity: self.scroll_sensitivity,
-            scrollable_size: self.scrollable_size,
-            external_id: self.external_id,
-        }
-    }
-}
-
-/// Contains information about reference frames.
-#[derive(Copy, Clone, Debug)]
-pub struct ReferenceFrameInfo {
-    /// The transformation that establishes this reference frame, relative to the parent
-    /// reference frame. The origin of the reference frame is included in the transformation.
-    pub resolved_transform: LayoutFastTransform,
-
-    /// The source transform and perspective matrices provided by the stacking context
-    /// that forms this reference frame. We maintain the property binding information
-    /// here so that we can resolve the animated transform and update the tree each
-    /// frame.
-    pub source_transform: PropertyBinding<LayoutTransform>,
-    pub source_perspective: LayoutFastTransform,
-
-    /// The original, not including the transform and relative to the parent reference frame,
-    /// origin of this reference frame. This is already rolled into the `transform' property, but
-    /// we also store it here to properly transform the viewport for sticky positioning.
-    pub origin_in_parent_reference_frame: LayoutVector2D,
-
-    /// True if the resolved transform is invertible.
-    pub invertible: bool,
-}
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,50 +1,51 @@
 /* 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/. */
 
 use api::{DeviceIntRect, DevicePixelScale, ExternalScrollId, LayoutPoint, LayoutRect, LayoutVector2D};
 use api::{PipelineId, ScrollClamping, ScrollLocation, ScrollNodeState};
 use api::{LayoutSize, LayoutTransform, PropertyBinding, ScrollSensitivity, WorldPoint};
 use clip::{ClipChain, ClipSourcesHandle, ClipStore};
-use clip_scroll_node::{ClipScrollNode, NodeType, SpatialNodeKind, ScrollFrameInfo, StickyFrameInfo};
+use clip_node::ClipNode;
 use gpu_cache::GpuCache;
-use gpu_types::TransformPalette;
+use gpu_types::{TransformIndex, TransformPalette};
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
 use resource_cache::ResourceCache;
 use scene::SceneProperties;
+use spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo};
 use util::{LayoutFastTransform, LayoutToWorldFastTransform};
 
 pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
 
 /// An id that identifies coordinate systems in the ClipScrollTree. Each
 /// coordinate system has an id and those ids will be shared when the coordinates
 /// system are the same or are in the same axis-aligned space. This allows
 /// for optimizing mask generation.
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CoordinateSystemId(pub u32);
 
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
-pub struct ClipScrollNodeIndex(pub usize);
+pub struct SpatialNodeIndex(pub usize);
+
+#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
+pub struct ClipNodeIndex(pub usize);
 
-// Used to index the smaller subset of nodes in the CST that define
-// new transform / positioning.
-// TODO(gw): In the future if we split the CST into a positioning and
-//           clipping tree, this can be tidied up a bit.
-#[derive(Copy, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct TransformIndex(pub u32);
+impl SpatialNodeIndex {
+    pub fn transform_index(&self) -> TransformIndex {
+        TransformIndex(self.0 as u32)
+    }
+}
 
-const ROOT_REFERENCE_FRAME_INDEX: ClipScrollNodeIndex = ClipScrollNodeIndex(0);
-const TOPMOST_SCROLL_NODE_INDEX: ClipScrollNodeIndex = ClipScrollNodeIndex(1);
+const ROOT_REFERENCE_FRAME_INDEX: SpatialNodeIndex = SpatialNodeIndex(0);
+const TOPMOST_SCROLL_NODE_INDEX: SpatialNodeIndex = SpatialNodeIndex(1);
 
 impl CoordinateSystemId {
     pub fn root() -> Self {
         CoordinateSystemId(0)
     }
 
     pub fn next(&self) -> Self {
         let CoordinateSystemId(id) = *self;
@@ -54,55 +55,53 @@ impl CoordinateSystemId {
     pub fn advance(&mut self) {
         self.0 += 1;
     }
 }
 
 pub struct ClipChainDescriptor {
     pub index: ClipChainIndex,
     pub parent: Option<ClipChainIndex>,
-    pub clips: Vec<ClipScrollNodeIndex>,
+    pub clips: Vec<ClipNodeIndex>,
 }
 
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub struct ClipChainIndex(pub usize);
 
 pub struct ClipScrollTree {
-    pub nodes: Vec<ClipScrollNode>,
+    /// Nodes which determine the positions (offsets and transforms) for primitives
+    /// and clips.
+    pub spatial_nodes: Vec<SpatialNode>,
+
+    /// Nodes which clip primitives.
+    pub clip_nodes: Vec<ClipNode>,
 
     /// A Vec of all descriptors that describe ClipChains in the order in which they are
     /// encountered during display list flattening. ClipChains are expected to never be
     /// the children of ClipChains later in the list.
     pub clip_chains_descriptors: Vec<ClipChainDescriptor>,
 
     /// A vector of all ClipChains in this ClipScrollTree including those from
     /// ClipChainDescriptors and also those defined by the clipping node hierarchy.
     pub clip_chains: Vec<ClipChain>,
 
     pub pending_scroll_offsets: FastHashMap<ExternalScrollId, (LayoutPoint, ScrollClamping)>,
 
     /// A set of pipelines which should be discarded the next time this
     /// tree is drained.
     pub pipelines_to_discard: FastHashSet<PipelineId>,
-
-    /// The number of nodes in the CST that are spatial. Currently, this is all
-    /// nodes that are not clip nodes.
-    spatial_node_count: usize,
 }
 
 #[derive(Clone)]
 pub struct TransformUpdateState {
     pub parent_reference_frame_transform: LayoutToWorldFastTransform,
     pub parent_accumulated_scroll_offset: LayoutVector2D,
     pub nearest_scrolling_ancestor_offset: LayoutVector2D,
     pub nearest_scrolling_ancestor_viewport: LayoutRect,
 
-    /// The index of the current parent's clip chain.
-    pub parent_clip_chain_index: ClipChainIndex,
-
     /// An id for keeping track of the axis-aligned space of this node. This is used in
     /// order to to track what kinds of clip optimizations can be done for a particular
     /// display list item, since optimizations can usually only be done among
     /// coordinate systems which are relatively axis aligned.
     pub current_coordinate_system_id: CoordinateSystemId,
 
     /// Transform from the coordinate system that started this compatible coordinate system.
     pub coordinate_system_relative_transform: LayoutFastTransform,
@@ -111,461 +110,433 @@ pub struct TransformUpdateState {
     /// transformed by this node will not be displayed and display items not transformed by this
     /// node will not be clipped by clips that are transformed by this node.
     pub invertible: bool,
 }
 
 impl ClipScrollTree {
     pub fn new() -> Self {
         ClipScrollTree {
-            nodes: Vec::new(),
+            spatial_nodes: Vec::new(),
+            clip_nodes: Vec::new(),
             clip_chains_descriptors: Vec::new(),
             clip_chains: vec![ClipChain::empty(&DeviceIntRect::zero())],
             pending_scroll_offsets: FastHashMap::default(),
             pipelines_to_discard: FastHashSet::default(),
-            spatial_node_count: 0,
         }
     }
 
     /// The root reference frame, which is the true root of the ClipScrollTree. Initially
-    /// this ID is not valid, which is indicated by ```nodes``` being empty.
-    pub fn root_reference_frame_index(&self) -> ClipScrollNodeIndex {
+    /// this ID is not valid, which is indicated by ```spatial_nodes``` being empty.
+    pub fn root_reference_frame_index(&self) -> SpatialNodeIndex {
         // TODO(mrobinson): We should eventually make this impossible to misuse.
-        debug_assert!(!self.nodes.is_empty());
+        debug_assert!(!self.spatial_nodes.is_empty());
         ROOT_REFERENCE_FRAME_INDEX
     }
 
     /// The root scroll node which is the first child of the root reference frame.
-    /// Initially this ID is not valid, which is indicated by ```nodes``` being empty.
-    pub fn topmost_scroll_node_index(&self) -> ClipScrollNodeIndex {
+    /// Initially this ID is not valid, which is indicated by ```spatial_nodes``` being empty.
+    pub fn topmost_scroll_node_index(&self) -> SpatialNodeIndex {
         // TODO(mrobinson): We should eventually make this impossible to misuse.
-        debug_assert!(self.nodes.len() >= 1);
+        debug_assert!(self.spatial_nodes.len() >= 1);
         TOPMOST_SCROLL_NODE_INDEX
     }
 
     pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
         let mut result = vec![];
-        for node in &self.nodes {
-            if let NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(info), .. } = node.node_type {
+        for node in &self.spatial_nodes {
+            if let SpatialNodeType::ScrollFrame(info) = node.node_type {
                 if let Some(id) = info.external_id {
                     result.push(ScrollNodeState { id, scroll_offset: info.offset })
                 }
             }
         }
         result
     }
 
     pub fn drain(&mut self) -> ScrollStates {
         let mut scroll_states = FastHashMap::default();
-        for old_node in &mut self.nodes.drain(..) {
+        for old_node in &mut self.spatial_nodes.drain(..) {
             if self.pipelines_to_discard.contains(&old_node.pipeline_id) {
                 continue;
             }
 
             match old_node.node_type {
-                NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(info), .. } if info.external_id.is_some() => {
+                SpatialNodeType::ScrollFrame(info) if info.external_id.is_some() => {
                     scroll_states.insert(info.external_id.unwrap(), info);
                 }
                 _ => {}
             }
         }
 
-        self.spatial_node_count = 0;
+        self.clip_nodes.clear();
         self.pipelines_to_discard.clear();
         self.clip_chains = vec![ClipChain::empty(&DeviceIntRect::zero())];
         self.clip_chains_descriptors.clear();
         scroll_states
     }
 
     pub fn scroll_node(
         &mut self,
         origin: LayoutPoint,
         id: ExternalScrollId,
         clamp: ScrollClamping
     ) -> bool {
-        for node in &mut self.nodes {
+        for node in &mut self.spatial_nodes {
             if node.matches_external_id(id) {
                 return node.set_scroll_origin(&origin, clamp);
             }
         }
 
         self.pending_scroll_offsets.insert(id, (origin, clamp));
         false
     }
 
     fn find_nearest_scrolling_ancestor(
         &self,
-        index: Option<ClipScrollNodeIndex>
-    ) -> ClipScrollNodeIndex {
+        index: Option<SpatialNodeIndex>
+    ) -> SpatialNodeIndex {
         let index = match index {
             Some(index) => index,
             None => return self.topmost_scroll_node_index(),
         };
 
-        let node = &self.nodes[index.0];
+        let node = &self.spatial_nodes[index.0];
         match node.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(state), .. } if state.sensitive_to_input_events() => index,
+            SpatialNodeType::ScrollFrame(state) if state.sensitive_to_input_events() => index,
             _ => self.find_nearest_scrolling_ancestor(node.parent)
         }
     }
 
     pub fn scroll_nearest_scrolling_ancestor(
         &mut self,
         scroll_location: ScrollLocation,
-        node_index: Option<ClipScrollNodeIndex>,
+        node_index: Option<SpatialNodeIndex>,
     ) -> bool {
-        if self.nodes.is_empty() {
+        if self.spatial_nodes.is_empty() {
             return false;
         }
         let node_index = self.find_nearest_scrolling_ancestor(node_index);
-        self.nodes[node_index.0].scroll(scroll_location)
+        self.spatial_nodes[node_index.0].scroll(scroll_location)
     }
 
     pub fn update_tree(
         &mut self,
         screen_rect: &DeviceIntRect,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         pan: WorldPoint,
         scene_properties: &SceneProperties,
     ) -> TransformPalette {
-        let mut transform_palette = TransformPalette::new(self.spatial_node_count);
+        let mut transform_palette = TransformPalette::new(self.spatial_nodes.len());
+        if self.spatial_nodes.is_empty() {
+            return transform_palette;
+        }
 
-        if !self.nodes.is_empty() {
-            self.clip_chains[0] = ClipChain::empty(screen_rect);
+        self.clip_chains[0] = ClipChain::empty(screen_rect);
 
-            let root_reference_frame_index = self.root_reference_frame_index();
-            let mut state = TransformUpdateState {
-                parent_reference_frame_transform: LayoutVector2D::new(pan.x, pan.y).into(),
-                parent_accumulated_scroll_offset: LayoutVector2D::zero(),
-                nearest_scrolling_ancestor_offset: LayoutVector2D::zero(),
-                nearest_scrolling_ancestor_viewport: LayoutRect::zero(),
-                parent_clip_chain_index: ClipChainIndex(0),
-                current_coordinate_system_id: CoordinateSystemId::root(),
-                coordinate_system_relative_transform: LayoutFastTransform::identity(),
-                invertible: true,
-            };
-            let mut next_coordinate_system_id = state.current_coordinate_system_id.next();
-            self.update_node(
-                root_reference_frame_index,
-                &mut state,
-                &mut next_coordinate_system_id,
+        let root_reference_frame_index = self.root_reference_frame_index();
+        let mut state = TransformUpdateState {
+            parent_reference_frame_transform: LayoutVector2D::new(pan.x, pan.y).into(),
+            parent_accumulated_scroll_offset: LayoutVector2D::zero(),
+            nearest_scrolling_ancestor_offset: LayoutVector2D::zero(),
+            nearest_scrolling_ancestor_viewport: LayoutRect::zero(),
+            current_coordinate_system_id: CoordinateSystemId::root(),
+            coordinate_system_relative_transform: LayoutFastTransform::identity(),
+            invertible: true,
+        };
+
+        let mut next_coordinate_system_id = state.current_coordinate_system_id.next();
+        self.update_node(
+            root_reference_frame_index,
+            &mut state,
+            &mut next_coordinate_system_id,
+            &mut transform_palette,
+            scene_properties,
+        );
+
+        for clip_node in &mut self.clip_nodes {
+            let spatial_node = &self.spatial_nodes[clip_node.spatial_node.0];
+            clip_node.update(
+                spatial_node,
                 device_pixel_scale,
                 clip_store,
                 resource_cache,
                 gpu_cache,
-                &mut transform_palette,
-                scene_properties,
+                &mut self.clip_chains,
             );
-
-            self.build_clip_chains(screen_rect);
         }
+        self.build_clip_chains(screen_rect);
 
         transform_palette
     }
 
     fn update_node(
         &mut self,
-        node_index: ClipScrollNodeIndex,
+        node_index: SpatialNodeIndex,
         state: &mut TransformUpdateState,
         next_coordinate_system_id: &mut CoordinateSystemId,
-        device_pixel_scale: DevicePixelScale,
-        clip_store: &mut ClipStore,
-        resource_cache: &mut ResourceCache,
-        gpu_cache: &mut GpuCache,
         transform_palette: &mut TransformPalette,
         scene_properties: &SceneProperties,
     ) {
         // TODO(gw): This is an ugly borrow check workaround to clone these.
         //           Restructure this to avoid the clones!
         let mut state = state.clone();
         let node_children = {
-            let node = match self.nodes.get_mut(node_index.0) {
+            let node = match self.spatial_nodes.get_mut(node_index.0) {
                 Some(node) => node,
                 None => return,
             };
 
-            node.update(
-                &mut state,
-                next_coordinate_system_id,
-                device_pixel_scale,
-                clip_store,
-                resource_cache,
-                gpu_cache,
-                scene_properties,
-                &mut self.clip_chains,
-            );
-
-            node.push_gpu_data(transform_palette);
+            node.update(&mut state, next_coordinate_system_id, scene_properties);
+            node.push_gpu_data(transform_palette, node_index);
 
             if node.children.is_empty() {
                 return;
             }
 
             node.prepare_state_for_children(&mut state);
             node.children.clone()
         };
 
         for child_node_index in node_children {
             self.update_node(
                 child_node_index,
                 &mut state,
                 next_coordinate_system_id,
-                device_pixel_scale,
-                clip_store,
-                resource_cache,
-                gpu_cache,
                 transform_palette,
                 scene_properties,
             );
         }
     }
 
     pub fn build_clip_chains(&mut self, screen_rect: &DeviceIntRect) {
         for descriptor in &self.clip_chains_descriptors {
             // A ClipChain is an optional parent (which is another ClipChain) and a list of
-            // ClipScrollNode clipping nodes. Here we start the ClipChain with a clone of the
+            // SpatialNode clipping nodes. Here we start the ClipChain with a clone of the
             // parent's node, if necessary.
             let mut chain = match descriptor.parent {
                 Some(index) => self.clip_chains[index.0].clone(),
                 None => ClipChain::empty(screen_rect),
             };
 
-            // Now we walk through each ClipScrollNode in the vector of clip nodes and
-            // extract their ClipChain nodes to construct the final list.
+            // Now we walk through each ClipNode in the vector and extract their ClipChain nodes to
+            // construct the final list.
             for clip_index in &descriptor.clips {
-                match self.nodes[clip_index.0].node_type {
-                    NodeType::Clip { clip_chain_node: Some(ref node), .. } => {
+                match self.clip_nodes[clip_index.0] {
+                    ClipNode { clip_chain_node: Some(ref node), .. } => {
                         chain.add_node(node.clone());
                     }
-                    NodeType::Clip { .. } => warn!("Found uninitialized clipping ClipScrollNode."),
-                    _ => warn!("Tried to create a clip chain with non-clipping node."),
+                    ClipNode { .. } => warn!("Found uninitialized clipping ClipNode."),
                 };
             }
 
             chain.parent_index = descriptor.parent;
             self.clip_chains[descriptor.index.0] = chain;
         }
     }
 
     pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
-        for node in &mut self.nodes {
+        for node in &mut self.spatial_nodes {
             let external_id = match node.node_type {
-                NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(ScrollFrameInfo { external_id: Some(id), ..} ), .. } => id,
+                SpatialNodeType::ScrollFrame(ScrollFrameInfo { external_id: Some(id), ..} ) => id,
                 _ => continue,
             };
 
             if let Some(scrolling_state) = old_states.get(&external_id) {
                 node.apply_old_scrolling_state(scrolling_state);
             }
 
             if let Some((offset, clamping)) = self.pending_scroll_offsets.remove(&external_id) {
                 node.set_scroll_origin(&offset, clamping);
             }
         }
     }
 
-    // Generate the next valid TransformIndex for the CST.
-    fn next_transform_index(&mut self) -> TransformIndex {
-        let transform_index = TransformIndex(self.spatial_node_count as u32);
-        self.spatial_node_count += 1;
-        transform_index
-    }
-
     pub fn add_clip_node(
         &mut self,
-        index: ClipScrollNodeIndex,
-        parent_index: ClipScrollNodeIndex,
+        index: ClipNodeIndex,
+        parent_clip_chain_index: ClipChainIndex,
+        spatial_node: SpatialNodeIndex,
         handle: ClipSourcesHandle,
-        pipeline_id: PipelineId,
     )  -> ClipChainIndex {
         let clip_chain_index = self.allocate_clip_chain();
-        let transform_index = self.nodes[parent_index.0].transform_index;
-
-        let node_type = NodeType::Clip {
-            handle,
+        let node = ClipNode {
+            parent_clip_chain_index,
+            spatial_node,
+            handle: Some(handle),
             clip_chain_index,
             clip_chain_node: None,
         };
-        let node = ClipScrollNode::new(
-            pipeline_id,
-            Some(parent_index),
-            node_type,
-            transform_index,
-        );
-        self.add_node(node, index);
+        self.push_clip_node(node, index);
         clip_chain_index
     }
 
     pub fn add_scroll_frame(
         &mut self,
-        index: ClipScrollNodeIndex,
-        parent_index: ClipScrollNodeIndex,
+        index: SpatialNodeIndex,
+        parent_index: SpatialNodeIndex,
         external_id: Option<ExternalScrollId>,
         pipeline_id: PipelineId,
         frame_rect: &LayoutRect,
         content_size: &LayoutSize,
         scroll_sensitivity: ScrollSensitivity,
     ) {
-        let node = ClipScrollNode::new_scroll_frame(
+        let node = SpatialNode::new_scroll_frame(
             pipeline_id,
             parent_index,
             external_id,
             frame_rect,
             content_size,
             scroll_sensitivity,
-            self.next_transform_index(),
         );
-        self.add_node(node, index);
+        self.add_spatial_node(node, index);
     }
 
     pub fn add_reference_frame(
         &mut self,
-        index: ClipScrollNodeIndex,
-        parent_index: Option<ClipScrollNodeIndex>,
+        index: SpatialNodeIndex,
+        parent_index: Option<SpatialNodeIndex>,
         source_transform: Option<PropertyBinding<LayoutTransform>>,
         source_perspective: Option<LayoutTransform>,
         origin_in_parent_reference_frame: LayoutVector2D,
         pipeline_id: PipelineId,
     ) {
-        let node = ClipScrollNode::new_reference_frame(
+        let node = SpatialNode::new_reference_frame(
             parent_index,
             source_transform,
             source_perspective,
             origin_in_parent_reference_frame,
             pipeline_id,
-            self.next_transform_index(),
         );
-        self.add_node(node, index);
+        self.add_spatial_node(node, index);
     }
 
     pub fn add_sticky_frame(
         &mut self,
-        index: ClipScrollNodeIndex,
-        parent_index: ClipScrollNodeIndex,
+        index: SpatialNodeIndex,
+        parent_index: SpatialNodeIndex,
         sticky_frame_info: StickyFrameInfo,
         pipeline_id: PipelineId,
     ) {
-        let node = ClipScrollNode::new_sticky_frame(
+        let node = SpatialNode::new_sticky_frame(
             parent_index,
             sticky_frame_info,
             pipeline_id,
-            self.next_transform_index(),
         );
-        self.add_node(node, index);
+        self.add_spatial_node(node, index);
     }
 
     pub fn add_clip_chain_descriptor(
         &mut self,
         parent: Option<ClipChainIndex>,
-        clips: Vec<ClipScrollNodeIndex>
+        clips: Vec<ClipNodeIndex>
     ) -> ClipChainIndex {
         let index = self.allocate_clip_chain();
         self.clip_chains_descriptors.push(ClipChainDescriptor { index, parent, clips });
         index
     }
 
-    pub fn add_node(&mut self, node: ClipScrollNode, index: ClipScrollNodeIndex) {
-        // When the parent node is None this means we are adding the root.
-        if let Some(parent_index) = node.parent {
-            self.nodes[parent_index.0].add_child(index);
-        }
-
-        if index.0 == self.nodes.len() {
-            self.nodes.push(node);
+    pub fn push_clip_node(&mut self, node: ClipNode, index: ClipNodeIndex) {
+        if index.0 == self.clip_nodes.len() {
+            self.clip_nodes.push(node);
             return;
         }
 
-
-        if let Some(empty_node) = self.nodes.get_mut(index.0) {
+        if let Some(empty_node) = self.clip_nodes.get_mut(index.0) {
             *empty_node = node;
             return
         }
 
-        let length_to_reserve = index.0 + 1 - self.nodes.len();
-        self.nodes.reserve_exact(length_to_reserve);
+        let length_to_reserve = index.0 + 1 - self.clip_nodes.len();
+        self.clip_nodes.reserve_exact(length_to_reserve);
 
         // We would like to use `Vec::resize` here, but the Clone trait is not supported
-        // for ClipScrollNodes. We can fix this either by splitting the clip nodes out into
-        // their own tree or when support is added for something like `Vec::resize_default`.
-        let length_to_extend = self.nodes.len() .. index.0;
-        self.nodes.extend(length_to_extend.map(|_| ClipScrollNode::empty()));
+        // for ClipNodes. We can fix this either when support is added for something like
+        // `Vec::resize_default`.
+        let length_to_extend = self.clip_nodes.len() .. index.0;
+        self.clip_nodes.extend(length_to_extend.map(|_| ClipNode::empty()));
+        self.clip_nodes.push(node);
+    }
+
+    pub fn add_spatial_node(&mut self, node: SpatialNode, index: SpatialNodeIndex) {
+        // When the parent node is None this means we are adding the root.
+        if let Some(parent_index) = node.parent {
+            self.spatial_nodes[parent_index.0].add_child(index);
+        }
 
-        self.nodes.push(node);
+        if index.0 == self.spatial_nodes.len() {
+            self.spatial_nodes.push(node);
+            return;
+        }
+
+        if let Some(empty_node) = self.spatial_nodes.get_mut(index.0) {
+            *empty_node = node;
+            return
+        }
+
+        debug_assert!(index.0 > self.spatial_nodes.len() - 1);
+        self.spatial_nodes.resize(index.0, SpatialNode::empty());
+        self.spatial_nodes.push(node);
     }
 
     pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
         self.pipelines_to_discard.insert(pipeline_id);
     }
 
     fn print_node<T: PrintTreePrinter>(
         &self,
-        index: ClipScrollNodeIndex,
+        index: SpatialNodeIndex,
         pt: &mut T,
         clip_store: &ClipStore
     ) {
-        let node = &self.nodes[index.0];
+        let node = &self.spatial_nodes[index.0];
         match node.node_type {
-            NodeType::Spatial { ref kind, .. } => {
-                match *kind {
-                    SpatialNodeKind::StickyFrame(ref sticky_frame_info) => {
-                        pt.new_level(format!("StickyFrame"));
-                        pt.add_item(format!("index: {:?}", index));
-                        pt.add_item(format!("sticky info: {:?}", sticky_frame_info));
-                    }
-                    SpatialNodeKind::ScrollFrame(scrolling_info) => {
-                        pt.new_level(format!("ScrollFrame"));
-                        pt.add_item(format!("index: {:?}", index));
-                        pt.add_item(format!("viewport: {:?}", scrolling_info.viewport_rect));
-                        pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
-                        pt.add_item(format!("scroll offset: {:?}", scrolling_info.offset));
-                    }
-                    SpatialNodeKind::ReferenceFrame(ref info) => {
-                        pt.new_level(format!("ReferenceFrame {:?}", info.resolved_transform));
-                        pt.add_item(format!("index: {:?}", index));
-                    }
-                }
+            SpatialNodeType::StickyFrame(ref sticky_frame_info) => {
+                pt.new_level(format!("StickyFrame"));
+                pt.add_item(format!("index: {:?}", index));
+                pt.add_item(format!("sticky info: {:?}", sticky_frame_info));
             }
-            NodeType::Clip { ref handle, .. } => {
-                pt.new_level("Clip".to_owned());
-
+            SpatialNodeType::ScrollFrame(scrolling_info) => {
+                pt.new_level(format!("ScrollFrame"));
                 pt.add_item(format!("index: {:?}", index));
-                let clips = clip_store.get(handle).clips();
-                pt.new_level(format!("Clip Sources [{}]", clips.len()));
-                for source in clips {
-                    pt.add_item(format!("{:?}", source));
-                }
-                pt.end_level();
+                pt.add_item(format!("viewport: {:?}", scrolling_info.viewport_rect));
+                pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
+                pt.add_item(format!("scroll offset: {:?}", scrolling_info.offset));
             }
-            NodeType::Empty => unreachable!("Empty node remaining in ClipScrollTree."),
+            SpatialNodeType::ReferenceFrame(ref info) => {
+                pt.new_level(format!("ReferenceFrame {:?}", info.resolved_transform));
+                pt.add_item(format!("index: {:?}", index));
+            }
+            SpatialNodeType::Empty => unreachable!("Empty node remaining in ClipScrollTree."),
         }
 
         pt.add_item(format!("world_viewport_transform: {:?}", node.world_viewport_transform));
         pt.add_item(format!("world_content_transform: {:?}", node.world_content_transform));
         pt.add_item(format!("coordinate_system_id: {:?}", node.coordinate_system_id));
 
         for child_index in &node.children {
             self.print_node(*child_index, pt, clip_store);
         }
 
         pt.end_level();
     }
 
     #[allow(dead_code)]
     pub fn print(&self, clip_store: &ClipStore) {
-        if !self.nodes.is_empty() {
+        if !self.spatial_nodes.is_empty() {
             let mut pt = PrintTree::new("clip_scroll tree");
             self.print_with(clip_store, &mut pt);
         }
     }
 
     pub fn print_with<T: PrintTreePrinter>(&self, clip_store: &ClipStore, pt: &mut T) {
-        if !self.nodes.is_empty() {
+        if !self.spatial_nodes.is_empty() {
             self.print_node(self.root_reference_frame_index(), pt, clip_store);
         }
     }
 
     pub fn allocate_clip_chain(&mut self) -> ClipChainIndex {
         debug_assert!(!self.clip_chains.is_empty());
         let new_clip_chain =self.clip_chains[0].clone();
         self.clip_chains.push(new_clip_chain);
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -9,18 +9,17 @@ use api::{DevicePixelScale, DeviceUintRe
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, GlyphRasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint};
 use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
 use api::{LineOrientation, LineStyle, LocalClip, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
 use api::{TransformStyle, YuvColorSpace, YuvData};
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
-use clip_scroll_node::{NodeType, SpatialNodeKind, StickyFrameInfo};
-use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
+use clip_scroll_tree::{ClipChainIndex, ClipNodeIndex, ClipScrollTree, SpatialNodeIndex};
 use euclid::vec2;
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
@@ -28,55 +27,68 @@ use picture::PictureCompositeMode;
 use prim_store::{BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegmentDescriptor};
 use prim_store::{EdgeAaSegmentMask, ImageSource};
 use prim_store::{BorderSource, BrushSegment, PictureIndex, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitiveCpu};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::{BuiltScene, SceneRequest};
+use spatial_node::{SpatialNodeType, StickyFrameInfo};
 use std::{f32, mem, usize};
 use tiling::{CompositeOps, ScrollbarPrimitive};
 use util::{MaxRect, RectHelpers, recycle_vec};
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
     r: 0.3,
     g: 0.3,
     b: 0.3,
     a: 0.6,
 };
 
+#[derive(Clone, Copy)]
+pub struct PipelineOffset {
+    pipeline: PipelineId,
+    spatial_offset: usize,
+    clip_offset: usize,
+}
+
 /// A data structure that keeps track of mapping between API ClipIds and the indices used
 /// internally in the ClipScrollTree to avoid having to do HashMap lookups. ClipIdToIndexMapper is
-/// responsible for mapping both ClipId to ClipChainIndex and ClipId to ClipScrollNodeIndex.  We
+/// responsible for mapping both ClipId to ClipChainIndex and ClipId to SpatialNodeIndex.  We
 /// also include two small LRU caches. Currently the caches are small (1 entry), but in the future
 /// we could use uluru here to do something more involved.
 #[derive(Default)]
 pub struct ClipIdToIndexMapper {
     /// A map which converts a ClipId for a clipping node or an API-defined ClipChain into
     /// a ClipChainIndex, which is the index used internally in the ClipScrollTree to
     /// identify ClipChains.
     clip_chain_map: FastHashMap<ClipId, ClipChainIndex>,
 
     /// The last mapped ClipChainIndex, used to avoid having to do lots of consecutive
     /// HashMap lookups.
     cached_clip_chain_index: Option<(ClipId, ClipChainIndex)>,
 
-    /// The offset in the ClipScrollTree's array of ClipScrollNodes for a particular pipeline.
-    /// This is used to convert a ClipId into a ClipScrollNodeIndex.
-    pipeline_offsets: FastHashMap<PipelineId, usize>,
+    /// The offset in the ClipScrollTree's array of SpatialNodes and ClipNodes for a particular
+    /// pipeline.  This is used to convert ClipIds into SpatialNodeIndex or ClipNodeIndex.
+    pipeline_offsets: FastHashMap<PipelineId, PipelineOffset>,
 
     /// The last mapped pipeline offset for this mapper. This is used to avoid having to
     /// consult `pipeline_offsets` repeatedly when flattening the display list.
-    cached_pipeline_offset: Option<(PipelineId, usize)>,
+    cached_pipeline_offset: Option<PipelineOffset>,
 
-    /// The next available pipeline offset for ClipScrollNodeIndex. When we encounter a pipeline
-    /// we will use this value and increment it by the total number of ClipScrollNodes in the
+    /// The next available pipeline offset for ClipNodeIndex. When we encounter a pipeline
+    /// we will use this value and increment it by the total number of clip nodes in the
     /// pipeline's display list.
-    next_available_offset: usize,
+    next_available_clip_offset: usize,
+
+    /// The next available pipeline offset for SpatialNodeIndex. When we encounter a pipeline
+    /// we will use this value and increment it by the total number of spatial nodes in the
+    /// pipeline's display list.
+    next_available_spatial_offset: usize,
 }
 
 impl ClipIdToIndexMapper {
     pub fn add_clip_chain(&mut self, id: ClipId, index: ClipChainIndex) {
         debug_assert!(!self.clip_chain_map.contains_key(&id));
         self.clip_chain_map.insert(id, index);
     }
 
@@ -96,49 +108,56 @@ impl ClipIdToIndexMapper {
     }
 
     pub fn get_clip_chain_index_and_cache_result(&mut self, id: &ClipId) -> ClipChainIndex {
         let index = self.get_clip_chain_index(id);
         self.cached_clip_chain_index = Some((*id, index));
         index
     }
 
-    pub fn map_clip_and_scroll(&mut self, info: &ClipAndScrollInfo) -> ScrollNodeAndClipChain {
-        ScrollNodeAndClipChain::new(
-            self.get_node_index(info.scroll_node_id),
-            self.get_clip_chain_index_and_cache_result(&info.clip_node_id())
-        )
-    }
-
-    pub fn simple_scroll_and_clip_chain(&mut self, id: &ClipId) -> ScrollNodeAndClipChain {
-        self.map_clip_and_scroll(&ClipAndScrollInfo::simple(*id))
-    }
-
     pub fn initialize_for_pipeline(&mut self, pipeline: &ScenePipeline) {
         debug_assert!(!self.pipeline_offsets.contains_key(&pipeline.pipeline_id));
-        self.pipeline_offsets.insert(pipeline.pipeline_id, self.next_available_offset);
-        self.next_available_offset += pipeline.display_list.total_clip_ids();
+        self.pipeline_offsets.insert(
+            pipeline.pipeline_id,
+            PipelineOffset {
+                pipeline: pipeline.pipeline_id,
+                spatial_offset: self.next_available_spatial_offset,
+                clip_offset: self.next_available_clip_offset,
+            }
+        );
+
+        self.next_available_clip_offset += pipeline.display_list.total_clip_nodes();
+        self.next_available_spatial_offset += pipeline.display_list.total_spatial_nodes();
     }
 
-    pub fn get_node_index(&mut self, id: ClipId) -> ClipScrollNodeIndex {
-        let (index, pipeline_id) = match id {
-            ClipId::Clip(index, pipeline_id) => (index, pipeline_id),
-            ClipId::ClipChain(_) => panic!("Tried to use ClipChain as scroll node."),
-        };
-
-        let pipeline_offset = match self.cached_pipeline_offset {
-            Some((last_used_id, offset)) if last_used_id == pipeline_id => offset,
+    pub fn get_pipeline_offet<'a>(&'a mut self, id: PipelineId) -> &'a PipelineOffset {
+        match self.cached_pipeline_offset {
+            Some(ref offset) if offset.pipeline == id => offset,
             _ => {
-                let offset = self.pipeline_offsets[&pipeline_id];
-                self.cached_pipeline_offset = Some((pipeline_id, offset));
+                let offset = &self.pipeline_offsets[&id];
+                self.cached_pipeline_offset = Some(*offset);
                 offset
             }
-        };
+        }
+    }
 
-        ClipScrollNodeIndex(pipeline_offset + index)
+    pub fn get_clip_node_index(&mut self, id: ClipId) -> ClipNodeIndex {
+        match id {
+            ClipId::Clip(index, pipeline_id) => {
+                let pipeline_offset = self.get_pipeline_offet(pipeline_id);
+                ClipNodeIndex(pipeline_offset.clip_offset + index)
+            }
+            ClipId::Spatial(..) => {
+                // We could theoretically map back to the containing clip node with the current
+                // design, but we will eventually fully separate out clipping from spatial nodes
+                // in the display list. We don't ever need to do this anyway.
+                panic!("Tried to use positioning node as clip node.");
+            }
+            ClipId::ClipChain(_) => panic!("Tried to use ClipChain as scroll node."),
+        }
     }
 }
 
 /// A structure that converts a serialized display list into a form that WebRender
 /// can use to later build a frame. This structure produces a FrameBuilder. Public
 /// members are typically those that are destructured into the FrameBuilder.
 pub struct DisplayListFlattener<'a> {
     /// The scene that we are currently flattening.
@@ -155,17 +174,17 @@ pub struct DisplayListFlattener<'a> {
     output_pipelines: &'a FastHashSet<PipelineId>,
 
     /// The data structure that converting between ClipId and the various index
     /// types that the ClipScrollTree uses.
     id_to_index_mapper: ClipIdToIndexMapper,
 
     /// A stack of scroll nodes used during display list processing to properly
     /// parent new scroll nodes.
-    reference_frame_stack: Vec<(ClipId, ClipScrollNodeIndex)>,
+    reference_frame_stack: Vec<(ClipId, SpatialNodeIndex)>,
 
     /// A stack of stacking context properties.
     sc_stack: Vec<FlattenedStackingContext>,
 
     /// A stack of the current pictures.
     picture_stack: Vec<PictureIndex>,
 
     /// A stack of the currently active shadows
@@ -268,24 +287,22 @@ impl<'a> DisplayListFlattener<'a> {
         if items.is_empty() {
             return vec![];
         }
         self.scene.get_display_list_for_pipeline(pipeline_id).get(items).collect()
     }
 
     fn flatten_root(&mut self, pipeline: &'a ScenePipeline, frame_size: &LayoutSize) {
         let pipeline_id = pipeline.pipeline_id;
-        let reference_frame_info = self.id_to_index_mapper.simple_scroll_and_clip_chain(
-            &ClipId::root_reference_frame(pipeline_id)
+        let reference_frame_info = self.simple_scroll_and_clip_chain(
+            &ClipId::root_reference_frame(pipeline_id),
         );
 
         let root_scroll_node = ClipId::root_scroll_node(pipeline_id);
-        let scroll_frame_info = self.id_to_index_mapper.simple_scroll_and_clip_chain(
-            &root_scroll_node,
-        );
+        let scroll_frame_info = self.simple_scroll_and_clip_chain(&root_scroll_node);
 
         self.push_stacking_context(
             pipeline_id,
             CompositeOps::default(),
             TransformStyle::Flat,
             true,
             true,
             root_scroll_node,
@@ -375,17 +392,17 @@ impl<'a> DisplayListFlattener<'a> {
         let sticky_frame_info = StickyFrameInfo::new(
             frame_rect,
             info.margins,
             info.vertical_offset_bounds,
             info.horizontal_offset_bounds,
             info.previously_applied_offset,
         );
 
-        let index = self.id_to_index_mapper.get_node_index(info.id);
+        let index = self.get_spatial_node_index_for_clip_id(info.id);
         self.clip_scroll_tree.add_sticky_frame(
             index,
             clip_and_scroll.scroll_node_id, /* parent id */
             sticky_frame_info,
             info.id.pipeline_id(),
         );
         self.id_to_index_mapper.map_to_parent_clip_chain(info.id, parent_id);
     }
@@ -402,17 +419,17 @@ impl<'a> DisplayListFlattener<'a> {
         let clip_region = ClipRegion::create_for_clip_node(
             *item.clip_rect(),
             complex_clips,
             info.image_mask,
             reference_frame_relative_offset,
         );
         // Just use clip rectangle as the frame rect for this scroll frame.
         // This is useful when calculating scroll extents for the
-        // ClipScrollNode::scroll(..) API as well as for properly setting sticky
+        // SpatialNode::scroll(..) API as well as for properly setting sticky
         // positioning offsets.
         let frame_rect = item.clip_rect().translate(reference_frame_relative_offset);
         let content_rect = item.rect().translate(reference_frame_relative_offset);
 
         debug_assert!(info.clip_id != info.scroll_frame_id);
 
         self.add_clip_node(info.clip_id, clip_and_scroll_ids.scroll_node_id, clip_region);
 
@@ -551,17 +568,17 @@ impl<'a> DisplayListFlattener<'a> {
 
     fn flatten_item<'b>(
         &'b mut self,
         item: DisplayItemRef<'a, 'b>,
         pipeline_id: PipelineId,
         reference_frame_relative_offset: LayoutVector2D,
     ) -> Option<BuiltDisplayListIter<'a>> {
         let clip_and_scroll_ids = item.clip_and_scroll();
-        let clip_and_scroll = self.id_to_index_mapper.map_clip_and_scroll(&clip_and_scroll_ids);
+        let clip_and_scroll = self.map_clip_and_scroll(&clip_and_scroll_ids);
 
         let prim_info = item.get_layout_primitive_info(&reference_frame_relative_offset);
         match *item.item() {
             SpecificDisplayItem::Image(ref info) => {
                 self.add_image(
                     clip_and_scroll,
                     &prim_info,
                     info.stretch_size,
@@ -712,17 +729,17 @@ impl<'a> DisplayListFlattener<'a> {
                     info.image_mask,
                     &reference_frame_relative_offset,
                 );
                 self.add_clip_node(info.id, clip_and_scroll_ids.scroll_node_id, clip_region);
             }
             SpecificDisplayItem::ClipChain(ref info) => {
                 let items = self.get_clip_chain_items(pipeline_id, item.clip_chain_items())
                                 .iter()
-                                .map(|id| self.id_to_index_mapper.get_node_index(*id))
+                                .map(|id| self.id_to_index_mapper.get_clip_node_index(*id))
                                 .collect();
                 let parent = info.parent.map(|id|
                      self.id_to_index_mapper.get_clip_chain_index(&ClipId::ClipChain(id))
                 );
                 let clip_chain_index =
                     self.clip_scroll_tree.add_clip_chain_descriptor(parent, items);
                 self.id_to_index_mapper.add_clip_chain(ClipId::ClipChain(info.id), clip_chain_index);
             },
@@ -878,26 +895,26 @@ impl<'a> DisplayListFlattener<'a> {
 
     pub fn push_stacking_context(
         &mut self,
         pipeline_id: PipelineId,
         composite_ops: CompositeOps,
         transform_style: TransformStyle,
         is_backface_visible: bool,
         is_pipeline_root: bool,
-        positioning_node: ClipId,
+        spatial_node: ClipId,
         clipping_node: Option<ClipId>,
         glyph_raster_space: GlyphRasterSpace,
     ) {
         let clip_chain_id = match clipping_node {
             Some(ref clipping_node) => self.id_to_index_mapper.get_clip_chain_index(clipping_node),
             None => ClipChainIndex(0), // This means no clipping.
         };
         let clip_and_scroll = ScrollNodeAndClipChain::new(
-            self.id_to_index_mapper.get_node_index(positioning_node),
+            self.get_spatial_node_index_for_clip_id(spatial_node),
             clip_chain_id
         );
 
         // Construct the necessary set of Picture primitives
         // to draw this stacking context.
         let current_reference_frame_index = self.current_reference_frame_index();
 
         // An arbitrary large clip rect. For now, we don't
@@ -1186,19 +1203,19 @@ impl<'a> DisplayListFlattener<'a> {
     pub fn push_reference_frame(
         &mut self,
         reference_frame_id: ClipId,
         parent_id: Option<ClipId>,
         pipeline_id: PipelineId,
         source_transform: Option<PropertyBinding<LayoutTransform>>,
         source_perspective: Option<LayoutTransform>,
         origin_in_parent_reference_frame: LayoutVector2D,
-    ) -> ClipScrollNodeIndex {
-        let index = self.id_to_index_mapper.get_node_index(reference_frame_id);
-        let parent_index = parent_id.map(|id| self.id_to_index_mapper.get_node_index(id));
+    ) -> SpatialNodeIndex {
+        let index = self.get_spatial_node_index_for_clip_id(reference_frame_id);
+        let parent_index = parent_id.map(|id| self.get_spatial_node_index_for_clip_id(id));
         self.clip_scroll_tree.add_reference_frame(
             index,
             parent_index,
             source_transform,
             source_perspective,
             origin_in_parent_reference_frame,
             pipeline_id,
         );
@@ -1207,29 +1224,29 @@ impl<'a> DisplayListFlattener<'a> {
         match parent_id {
             Some(ref parent_id) =>
                 self.id_to_index_mapper.map_to_parent_clip_chain(reference_frame_id, parent_id),
             _ => self.id_to_index_mapper.add_clip_chain(reference_frame_id, ClipChainIndex(0)),
         }
         index
     }
 
-    pub fn current_reference_frame_index(&self) -> ClipScrollNodeIndex {
+    pub fn current_reference_frame_index(&self) -> SpatialNodeIndex {
         self.reference_frame_stack.last().unwrap().1
     }
 
     pub fn setup_viewport_offset(
         &mut self,
         inner_rect: DeviceUintRect,
         device_pixel_scale: DevicePixelScale,
     ) {
         let viewport_offset = (inner_rect.origin.to_vector().to_f32() / device_pixel_scale).round();
         let root_id = self.clip_scroll_tree.root_reference_frame_index();
-        let root_node = &mut self.clip_scroll_tree.nodes[root_id.0];
-        if let NodeType::Spatial { kind: SpatialNodeKind::ReferenceFrame(ref mut info), .. } = root_node.node_type {
+        let root_node = &mut self.clip_scroll_tree.spatial_nodes[root_id.0];
+        if let SpatialNodeType::ReferenceFrame(ref mut info) = root_node.node_type {
             info.resolved_transform =
                 LayoutVector2D::new(viewport_offset.x, viewport_offset.y).into();
         }
     }
 
     pub fn push_root(
         &mut self,
         pipeline_id: PipelineId,
@@ -1256,45 +1273,48 @@ impl<'a> DisplayListFlattener<'a> {
         );
     }
 
     pub fn add_clip_node(
         &mut self,
         new_node_id: ClipId,
         parent_id: ClipId,
         clip_region: ClipRegion,
-    ) -> ClipScrollNodeIndex {
+    ) {
         let clip_sources = ClipSources::from(clip_region);
         let handle = self.clip_store.insert(clip_sources);
 
-        let node_index = self.id_to_index_mapper.get_node_index(new_node_id);
+        let node_index = self.id_to_index_mapper.get_clip_node_index(new_node_id);
+        let parent_clip_chain_index =
+            self.id_to_index_mapper.get_clip_chain_index_and_cache_result(&parent_id);
+        let spatial_node = self.get_spatial_node_index_for_clip_id(parent_id);
         let clip_chain_index = self.clip_scroll_tree.add_clip_node(
             node_index,
-            self.id_to_index_mapper.get_node_index(parent_id),
+            parent_clip_chain_index,
+            spatial_node,
             handle,
-            new_node_id.pipeline_id(),
         );
         self.id_to_index_mapper.add_clip_chain(new_node_id, clip_chain_index);
-        node_index
     }
 
     pub fn add_scroll_frame(
         &mut self,
         new_node_id: ClipId,
         parent_id: ClipId,
         external_id: Option<ExternalScrollId>,
         pipeline_id: PipelineId,
         frame_rect: &LayoutRect,
         content_size: &LayoutSize,
         scroll_sensitivity: ScrollSensitivity,
-    ) -> ClipScrollNodeIndex {
-        let node_index = self.id_to_index_mapper.get_node_index(new_node_id);
+    ) -> SpatialNodeIndex {
+        let node_index = self.get_spatial_node_index_for_clip_id(new_node_id);
+        let parent_node_index = self.get_spatial_node_index_for_clip_id(parent_id);
         self.clip_scroll_tree.add_scroll_frame(
             node_index,
-            self.id_to_index_mapper.get_node_index(parent_id),
+            parent_node_index,
             external_id,
             pipeline_id,
             frame_rect,
             content_size,
             scroll_sensitivity,
         );
         self.id_to_index_mapper.map_to_parent_clip_chain(new_node_id, &parent_id);
         node_index
@@ -1928,16 +1948,41 @@ impl<'a> DisplayListFlattener<'a> {
 
         self.add_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
             PrimitiveContainer::Brush(prim),
         );
     }
+
+    pub fn map_clip_and_scroll(&mut self, info: &ClipAndScrollInfo) -> ScrollNodeAndClipChain {
+        ScrollNodeAndClipChain::new(
+            self.get_spatial_node_index_for_clip_id(info.scroll_node_id),
+            self.id_to_index_mapper.get_clip_chain_index_and_cache_result(&info.clip_node_id())
+        )
+    }
+
+    pub fn simple_scroll_and_clip_chain(&mut self, id: &ClipId) -> ScrollNodeAndClipChain {
+        self.map_clip_and_scroll(&ClipAndScrollInfo::simple(*id))
+    }
+
+    pub fn get_spatial_node_index_for_clip_id(&mut self, id: ClipId,) -> SpatialNodeIndex {
+        match id {
+            ClipId::Spatial(index, pipeline_id) => {
+                let pipeline_offset = self.id_to_index_mapper.get_pipeline_offet(pipeline_id);
+                SpatialNodeIndex(pipeline_offset.spatial_offset + index)
+            }
+            ClipId::Clip(..) => {
+                let clip_node_index = self.id_to_index_mapper.get_clip_node_index(id);
+                self.clip_scroll_tree.clip_nodes[clip_node_index.0].spatial_node
+            }
+            ClipId::ClipChain(_) => panic!("Tried to use ClipChain as scroll node."),
+        }
+    }
 }
 
 pub fn build_scene(config: &FrameBuilderConfig, request: SceneRequest) -> BuiltScene {
 
     let mut clip_scroll_tree = ClipScrollTree::new();
     let mut new_scene = Scene::new();
 
     let frame_builder = DisplayListFlattener::create_frame_builder(
@@ -1982,9 +2027,9 @@ struct FlattenedStackingContext {
 
     /// If Some(..), this stacking context establishes a new
     /// 3d rendering context, and the value is the picture
     // index of the 3d context container.
     rendering_context_3d_pic_index: Option<PictureIndex>,
 }
 
 #[derive(Debug)]
-pub struct ScrollbarInfo(pub ClipScrollNodeIndex, pub LayoutRect);
+pub struct ScrollbarInfo(pub SpatialNodeIndex, pub LayoutRect);
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,30 +1,30 @@
 /* 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/. */
 
 use api::{BuiltDisplayList, ColorF, DeviceIntPoint, DeviceIntRect, DevicePixelScale};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FontRenderMode};
 use api::{LayoutPoint, LayoutRect, LayoutSize, PipelineId, WorldPoint};
 use clip::{ClipChain, ClipStore};
-use clip_scroll_node::{ClipScrollNode};
-use clip_scroll_tree::{ClipScrollNodeIndex, ClipScrollTree};
+use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
-use gpu_types::{PrimitiveHeaders, TransformData, UvRectKind};
+use gpu_types::{PrimitiveHeaders, TransformData, TransformIndex, UvRectKind};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap};
 use picture::PictureSurface;
 use prim_store::{PrimitiveIndex, PrimitiveRun, PrimitiveStore};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_backend::FrameId;
 use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
+use spatial_node::SpatialNode;
 use std::{mem, f32};
 use std::sync::Arc;
 use tiling::{Frame, RenderPass, RenderPassKind, RenderTargetContext};
 use tiling::{ScrollbarPrimitive, SpecialRenderPasses};
 use util::{self, WorldToLayoutFastTransform};
 
 
 #[derive(Clone, Copy, Debug, PartialEq)]
@@ -83,17 +83,17 @@ pub struct FrameBuildingState<'a> {
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub special_render_passes: &'a mut SpecialRenderPasses,
 }
 
 pub struct PictureContext<'a> {
     pub pipeline_id: PipelineId,
     pub prim_runs: Vec<PrimitiveRun>,
-    pub original_reference_frame_index: Option<ClipScrollNodeIndex>,
+    pub original_reference_frame_index: Option<SpatialNodeIndex>,
     pub display_list: &'a BuiltDisplayList,
     pub inv_world_transform: Option<WorldToLayoutFastTransform>,
     pub apply_local_clip_rect: bool,
     pub inflation_factor: f32,
     pub allow_subpixel_aa: bool,
 }
 
 pub struct PictureState {
@@ -109,30 +109,33 @@ impl PictureState {
             has_non_root_coord_system: false,
             local_rect_changed: false,
         }
     }
 }
 
 pub struct PrimitiveRunContext<'a> {
     pub clip_chain: &'a ClipChain,
-    pub scroll_node: &'a ClipScrollNode,
+    pub scroll_node: &'a SpatialNode,
+    pub transform_index: TransformIndex,
     pub local_clip_rect: LayoutRect,
 }
 
 impl<'a> PrimitiveRunContext<'a> {
     pub fn new(
         clip_chain: &'a ClipChain,
-        scroll_node: &'a ClipScrollNode,
+        scroll_node: &'a SpatialNode,
+        transform_index: TransformIndex,
         local_clip_rect: LayoutRect,
     ) -> Self {
         PrimitiveRunContext {
             clip_chain,
             scroll_node,
             local_clip_rect,
+            transform_index,
         }
     }
 }
 
 impl FrameBuilder {
     pub fn empty() -> Self {
         FrameBuilder {
             hit_testing_runs: Vec::new(),
@@ -190,21 +193,21 @@ impl FrameBuilder {
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
         if self.prim_store.pictures.is_empty() {
             return None
         }
 
         // The root picture is always the first one added.
-        let root_clip_scroll_node =
-            &clip_scroll_tree.nodes[clip_scroll_tree.root_reference_frame_index().0];
+        let root_spatial_node =
+            &clip_scroll_tree.spatial_nodes[clip_scroll_tree.root_reference_frame_index().0];
 
         let display_list = &pipelines
-            .get(&root_clip_scroll_node.pipeline_id)
+            .get(&root_spatial_node.pipeline_id)
             .expect("No display list?")
             .display_list;
 
         const MAX_CLIP_COORD: f32 = 1.0e9;
 
         let frame_context = FrameBuildingContext {
             scene_id: self.scene_id,
             device_pixel_scale,
@@ -224,17 +227,17 @@ impl FrameBuilder {
             profile_counters,
             clip_store: &mut self.clip_store,
             resource_cache,
             gpu_cache,
             special_render_passes,
         };
 
         let pic_context = PictureContext {
-            pipeline_id: root_clip_scroll_node.pipeline_id,
+            pipeline_id: root_spatial_node.pipeline_id,
             prim_runs: mem::replace(&mut self.prim_store.pictures[0].runs, Vec::new()),
             original_reference_frame_index: None,
             display_list,
             inv_world_transform: None,
             apply_local_clip_rect: true,
             inflation_factor: 0.0,
             allow_subpixel_aa: true,
         };
@@ -265,17 +268,17 @@ impl FrameBuilder {
         Some(render_task_id)
     }
 
     fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache) {
         static SCROLLBAR_PADDING: f32 = 8.0;
 
         for scrollbar_prim in &self.scrollbar_prims {
             let metadata = &mut self.prim_store.cpu_metadata[scrollbar_prim.prim_index.0];
-            let scroll_frame = &clip_scroll_tree.nodes[scrollbar_prim.scroll_frame_index.0];
+            let scroll_frame = &clip_scroll_tree.spatial_nodes[scrollbar_prim.scroll_frame_index.0];
 
             // Invalidate what's in the cache so it will get rebuilt.
             gpu_cache.invalidate(&metadata.gpu_location);
 
             let scrollable_distance = scroll_frame.scrollable_size().height;
             if scrollable_distance <= 0.0 {
                 metadata.local_clip_rect.size = LayoutSize::zero();
                 continue;
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -1,15 +1,14 @@
 /* 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/. */
 
 use api::{DevicePoint, DeviceSize, DeviceRect, LayoutRect, LayoutToWorldTransform};
 use api::{PremultipliedColorF, WorldToLayoutTransform};
-use clip_scroll_tree::TransformIndex;
 use gpu_cache::{GpuCacheAddress, GpuDataRequest};
 use prim_store::{EdgeAaSegmentMask};
 use render_task::RenderTaskAddress;
 use util::{MatrixHelpers, TransformedRectKind};
 
 // Contains type that must exactly match the same structures declared in GLSL.
 
 #[derive(Copy, Clone, Debug)]
@@ -395,16 +394,21 @@ pub struct TransformMetadata {
 //           the future, the transform palette will support
 //           specifying a coordinate system that the transform
 //           should be relative to.
 pub struct TransformPalette {
     pub transforms: Vec<TransformData>,
     metadata: Vec<TransformMetadata>,
 }
 
+#[derive(Copy, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct TransformIndex(pub u32);
+
 impl TransformPalette {
     pub fn new(spatial_node_count: usize) -> TransformPalette {
         TransformPalette {
             transforms: Vec::with_capacity(spatial_node_count),
             metadata: Vec::with_capacity(spatial_node_count),
         }
     }
 
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -1,48 +1,53 @@
 /* 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/. */
 
 use api::{BorderRadius, ClipMode, HitTestFlags, HitTestItem, HitTestResult, ItemTag, LayoutPoint};
 use api::{LayoutPrimitiveInfo, LayoutRect, PipelineId, WorldPoint};
 use clip::{ClipSource, ClipStore, rounded_rectangle_contains_point};
-use clip_scroll_node::{ClipScrollNode, NodeType};
-use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
+use clip_node::ClipNode;
+use clip_scroll_tree::{ClipChainIndex, ClipNodeIndex, SpatialNodeIndex, ClipScrollTree};
 use internal_types::FastHashMap;
 use prim_store::ScrollNodeAndClipChain;
 use util::LayoutToWorldFastTransform;
 
 /// A copy of important clip scroll node data to use during hit testing. This a copy of
 /// data from the ClipScrollTree that will persist as a new frame is under construction,
 /// allowing hit tests consistent with the currently rendered frame.
-pub struct HitTestClipScrollNode {
+pub struct HitTestSpatialNode {
     /// The pipeline id of this node.
     pipeline_id: PipelineId,
 
-    /// A particular point must be inside all of these regions to be considered clipped in
-    /// for the purposes of a hit test.
-    regions: Vec<HitTestRegion>,
-
     /// World transform for content transformed by this node.
     world_content_transform: LayoutToWorldFastTransform,
 
     /// World viewport transform for content transformed by this node.
     world_viewport_transform: LayoutToWorldFastTransform,
 }
 
+pub struct HitTestClipNode {
+    /// The positioning node for this clip node.
+    spatial_node: SpatialNodeIndex,
+
+    /// A particular point must be inside all of these regions to be considered clipped in
+    /// for the purposes of a hit test.
+    regions: Vec<HitTestRegion>,
+}
+
 /// A description of a clip chain in the HitTester. This is used to describe
 /// hierarchical clip scroll nodes as well as ClipChains, so that they can be
 /// handled the same way during hit testing. Once we represent all ClipChains
 /// using ClipChainDescriptors, we can get rid of this and just use the
 /// ClipChainDescriptor here.
 #[derive(Clone)]
 struct HitTestClipChainDescriptor {
     parent: Option<ClipChainIndex>,
-    clips: Vec<ClipScrollNodeIndex>,
+    clips: Vec<ClipNodeIndex>,
 }
 
 impl HitTestClipChainDescriptor {
     fn empty() -> HitTestClipChainDescriptor {
         HitTestClipChainDescriptor {
             parent: None,
             clips: Vec::new(),
         }
@@ -88,69 +93,79 @@ impl HitTestRegion {
             HitTestRegion::RoundedRectangle(rect, radii, ClipMode::ClipOut) =>
                 !rounded_rectangle_contains_point(point, &rect, &radii),
         }
     }
 }
 
 pub struct HitTester {
     runs: Vec<HitTestingRun>,
-    nodes: Vec<HitTestClipScrollNode>,
+    spatial_nodes: Vec<HitTestSpatialNode>,
+    clip_nodes: Vec<HitTestClipNode>,
     clip_chains: Vec<HitTestClipChainDescriptor>,
-    pipeline_root_nodes: FastHashMap<PipelineId, ClipScrollNodeIndex>,
+    pipeline_root_nodes: FastHashMap<PipelineId, SpatialNodeIndex>,
 }
 
 impl HitTester {
     pub fn new(
         runs: &Vec<HitTestingRun>,
         clip_scroll_tree: &ClipScrollTree,
         clip_store: &ClipStore
     ) -> HitTester {
         let mut hit_tester = HitTester {
             runs: runs.clone(),
-            nodes: Vec::new(),
+            spatial_nodes: Vec::new(),
+            clip_nodes: Vec::new(),
             clip_chains: Vec::new(),
             pipeline_root_nodes: FastHashMap::default(),
         };
         hit_tester.read_clip_scroll_tree(clip_scroll_tree, clip_store);
         hit_tester
     }
 
     fn read_clip_scroll_tree(
         &mut self,
         clip_scroll_tree: &ClipScrollTree,
         clip_store: &ClipStore
     ) {
-        self.nodes.clear();
+        self.spatial_nodes.clear();
         self.clip_chains.clear();
         self.clip_chains.resize(
             clip_scroll_tree.clip_chains.len(),
             HitTestClipChainDescriptor::empty()
         );
 
-        for (index, node) in clip_scroll_tree.nodes.iter().enumerate() {
-            let index = ClipScrollNodeIndex(index);
+        for (index, node) in clip_scroll_tree.spatial_nodes.iter().enumerate() {
+            let index = SpatialNodeIndex(index);
 
             // If we haven't already seen a node for this pipeline, record this one as the root
             // node.
             self.pipeline_root_nodes.entry(node.pipeline_id).or_insert(index);
 
-            self.nodes.push(HitTestClipScrollNode {
+            self.spatial_nodes.push(HitTestSpatialNode {
                 pipeline_id: node.pipeline_id,
-                regions: get_regions_for_clip_scroll_node(node, clip_store),
                 world_content_transform: node.world_content_transform,
                 world_viewport_transform: node.world_viewport_transform,
             });
+        }
 
-            if let NodeType::Clip { clip_chain_index, .. } = node.node_type {
-              let clip_chain = self.clip_chains.get_mut(clip_chain_index.0).unwrap();
-              clip_chain.parent =
-                  clip_scroll_tree.get_clip_chain(clip_chain_index).parent_index;
-              clip_chain.clips = vec![index];
-            }
+        for (index, node) in clip_scroll_tree.clip_nodes.iter().enumerate() {
+            let regions = match get_regions_for_clip_node(node, clip_store) {
+                Some(regions) => regions,
+                None => continue,
+            };
+            self.clip_nodes.push(HitTestClipNode {
+                spatial_node: node.spatial_node,
+                regions,
+            });
+
+             let clip_chain = self.clip_chains.get_mut(node.clip_chain_index.0).unwrap();
+             clip_chain.parent =
+                 clip_scroll_tree.get_clip_chain(node.clip_chain_index).parent_index;
+             clip_chain.clips = vec![ClipNodeIndex(index)];
         }
 
         for descriptor in &clip_scroll_tree.clip_chains_descriptors {
             let clip_chain = self.clip_chains.get_mut(descriptor.index.0).unwrap();
             clip_chain.parent = clip_scroll_tree.get_clip_chain(descriptor.index).parent_index;
             clip_chain.clips = descriptor.clips.clone();
         }
     }
@@ -172,38 +187,38 @@ impl HitTester {
         };
 
         if !parent_clipped_in {
             test.set_in_clip_chain_cache(clip_chain_index, ClippedIn::NotClippedIn);
             return false;
         }
 
         for clip_node_index in &descriptor.clips {
-            if !self.is_point_clipped_in_for_node(point, *clip_node_index, test) {
+            if !self.is_point_clipped_in_for_clip_node(point, *clip_node_index, test) {
                 test.set_in_clip_chain_cache(clip_chain_index, ClippedIn::NotClippedIn);
                 return false;
             }
         }
 
         test.set_in_clip_chain_cache(clip_chain_index, ClippedIn::ClippedIn);
         true
     }
 
-    fn is_point_clipped_in_for_node(
+    fn is_point_clipped_in_for_clip_node(
         &self,
         point: WorldPoint,
-        node_index: ClipScrollNodeIndex,
+        node_index: ClipNodeIndex,
         test: &mut HitTest
     ) -> bool {
         if let Some(clipped_in) = test.node_cache.get(&node_index) {
             return *clipped_in == ClippedIn::ClippedIn;
         }
 
-        let node = &self.nodes[node_index.0];
-        let transform = node.world_viewport_transform;
+        let node = &self.clip_nodes[node_index.0];
+        let transform = self.spatial_nodes[node.spatial_node.0].world_viewport_transform;
         let transformed_point = match transform.inverse() {
             Some(inverted) => inverted.transform_point2d(&point),
             None => {
                 test.node_cache.insert(node_index, ClippedIn::NotClippedIn);
                 return false;
             }
         };
 
@@ -213,22 +228,22 @@ impl HitTester {
                 return false;
             }
         }
 
         test.node_cache.insert(node_index, ClippedIn::ClippedIn);
         true
     }
 
-    pub fn find_node_under_point(&self, mut test: HitTest) -> Option<ClipScrollNodeIndex> {
+    pub fn find_node_under_point(&self, mut test: HitTest) -> Option<SpatialNodeIndex> {
         let point = test.get_absolute_point(self);
 
         for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() {
             let scroll_node_id = clip_and_scroll.scroll_node_id;
-            let scroll_node = &self.nodes[scroll_node_id.0];
+            let scroll_node = &self.spatial_nodes[scroll_node_id.0];
             let transform = scroll_node.world_content_transform;
             let point_in_layer = match transform.inverse() {
                 Some(inverted) => inverted.transform_point2d(&point),
                 None => continue,
             };
 
             let mut clipped_in = false;
             for item in items.iter().rev() {
@@ -252,17 +267,17 @@ impl HitTester {
     }
 
     pub fn hit_test(&self, mut test: HitTest) -> HitTestResult {
         let point = test.get_absolute_point(self);
 
         let mut result = HitTestResult::default();
         for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() {
             let scroll_node_id = clip_and_scroll.scroll_node_id;
-            let scroll_node = &self.nodes[scroll_node_id.0];
+            let scroll_node = &self.spatial_nodes[scroll_node_id.0];
             let pipeline_id = scroll_node.pipeline_id;
             match (test.pipeline_id, pipeline_id) {
                 (Some(id), node_id) if node_id != id => continue,
                 _ => {},
             }
 
             let transform = scroll_node.world_content_transform;
             let mut facing_backwards: Option<bool> = None;  // will be computed on first use
@@ -291,17 +306,17 @@ impl HitTester {
                         continue;
                     }
                 }
 
                 // We need to calculate the position of the test point relative to the origin of
                 // the pipeline of the hit item. If we cannot get a transformed point, we are
                 // in a situation with an uninvertible transformation so we should just skip this
                 // result.
-                let root_node = &self.nodes[self.pipeline_root_nodes[&pipeline_id].0];
+                let root_node = &self.spatial_nodes[self.pipeline_root_nodes[&pipeline_id].0];
                 let point_in_viewport = match root_node.world_viewport_transform.inverse() {
                     Some(inverted) => inverted.transform_point2d(&point),
                     None => continue,
                 };
 
                 result.items.push(HitTestItem {
                     pipeline: pipeline_id,
                     tag: item.tag,
@@ -313,55 +328,59 @@ impl HitTester {
                 }
             }
         }
 
         result.items.dedup();
         result
     }
 
-    pub fn get_pipeline_root(&self, pipeline_id: PipelineId) -> &HitTestClipScrollNode {
-        &self.nodes[self.pipeline_root_nodes[&pipeline_id].0]
+    pub fn get_pipeline_root(&self, pipeline_id: PipelineId) -> &HitTestSpatialNode {
+        &self.spatial_nodes[self.pipeline_root_nodes[&pipeline_id].0]
     }
 }
 
-fn get_regions_for_clip_scroll_node(
-    node: &ClipScrollNode,
+fn get_regions_for_clip_node(
+    node: &ClipNode,
     clip_store: &ClipStore
-) -> Vec<HitTestRegion> {
-    let clips = match node.node_type {
-        NodeType::Clip{ ref handle, .. } => clip_store.get(handle).clips(),
-        _ => return Vec::new(),
+) -> Option<Vec<HitTestRegion>> {
+    let handle = match node.handle.as_ref() {
+        Some(handle) => handle,
+        None => {
+            warn!("Encountered an empty clip node unexpectedly.");
+            return None;
+        }
     };
 
-    clips.iter().map(|source| {
+    let clips = clip_store.get(handle).clips();
+    Some(clips.iter().map(|source| {
         match source.0 {
             ClipSource::Rectangle(ref rect, mode) => HitTestRegion::Rectangle(*rect, mode),
             ClipSource::RoundedRectangle(ref rect, ref radii, ref mode) =>
                 HitTestRegion::RoundedRectangle(*rect, *radii, *mode),
             ClipSource::Image(ref mask) => HitTestRegion::Rectangle(mask.rect, ClipMode::Clip),
             ClipSource::LineDecoration(_) |
             ClipSource::BoxShadow(_) => {
                 unreachable!("Didn't expect to hit test against BorderCorner / BoxShadow / LineDecoration");
             }
         }
-    }).collect()
+    }).collect())
 }
 
 #[derive(Clone, Copy, PartialEq)]
 enum ClippedIn {
     ClippedIn,
     NotClippedIn,
 }
 
 pub struct HitTest {
     pipeline_id: Option<PipelineId>,
     point: WorldPoint,
     flags: HitTestFlags,
-    node_cache: FastHashMap<ClipScrollNodeIndex, ClippedIn>,
+    node_cache: FastHashMap<ClipNodeIndex, ClippedIn>,
     clip_chain_cache: Vec<Option<ClippedIn>>,
 }
 
 impl HitTest {
     pub fn new(
         pipeline_id: Option<PipelineId>,
         point: WorldPoint,
         flags: HitTestFlags,
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -55,17 +55,17 @@ extern crate serde;
 extern crate thread_profiler;
 
 mod batch;
 mod border;
 mod box_shadow;
 #[cfg(any(feature = "capture", feature = "replay"))]
 mod capture;
 mod clip;
-mod clip_scroll_node;
+mod clip_node;
 mod clip_scroll_tree;
 mod debug_colors;
 #[cfg(feature = "debug_renderer")]
 mod debug_font_data;
 #[cfg(feature = "debug_renderer")]
 mod debug_render;
 #[cfg(feature = "debugger")]
 mod debug_server;
@@ -93,16 +93,17 @@ mod record;
 mod render_backend;
 mod render_task;
 mod renderer;
 mod resource_cache;
 mod scene;
 mod scene_builder;
 mod segment;
 mod shade;
+mod spatial_node;
 mod texture_allocator;
 mod texture_cache;
 mod tiling;
 mod util;
 
 mod shader_source {
     include!(concat!(env!("OUT_DIR"), "/shaders.rs"));
 }
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -1,18 +1,18 @@
 /* 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/. */
 
 use api::{DeviceRect, FilterOp, MixBlendMode, PipelineId, PremultipliedColorF};
 use api::{DeviceIntRect, DeviceIntSize, DevicePoint, LayoutPoint, LayoutRect};
 use api::{DevicePixelScale, PictureIntPoint, PictureIntRect, PictureIntSize};
 use box_shadow::{BLUR_SAMPLE_SCALE};
-use clip_scroll_node::ClipScrollNode;
-use clip_scroll_tree::ClipScrollNodeIndex;
+use spatial_node::SpatialNode;
+use clip_scroll_tree::SpatialNodeIndex;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PrimitiveRunContext};
 use gpu_cache::{GpuCacheHandle};
 use gpu_types::UvRectKind;
 use prim_store::{PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
 use prim_store::{PrimitiveMetadata, ScrollNodeAndClipChain};
 use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle};
 use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use scene::{FilterOpHelpers, SceneProperties};
@@ -145,17 +145,17 @@ pub struct PicturePrimitive {
     pub is_in_3d_context: bool,
     // If requested as a frame output (for rendering
     // pages to a texture), this is the pipeline this
     // picture is the root of.
     pub frame_output_pipeline_id: Option<PipelineId>,
     // The original reference frame ID for this picture.
     // It is only different if this is part of a 3D
     // rendering context.
-    pub reference_frame_index: ClipScrollNodeIndex,
+    pub reference_frame_index: SpatialNodeIndex,
     pub real_local_rect: LayoutRect,
     // An optional cache handle for storing extra data
     // in the GPU cache, depending on the type of
     // picture.
     pub extra_gpu_data_handle: GpuCacheHandle,
 
     // Unique identifier for this picture.
     pub id: PictureId,
@@ -178,17 +178,17 @@ impl PicturePrimitive {
         }
     }
 
     pub fn new_image(
         id: PictureId,
         composite_mode: Option<PictureCompositeMode>,
         is_in_3d_context: bool,
         pipeline_id: PipelineId,
-        reference_frame_index: ClipScrollNodeIndex,
+        reference_frame_index: SpatialNodeIndex,
         frame_output_pipeline_id: Option<PipelineId>,
         apply_local_clip_rect: bool,
     ) -> Self {
         PicturePrimitive {
             runs: Vec::new(),
             surface: None,
             secondary_render_task_id: None,
             composite_mode,
@@ -585,72 +585,72 @@ impl PicturePrimitive {
             }
         }
     }
 }
 
 // Calculate a single screen-space UV for a picture.
 fn calculate_screen_uv(
     local_pos: &LayoutPoint,
-    clip_scroll_node: &ClipScrollNode,
+    spatial_node: &SpatialNode,
     rendered_rect: &DeviceRect,
     device_pixel_scale: DevicePixelScale,
 ) -> DevicePoint {
-    let world_pos = clip_scroll_node
+    let world_pos = spatial_node
         .world_content_transform
         .transform_point2d(local_pos);
 
     let mut device_pos = world_pos * device_pixel_scale;
 
     // Apply snapping for axis-aligned scroll nodes, as per prim_shared.glsl.
-    if clip_scroll_node.transform_kind == TransformedRectKind::AxisAligned {
+    if spatial_node.transform_kind == TransformedRectKind::AxisAligned {
         device_pos.x = (device_pos.x + 0.5).floor();
         device_pos.y = (device_pos.y + 0.5).floor();
     }
 
     DevicePoint::new(
         (device_pos.x - rendered_rect.origin.x) / rendered_rect.size.width,
         (device_pos.y - rendered_rect.origin.y) / rendered_rect.size.height,
     )
 }
 
 // Calculate a UV rect within an image based on the screen space
 // vertex positions of a picture.
 fn calculate_uv_rect_kind(
     local_rect: &LayoutRect,
-    clip_scroll_node: &ClipScrollNode,
+    spatial_node: &SpatialNode,
     rendered_rect: &DeviceIntRect,
     device_pixel_scale: DevicePixelScale,
 ) -> UvRectKind {
     let rendered_rect = rendered_rect.to_f32();
 
     let top_left = calculate_screen_uv(
         &local_rect.origin,
-        clip_scroll_node,
+        spatial_node,
         &rendered_rect,
         device_pixel_scale,
     );
 
     let top_right = calculate_screen_uv(
         &local_rect.top_right(),
-        clip_scroll_node,
+        spatial_node,
         &rendered_rect,
         device_pixel_scale,
     );
 
     let bottom_left = calculate_screen_uv(
         &local_rect.bottom_left(),
-        clip_scroll_node,
+        spatial_node,
         &rendered_rect,
         device_pixel_scale,
     );
 
     let bottom_right = calculate_screen_uv(
         &local_rect.bottom_right(),
-        clip_scroll_node,
+        spatial_node,
         &rendered_rect,
         device_pixel_scale,
     );
 
     UvRectKind::Quad {
         top_left,
         top_right,
         bottom_left,
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -182,69 +182,69 @@ fn new_ct_font_with_variations(cg_font: 
         let axes: CFArray<CFDictionary> = TCFType::wrap_under_create_rule(axes_ref);
         let mut vals: Vec<(CFString, CFNumber)> = Vec::with_capacity(variations.len() as usize);
         for axis in axes.iter() {
             if !axis.instance_of::<CFDictionary>() {
                 return ct_font;
             }
             let tag_val = match axis.find(kCTFontVariationAxisIdentifierKey as *const _) {
                 Some(tag_ptr) => {
-                    let tag: CFNumber = TCFType::wrap_under_get_rule(tag_ptr as CFNumberRef);
+                    let tag: CFNumber = TCFType::wrap_under_get_rule(*tag_ptr as CFNumberRef);
                     if !tag.instance_of::<CFNumber>() {
                         return ct_font;
                     }
                     match tag.to_i64() {
                         Some(val) => val,
                         None => return ct_font,
                     }
                 }
                 None => return ct_font,
             };
             let mut val = match variations.iter().find(|variation| (variation.tag as i64) == tag_val) {
                 Some(variation) => variation.value as f64,
                 None => continue,
             };
 
             let name: CFString = match axis.find(kCTFontVariationAxisNameKey as *const _) {
-                Some(name_ptr) => TCFType::wrap_under_get_rule(name_ptr as CFStringRef),
+                Some(name_ptr) => TCFType::wrap_under_get_rule(*name_ptr as CFStringRef),
                 None => return ct_font,
             };
             if !name.instance_of::<CFString>() {
                 return ct_font;
             }
 
             let min_val = match axis.find(kCTFontVariationAxisMinimumValueKey as *const _) {
                 Some(min_ptr) => {
-                    let min: CFNumber = TCFType::wrap_under_get_rule(min_ptr as CFNumberRef);
+                    let min: CFNumber = TCFType::wrap_under_get_rule(*min_ptr as CFNumberRef);
                     if !min.instance_of::<CFNumber>() {
                         return ct_font;
                     }
                     match min.to_f64() {
                         Some(val) => val,
                         None => return ct_font,
                     }
                 }
                 None => return ct_font,
             };
             let max_val = match axis.find(kCTFontVariationAxisMaximumValueKey as *const _) {
                 Some(max_ptr) => {
-                    let max: CFNumber = TCFType::wrap_under_get_rule(max_ptr as CFNumberRef);
+                    let max: CFNumber = TCFType::wrap_under_get_rule(*max_ptr as CFNumberRef);
                     if !max.instance_of::<CFNumber>() {
                         return ct_font;
                     }
                     match max.to_f64() {
                         Some(val) => val,
                         None => return ct_font,
                     }
                 }
                 None => return ct_font,
             };
             let def_val = match axis.find(kCTFontVariationAxisDefaultValueKey as *const _) {
                 Some(def_ptr) => {
-                    let def: CFNumber = TCFType::wrap_under_get_rule(def_ptr as CFNumberRef);
+                    let def: CFNumber = TCFType::wrap_under_get_rule(*def_ptr as CFNumberRef);
                     if !def.instance_of::<CFNumber>() {
                         return ct_font;
                     }
                     match def.to_f64() {
                         Some(val) => val,
                         None => return ct_font,
                     }
                 }
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -6,18 +6,17 @@ use api::{AlphaType, BorderRadius, BoxSh
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode};
 use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, TileOffset};
 use api::{GlyphRasterSpace, LayoutPoint, LayoutRect, LayoutSize, LayoutToWorldTransform, LayoutVector2D};
 use api::{PipelineId, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat, DeviceIntSideOffsets};
 use api::{BorderWidths, LayoutToWorldScale, NormalBorder};
 use app_units::Au;
 use border::{BorderCacheKey, BorderRenderTaskInfo};
 use box_shadow::BLUR_SAMPLE_SCALE;
-use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
-use clip_scroll_node::ClipScrollNode;
+use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId, SpatialNodeIndex};
 use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
 use clip::{ClipSourcesHandle, ClipWorkItem};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveRunContext;
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::BrushFlags;
@@ -26,34 +25,35 @@ use picture::{PictureCompositeMode, Pict
 #[cfg(debug_assertions)]
 use render_backend::FrameId;
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
 use scene::SceneProperties;
 use segment::SegmentBuilder;
+use spatial_node::SpatialNode;
 use std::{mem, usize};
 use std::sync::Arc;
-use util::{MatrixHelpers, WorldToLayoutFastTransform, calculate_screen_bounding_rect};
+use util::{MatrixHelpers, calculate_screen_bounding_rect};
 use util::{pack_as_float, recycle_vec};
 
 
 const MIN_BRUSH_SPLIT_AREA: f32 = 256.0 * 256.0;
 pub const VECS_PER_SEGMENT: usize = 2;
 
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub struct ScrollNodeAndClipChain {
-    pub scroll_node_id: ClipScrollNodeIndex,
+    pub scroll_node_id: SpatialNodeIndex,
     pub clip_chain_index: ClipChainIndex,
 }
 
 impl ScrollNodeAndClipChain {
     pub fn new(
-        scroll_node_id: ClipScrollNodeIndex,
+        scroll_node_id: SpatialNodeIndex,
         clip_chain_index: ClipChainIndex
     ) -> Self {
         ScrollNodeAndClipChain { scroll_node_id, clip_chain_index }
     }
 }
 
 #[derive(Debug)]
 pub struct PrimitiveRun {
@@ -1237,17 +1237,17 @@ impl PrimitiveStore {
         }
     }
 
     pub fn add_image_picture(
         &mut self,
         composite_mode: Option<PictureCompositeMode>,
         is_in_3d_context: bool,
         pipeline_id: PipelineId,
-        reference_frame_index: ClipScrollNodeIndex,
+        reference_frame_index: SpatialNodeIndex,
         frame_output_pipeline_id: Option<PipelineId>,
         apply_local_clip_rect: bool,
     ) -> PictureIndex {
         let picture = PicturePrimitive::new_image(
             PictureId(self.next_picture_id),
             composite_mode,
             is_in_3d_context,
             pipeline_id,
@@ -1989,17 +1989,16 @@ impl PrimitiveStore {
     }
 
     fn write_brush_segment_description(
         brush: &mut BrushPrimitive,
         metadata: &PrimitiveMetadata,
         prim_run_context: &PrimitiveRunContext,
         clips: &Vec<ClipWorkItem>,
         has_clips_from_other_coordinate_systems: bool,
-        frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) {
         match brush.segment_desc {
             Some(ref segment_desc) => {
                 // If we already have a segment descriptor, only run through the
                 // clips list if we haven't already determined the mask kind.
                 if segment_desc.clip_mask_kind != BrushClipMaskKind::Unknown {
                     return;
@@ -2042,29 +2041,49 @@ impl PrimitiveStore {
 
         // Segment the primitive on all the local-space clip sources that we can.
         for clip_item in clips {
             if clip_item.coordinate_system_id != prim_run_context.scroll_node.coordinate_system_id {
                 continue;
             }
 
             let local_clips = frame_state.clip_store.get_opt(&clip_item.clip_sources).expect("bug");
+            rect_clips_only = rect_clips_only && local_clips.only_rectangular_clips;
+
+            // TODO(gw): We can easily extend the segment builder to support these clip sources in
+            // the future, but they are rarely used.
+            // We must do this check here in case we continue early below.
+            if local_clips.has_image_or_line_decoration_clip {
+                clip_mask_kind = BrushClipMaskKind::Global;
+            }
+
+            // If this clip item is positioned by another positioning node, its relative position
+            // could change during scrolling. This means that we would need to resegment. Instead
+            // of doing that, only segment with clips that have the same positioning node.
+            // TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only
+            // when necessary while scrolling.
+            if clip_item.transform_index != prim_run_context.transform_index {
+                // We don't need to generate a global clip mask for rectangle clips because we are
+                // in the same coordinate system and rectangular clips are handled by the local
+                // clip chain rectangle.
+                if !local_clips.only_rectangular_clips {
+                    clip_mask_kind = BrushClipMaskKind::Global;
+                }
+                continue;
+            }
+
             for &(ref clip, _) in &local_clips.clips {
                 let (local_clip_rect, radius, mode) = match *clip {
                     ClipSource::RoundedRectangle(rect, radii, clip_mode) => {
-                        rect_clips_only = false;
-
                         (rect, Some(radii), clip_mode)
                     }
                     ClipSource::Rectangle(rect, mode) => {
                         (rect, None, mode)
                     }
                     ClipSource::BoxShadow(ref info) => {
-                        rect_clips_only = false;
-
                         // For inset box shadows, we can clip out any
                         // pixels that are inside the shadow region
                         // and are beyond the inner rect, as they can't
                         // be affected by the blur radius.
                         let inner_clip_mode = match info.clip_mode {
                             BoxShadowClipMode::Outset => None,
                             BoxShadowClipMode::Inset => Some(ClipMode::ClipOut),
                         };
@@ -2079,45 +2098,17 @@ impl PrimitiveStore {
                                 -0.5 * info.shadow_rect_alloc_size.width,
                                 -0.5 * info.shadow_rect_alloc_size.height,
                             ),
                             inner_clip_mode,
                         );
 
                         continue;
                     }
-                    ClipSource::LineDecoration(..) |
-                    ClipSource::Image(..) => {
-                        rect_clips_only = false;
-
-                        // TODO(gw): We can easily extend the segment builder
-                        //           to support these clip sources in the
-                        //           future, but they are rarely used.
-                        clip_mask_kind = BrushClipMaskKind::Global;
-                        continue;
-                    }
-                };
-
-                // If the scroll node transforms are different between the clip
-                // node and the primitive, we need to get the clip rect in the
-                // local space of the primitive, in order to generate correct
-                // local segments.
-                let local_clip_rect = if clip_item.transform_index == prim_run_context.scroll_node.transform_index {
-                    local_clip_rect
-                } else {
-                    let clip_transform = frame_context
-                        .transforms[clip_item.transform_index.0 as usize]
-                        .transform;
-                    let prim_transform = &prim_run_context.scroll_node.world_content_transform;
-                    let relative_transform = prim_transform
-                        .inverse()
-                        .unwrap_or(WorldToLayoutFastTransform::identity())
-                        .pre_mul(&clip_transform.into());
-
-                    relative_transform.transform_rect(&local_clip_rect)
+                    ClipSource::LineDecoration(..) | ClipSource::Image(..) => continue,
                 };
 
                 segment_builder.push_clip_rect(local_clip_rect, radius, mode);
             }
         }
 
         if is_large || rect_clips_only {
             match brush.segment_desc {
@@ -2176,17 +2167,16 @@ impl PrimitiveStore {
         };
 
         PrimitiveStore::write_brush_segment_description(
             brush,
             metadata,
             prim_run_context,
             clips,
             has_clips_from_other_coordinate_systems,
-            frame_context,
             frame_state,
         );
 
         let segment_desc = match brush.segment_desc {
             Some(ref mut description) => description,
             None => return false,
         };
         let clip_mask_kind = segment_desc.clip_mask_kind;
@@ -2298,21 +2288,21 @@ impl PrimitiveStore {
                     combined_outer_rect = combined_outer_rect.and_then(|r| r.intersection(&outer));
                 }
                 if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
                     println!("\tfound extra clip with screen bounds {:?}", screen_outer_rect);
                 }
 
                 Arc::new(ClipChainNode {
                     work_item: ClipWorkItem {
-                        transform_index: prim_run_context.scroll_node.transform_index,
+                        transform_index: prim_run_context.transform_index,
                         clip_sources: clip_sources.weak(),
                         coordinate_system_id: prim_coordinate_system_id,
                     },
-                    // The local_clip_rect a property of ClipChain nodes that are ClipScrollNodes.
+                    // The local_clip_rect a property of ClipChain nodes that are ClipNodes.
                     // It's used to calculate a local clipping rectangle before we reach this
                     // point, so we can set it to zero here. It should be unused from this point
                     // on.
                     local_clip_rect: LayoutRect::zero(),
                     screen_inner_rect,
                     screen_outer_rect: screen_outer_rect.unwrap_or(prim_screen_rect),
                     prev: None,
                 })
@@ -2644,17 +2634,17 @@ impl PrimitiveStore {
                 println!("\tpreparing a run of length {} in pipeline {:?}",
                     run.count, pic_context.pipeline_id);
             }
             // TODO(gw): Perhaps we can restructure this to not need to create
             //           a new primitive context for every run (if the hash
             //           lookups ever show up in a profile).
             let scroll_node = &frame_context
                 .clip_scroll_tree
-                .nodes[run.clip_and_scroll.scroll_node_id.0];
+                .spatial_nodes[run.clip_and_scroll.scroll_node_id.0];
             let clip_chain = frame_context
                 .clip_scroll_tree
                 .get_clip_chain(run.clip_and_scroll.clip_chain_index);
 
             // Mark whether this picture contains any complex coordinate
             // systems, due to either the scroll node or the clip-chain.
             pic_state.has_non_root_coord_system |=
                 scroll_node.coordinate_system_id != CoordinateSystemId::root();
@@ -2680,17 +2670,17 @@ impl PrimitiveStore {
                     inv_parent.pre_mul(&scroll_node.world_content_transform)
                 });
 
             let original_relative_transform = pic_context
                 .original_reference_frame_index
                 .and_then(|original_reference_frame_index| {
                     frame_context
                         .clip_scroll_tree
-                        .nodes[original_reference_frame_index.0]
+                        .spatial_nodes[original_reference_frame_index.0]
                         .world_content_transform
                         .inverse()
                 })
                 .map(|inv_parent| {
                     inv_parent.pre_mul(&scroll_node.world_content_transform)
                 });
 
             if run.is_chasing(self.chase_id) {
@@ -2717,16 +2707,17 @@ impl PrimitiveStore {
                 Some(rect) if rect.is_empty() => continue,
                 Some(rect) => rect,
                 None => frame_context.max_local_clip,
             };
 
             let child_prim_run_context = PrimitiveRunContext::new(
                 clip_chain,
                 scroll_node,
+                run.clip_and_scroll.scroll_node_id.transform_index(),
                 local_clip_chain_rect,
             );
 
             for i in 0 .. run.count {
                 let prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
 
                 if let Some(prim_local_rect) = self.prepare_prim_for_render(
                     prim_index,
@@ -2909,17 +2900,17 @@ fn convert_clip_chain_to_clip_vector(
             };
 
             Some(node.work_item.clone())
         })
         .collect()
 }
 
 fn get_local_clip_rect_for_nodes(
-    scroll_node: &ClipScrollNode,
+    scroll_node: &SpatialNode,
     clip_chain: &ClipChain,
 ) -> Option<LayoutRect> {
     ClipChainNodeIter { current: clip_chain.nodes.clone() }
         .fold(
             None,
             |combined_local_clip_rect: Option<LayoutRect>, node| {
                 if node.work_item.coordinate_system_id != scroll_node.coordinate_system_id {
                     return combined_local_clip_rect;
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -9,17 +9,17 @@ use api::{DeviceIntPoint, DevicePixelSca
 use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestFlags, HitTestResult};
 use api::{IdNamespace, LayoutPoint, PipelineId, RenderNotifier, SceneMsg, ScrollClamping};
 use api::{ScrollLocation, ScrollNodeState, TransactionMsg};
 use api::channel::{MsgReceiver, Payload};
 #[cfg(feature = "capture")]
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
-use clip_scroll_tree::{ClipScrollNodeIndex, ClipScrollTree};
+use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
 #[cfg(feature = "debugger")]
 use debug_server;
 use display_list_flattener::DisplayListFlattener;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use hit_test::{HitTest, HitTester};
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
 use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
@@ -84,19 +84,19 @@ struct Document {
     // received from the scene builder thread.
     current: SceneData,
     // The scene with the latest transactions applied, not necessarily built yet.
     // what we will send to the scene builder.
     pending: SceneData,
 
     view: DocumentView,
 
-    /// The ClipScrollTree for this document which tracks both ClipScrollNodes and ClipChains.
-    /// This is stored here so that we are able to preserve scrolling positions between
-    /// rendered frames.
+    /// The ClipScrollTree for this document which tracks SpatialNodes, ClipNodes, and ClipChains.
+    /// This is stored here so that we are able to preserve scrolling positions between rendered
+    /// frames.
     clip_scroll_tree: ClipScrollTree,
 
     /// The id of the current frame.
     frame_id: FrameId,
 
     /// A configuration object for the FrameBuilder that we produce.
     frame_builder_config: FrameBuilderConfig,
 
@@ -308,17 +308,17 @@ impl Document {
         self.clip_scroll_tree
             .discard_frame_state_for_pipeline(pipeline_id);
     }
 
     /// Returns true if any nodes actually changed position or false otherwise.
     pub fn scroll_nearest_scrolling_ancestor(
         &mut self,
         scroll_location: ScrollLocation,
-        scroll_node_index: Option<ClipScrollNodeIndex>,
+        scroll_node_index: Option<SpatialNodeIndex>,
     ) -> bool {
         self.clip_scroll_tree.scroll_nearest_scrolling_ancestor(scroll_location, scroll_node_index)
     }
 
     /// Returns true if the node actually changed position or false otherwise.
     pub fn scroll_node(
         &mut self,
         origin: LayoutPoint,
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/spatial_node.rs
@@ -0,0 +1,706 @@
+
+/* 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/. */
+
+use api::{ExternalScrollId, LayoutPixel, LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
+use api::{LayoutVector2D, PipelineId, PropertyBinding, ScrollClamping, ScrollLocation};
+use api::{ScrollSensitivity, StickyOffsetBounds};
+use clip_scroll_tree::{CoordinateSystemId, SpatialNodeIndex, TransformUpdateState};
+use euclid::SideOffsets2D;
+use gpu_types::{TransformData, TransformIndex, TransformPalette};
+use scene::SceneProperties;
+use util::{LayoutFastTransform, LayoutToWorldFastTransform, TransformedRectKind};
+
+#[derive(Clone, Debug)]
+pub enum SpatialNodeType {
+    /// A special kind of node that adjusts its position based on the position
+    /// of its parent node and a given set of sticky positioning offset bounds.
+    /// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here:
+    /// https://www.w3.org/TR/css-position-3/#sticky-pos
+    StickyFrame(StickyFrameInfo),
+
+    /// Transforms it's content, but doesn't clip it. Can also be adjusted
+    /// by scroll events or setting scroll offsets.
+    ScrollFrame(ScrollFrameInfo),
+
+    /// A reference frame establishes a new coordinate space in the tree.
+    ReferenceFrame(ReferenceFrameInfo),
+
+    /// An empty node, used to pad the ClipScrollTree's array of nodes so that
+    /// we can immediately use each assigned SpatialNodeIndex. After display
+    /// list flattening this node type should never be used.
+    Empty,
+}
+
+impl SpatialNodeType {
+    fn is_reference_frame(&self) -> bool {
+        match *self {
+            SpatialNodeType::ReferenceFrame(_) => true,
+            _ => false,
+        }
+    }
+}
+
+/// Contains information common among all types of ClipScrollTree nodes.
+#[derive(Clone, Debug)]
+pub struct SpatialNode {
+    /// The transformation for this viewport in world coordinates is the transformation for
+    /// our parent reference frame, plus any accumulated scrolling offsets from nodes
+    /// between our reference frame and this node. For reference frames, we also include
+    /// whatever local transformation this reference frame provides.
+    pub world_viewport_transform: LayoutToWorldFastTransform,
+
+    /// World transform for content transformed by this node.
+    pub world_content_transform: LayoutToWorldFastTransform,
+
+    /// The current transform kind of world_content_transform.
+    pub transform_kind: TransformedRectKind,
+
+    /// Pipeline that this layer belongs to
+    pub pipeline_id: PipelineId,
+
+    /// Parent layer. If this is None, we are the root node.
+    pub parent: Option<SpatialNodeIndex>,
+
+    /// Child layers
+    pub children: Vec<SpatialNodeIndex>,
+
+    /// The type of this node and any data associated with that node type.
+    pub node_type: SpatialNodeType,
+
+    /// True if this node is transformed by an invertible transform.  If not, display items
+    /// transformed by this node will not be displayed and display items not transformed by this
+    /// node will not be clipped by clips that are transformed by this node.
+    pub invertible: bool,
+
+    /// The axis-aligned coordinate system id of this node.
+    pub coordinate_system_id: CoordinateSystemId,
+
+    /// The transformation from the coordinate system which established our compatible coordinate
+    /// system (same coordinate system id) and us. This can change via scroll offsets and via new
+    /// reference frame transforms.
+    pub coordinate_system_relative_transform: LayoutFastTransform,
+}
+
+impl SpatialNode {
+    pub fn new(
+        pipeline_id: PipelineId,
+        parent_index: Option<SpatialNodeIndex>,
+        node_type: SpatialNodeType,
+    ) -> Self {
+        SpatialNode {
+            world_viewport_transform: LayoutToWorldFastTransform::identity(),
+            world_content_transform: LayoutToWorldFastTransform::identity(),
+            transform_kind: TransformedRectKind::AxisAligned,
+            parent: parent_index,
+            children: Vec::new(),
+            pipeline_id,
+            node_type,
+            invertible: true,
+            coordinate_system_id: CoordinateSystemId(0),
+            coordinate_system_relative_transform: LayoutFastTransform::identity(),
+        }
+    }
+
+    pub fn empty() -> SpatialNode {
+        Self::new(PipelineId::dummy(), None, SpatialNodeType::Empty)
+    }
+
+    pub fn new_scroll_frame(
+        pipeline_id: PipelineId,
+        parent_index: SpatialNodeIndex,
+        external_id: Option<ExternalScrollId>,
+        frame_rect: &LayoutRect,
+        content_size: &LayoutSize,
+        scroll_sensitivity: ScrollSensitivity,
+    ) -> Self {
+        let node_type = SpatialNodeType::ScrollFrame(ScrollFrameInfo::new(
+                *frame_rect,
+                scroll_sensitivity,
+                LayoutSize::new(
+                    (content_size.width - frame_rect.size.width).max(0.0),
+                    (content_size.height - frame_rect.size.height).max(0.0)
+                ),
+                external_id,
+            )
+        );
+
+        Self::new(pipeline_id, Some(parent_index), node_type)
+    }
+
+    pub fn new_reference_frame(
+        parent_index: Option<SpatialNodeIndex>,
+        source_transform: Option<PropertyBinding<LayoutTransform>>,
+        source_perspective: Option<LayoutTransform>,
+        origin_in_parent_reference_frame: LayoutVector2D,
+        pipeline_id: PipelineId,
+    ) -> Self {
+        let identity = LayoutTransform::identity();
+        let source_perspective = source_perspective.map_or_else(
+            LayoutFastTransform::identity, |perspective| perspective.into());
+        let info = ReferenceFrameInfo {
+            resolved_transform: LayoutFastTransform::identity(),
+            source_transform: source_transform.unwrap_or(PropertyBinding::Value(identity)),
+            source_perspective,
+            origin_in_parent_reference_frame,
+            invertible: true,
+        };
+        Self::new(pipeline_id, parent_index, SpatialNodeType:: ReferenceFrame(info))
+    }
+
+    pub fn new_sticky_frame(
+        parent_index: SpatialNodeIndex,
+        sticky_frame_info: StickyFrameInfo,
+        pipeline_id: PipelineId,
+    ) -> Self {
+        Self::new(pipeline_id, Some(parent_index), SpatialNodeType::StickyFrame(sticky_frame_info))
+    }
+
+
+    pub fn add_child(&mut self, child: SpatialNodeIndex) {
+        self.children.push(child);
+    }
+
+    pub fn apply_old_scrolling_state(&mut self, old_scroll_info: &ScrollFrameInfo) {
+        match self.node_type {
+            SpatialNodeType::ScrollFrame(ref mut scrolling) => {
+                *scrolling = scrolling.combine_with_old_scroll_info(old_scroll_info);
+            }
+            _ if old_scroll_info.offset != LayoutVector2D::zero() => {
+                warn!("Tried to scroll a non-scroll node.")
+            }
+            _ => {}
+        }
+    }
+
+    pub fn set_scroll_origin(&mut self, origin: &LayoutPoint, clamp: ScrollClamping) -> bool {
+        let scrollable_size = self.scrollable_size();
+        let scrollable_width = scrollable_size.width;
+        let scrollable_height = scrollable_size.height;
+
+        let scrolling = match self.node_type {
+            SpatialNodeType::ScrollFrame(ref mut scrolling) => scrolling,
+            _ => {
+                warn!("Tried to scroll a non-scroll node.");
+                return false;
+            }
+        };
+
+        let new_offset = match clamp {
+            ScrollClamping::ToContentBounds => {
+                if scrollable_height <= 0. && scrollable_width <= 0. {
+                    return false;
+                }
+
+                let origin = LayoutPoint::new(origin.x.max(0.0), origin.y.max(0.0));
+                LayoutVector2D::new(
+                    (-origin.x).max(-scrollable_width).min(0.0).round(),
+                    (-origin.y).max(-scrollable_height).min(0.0).round(),
+                )
+            }
+            ScrollClamping::NoClamping => LayoutPoint::zero() - *origin,
+        };
+
+        if new_offset == scrolling.offset {
+            return false;
+        }
+
+        scrolling.offset = new_offset;
+        true
+    }
+
+    pub fn mark_uninvertible(&mut self) {
+        self.invertible = false;
+        self.world_content_transform = LayoutToWorldFastTransform::identity();
+        self.world_viewport_transform = LayoutToWorldFastTransform::identity();
+    }
+
+    pub fn push_gpu_data(
+        &mut self,
+        transform_palette: &mut TransformPalette,
+        node_index: SpatialNodeIndex,
+    ) {
+        let transform_index = TransformIndex(node_index.0 as u32);
+        if !self.invertible {
+            transform_palette.set(transform_index, TransformData::invalid());
+            return;
+        }
+
+        let inv_transform = match self.world_content_transform.inverse() {
+            Some(inverted) => inverted.to_transform(),
+            None => {
+                transform_palette.set(transform_index, TransformData::invalid());
+                return;
+            }
+        };
+
+        let data = TransformData {
+            transform: self.world_content_transform.into(),
+            inv_transform,
+        };
+
+        // Write the data that will be made available to the GPU for this node.
+        transform_palette.set(transform_index, data);
+    }
+
+    pub fn update(
+        &mut self,
+        state: &mut TransformUpdateState,
+        next_coordinate_system_id: &mut CoordinateSystemId,
+        scene_properties: &SceneProperties,
+    ) {
+        // If any of our parents was not rendered, we are not rendered either and can just
+        // quit here.
+        if !state.invertible {
+            self.mark_uninvertible();
+            return;
+        }
+
+        self.update_transform(state, next_coordinate_system_id, scene_properties);
+
+        self.transform_kind = if self.world_content_transform.preserves_2d_axis_alignment() {
+            TransformedRectKind::AxisAligned
+        } else {
+            TransformedRectKind::Complex
+        };
+
+        // If this node is a reference frame, we check if it has a non-invertible matrix.
+        // For non-reference-frames we assume that they will produce only additional
+        // translations which should be invertible.
+        match self.node_type {
+            SpatialNodeType::ReferenceFrame(info) if !info.invertible => {
+                self.mark_uninvertible();
+                return;
+            }
+            _ => self.invertible = true,
+        }
+    }
+
+    pub fn update_transform(
+        &mut self,
+        state: &mut TransformUpdateState,
+        next_coordinate_system_id: &mut CoordinateSystemId,
+        scene_properties: &SceneProperties,
+    ) {
+        if self.node_type.is_reference_frame() {
+            self.update_transform_for_reference_frame(
+                state,
+                next_coordinate_system_id,
+                scene_properties
+            );
+            return;
+        }
+
+        // We calculate this here to avoid a double-borrow later.
+        let sticky_offset = self.calculate_sticky_offset(
+            &state.nearest_scrolling_ancestor_offset,
+            &state.nearest_scrolling_ancestor_viewport,
+        );
+
+        // The transformation for the bounds of our viewport is the parent reference frame
+        // transform, plus any accumulated scroll offset from our parents, plus any offset
+        // provided by our own sticky positioning.
+        let accumulated_offset = state.parent_accumulated_scroll_offset + sticky_offset;
+        self.world_viewport_transform = if accumulated_offset != LayoutVector2D::zero() {
+            state.parent_reference_frame_transform.pre_translate(&accumulated_offset)
+        } else {
+            state.parent_reference_frame_transform
+        };
+
+        // The transformation for any content inside of us is the viewport transformation, plus
+        // whatever scrolling offset we supply as well.
+        let scroll_offset = self.scroll_offset();
+        self.world_content_transform = if scroll_offset != LayoutVector2D::zero() {
+            self.world_viewport_transform.pre_translate(&scroll_offset)
+        } else {
+            self.world_viewport_transform
+        };
+
+        let added_offset = state.parent_accumulated_scroll_offset + sticky_offset + scroll_offset;
+        self.coordinate_system_relative_transform =
+            state.coordinate_system_relative_transform.offset(added_offset);
+
+        if let SpatialNodeType::StickyFrame(ref mut info) = self.node_type {
+            info.current_offset = sticky_offset;
+        }
+
+        self.coordinate_system_id = state.current_coordinate_system_id;
+    }
+
+    pub fn update_transform_for_reference_frame(
+        &mut self,
+        state: &mut TransformUpdateState,
+        next_coordinate_system_id: &mut CoordinateSystemId,
+        scene_properties: &SceneProperties,
+    ) {
+        let info = match self.node_type {
+            SpatialNodeType::ReferenceFrame(ref mut info) => info,
+            _ => unreachable!("Called update_transform_for_reference_frame on non-ReferenceFrame"),
+        };
+
+        // Resolve the transform against any property bindings.
+        let source_transform = scene_properties.resolve_layout_transform(&info.source_transform);
+        info.resolved_transform =
+            LayoutFastTransform::with_vector(info.origin_in_parent_reference_frame)
+            .pre_mul(&source_transform.into())
+            .pre_mul(&info.source_perspective);
+
+        // The transformation for this viewport in world coordinates is the transformation for
+        // our parent reference frame, plus any accumulated scrolling offsets from nodes
+        // between our reference frame and this node. Finally, we also include
+        // whatever local transformation this reference frame provides.
+        let relative_transform = info.resolved_transform
+            .post_translate(state.parent_accumulated_scroll_offset)
+            .to_transform()
+            .with_destination::<LayoutPixel>();
+        self.world_viewport_transform =
+            state.parent_reference_frame_transform.pre_mul(&relative_transform.into());
+        self.world_content_transform = self.world_viewport_transform;
+
+        info.invertible = self.world_viewport_transform.is_invertible();
+        if !info.invertible {
+            return;
+        }
+
+        // Try to update our compatible coordinate system transform. If we cannot, start a new
+        // incompatible coordinate system.
+        match state.coordinate_system_relative_transform.update(relative_transform) {
+            Some(offset) => self.coordinate_system_relative_transform = offset,
+            None => {
+                self.coordinate_system_relative_transform = LayoutFastTransform::identity();
+                state.current_coordinate_system_id = *next_coordinate_system_id;
+                next_coordinate_system_id.advance();
+            }
+        }
+
+        self.coordinate_system_id = state.current_coordinate_system_id;
+    }
+
+    fn calculate_sticky_offset(
+        &self,
+        viewport_scroll_offset: &LayoutVector2D,
+        viewport_rect: &LayoutRect,
+    ) -> LayoutVector2D {
+        let info = match self.node_type {
+            SpatialNodeType::StickyFrame(ref info) => info,
+            _ => return LayoutVector2D::zero(),
+        };
+
+        if info.margins.top.is_none() && info.margins.bottom.is_none() &&
+            info.margins.left.is_none() && info.margins.right.is_none() {
+            return LayoutVector2D::zero();
+        }
+
+        // The viewport and margins of the item establishes the maximum amount that it can
+        // be offset in order to keep it on screen. Since we care about the relationship
+        // between the scrolled content and unscrolled viewport we adjust the viewport's
+        // position by the scroll offset in order to work with their relative positions on the
+        // page.
+        let sticky_rect = info.frame_rect.translate(viewport_scroll_offset);
+
+        let mut sticky_offset = LayoutVector2D::zero();
+        if let Some(margin) = info.margins.top {
+            let top_viewport_edge = viewport_rect.min_y() + margin;
+            if sticky_rect.min_y() < top_viewport_edge {
+                // If the sticky rect is positioned above the top edge of the viewport (plus margin)
+                // we move it down so that it is fully inside the viewport.
+                sticky_offset.y = top_viewport_edge - sticky_rect.min_y();
+            } else if info.previously_applied_offset.y > 0.0 &&
+                sticky_rect.min_y() > top_viewport_edge {
+                // However, if the sticky rect is positioned *below* the top edge of the viewport
+                // and there is already some offset applied to the sticky rect's position, then
+                // we need to move it up so that it remains at the correct position. This
+                // makes sticky_offset.y negative and effectively reduces the amount of the
+                // offset that was already applied. We limit the reduction so that it can, at most,
+                // cancel out the already-applied offset, but should never end up adjusting the
+                // position the other way.
+                sticky_offset.y = top_viewport_edge - sticky_rect.min_y();
+                sticky_offset.y = sticky_offset.y.max(-info.previously_applied_offset.y);
+            }
+            debug_assert!(sticky_offset.y + info.previously_applied_offset.y >= 0.0);
+        }
+
+        // If we don't have a sticky-top offset (sticky_offset.y + info.previously_applied_offset.y
+        // == 0), or if we have a previously-applied bottom offset (previously_applied_offset.y < 0)
+        // then we check for handling the bottom margin case.
+        if sticky_offset.y + info.previously_applied_offset.y <= 0.0 {
+            if let Some(margin) = info.margins.bottom {
+                // Same as the above case, but inverted for bottom-sticky items. Here
+                // we adjust items upwards, resulting in a negative sticky_offset.y,
+                // or reduce the already-present upward adjustment, resulting in a positive
+                // sticky_offset.y.
+                let bottom_viewport_edge = viewport_rect.max_y() - margin;
+                if sticky_rect.max_y() > bottom_viewport_edge {
+                    sticky_offset.y = bottom_viewport_edge - sticky_rect.max_y();
+                } else if info.previously_applied_offset.y < 0.0 &&
+                    sticky_rect.max_y() < bottom_viewport_edge {
+                    sticky_offset.y = bottom_viewport_edge - sticky_rect.max_y();
+                    sticky_offset.y = sticky_offset.y.min(-info.previously_applied_offset.y);
+                }
+                debug_assert!(sticky_offset.y + info.previously_applied_offset.y <= 0.0);
+            }
+        }
+
+        // Same as above, but for the x-axis.
+        if let Some(margin) = info.margins.left {
+            let left_viewport_edge = viewport_rect.min_x() + margin;
+            if sticky_rect.min_x() < left_viewport_edge {
+                sticky_offset.x = left_viewport_edge - sticky_rect.min_x();
+            } else if info.previously_applied_offset.x > 0.0 &&
+                sticky_rect.min_x() > left_viewport_edge {
+                sticky_offset.x = left_viewport_edge - sticky_rect.min_x();
+                sticky_offset.x = sticky_offset.x.max(-info.previously_applied_offset.x);
+            }
+            debug_assert!(sticky_offset.x + info.previously_applied_offset.x >= 0.0);
+        }
+
+        if sticky_offset.x + info.previously_applied_offset.x <= 0.0 {
+            if let Some(margin) = info.margins.right {
+                let right_viewport_edge = viewport_rect.max_x() - margin;
+                if sticky_rect.max_x() > right_viewport_edge {
+                    sticky_offset.x = right_viewport_edge - sticky_rect.max_x();
+                } else if info.previously_applied_offset.x < 0.0 &&
+                    sticky_rect.max_x() < right_viewport_edge {
+                    sticky_offset.x = right_viewport_edge - sticky_rect.max_x();
+                    sticky_offset.x = sticky_offset.x.min(-info.previously_applied_offset.x);
+                }
+                debug_assert!(sticky_offset.x + info.previously_applied_offset.x <= 0.0);
+            }
+        }
+
+        // The total "sticky offset" (which is the sum that was already applied by
+        // the calling code, stored in info.previously_applied_offset, and the extra amount we
+        // computed as a result of scrolling, stored in sticky_offset) needs to be
+        // clamped to the provided bounds.
+        let clamp_adjusted = |value: f32, adjust: f32, bounds: &StickyOffsetBounds| {
+            (value + adjust).max(bounds.min).min(bounds.max) - adjust
+        };
+        sticky_offset.y = clamp_adjusted(sticky_offset.y,
+                                         info.previously_applied_offset.y,
+                                         &info.vertical_offset_bounds);
+        sticky_offset.x = clamp_adjusted(sticky_offset.x,
+                                         info.previously_applied_offset.x,
+                                         &info.horizontal_offset_bounds);
+
+        sticky_offset
+    }
+
+    pub fn prepare_state_for_children(&self, state: &mut TransformUpdateState) {
+        if !self.invertible {
+            state.invertible = false;
+            return;
+        }
+
+        // The transformation we are passing is the transformation of the parent
+        // reference frame and the offset is the accumulated offset of all the nodes
+        // between us and the parent reference frame. If we are a reference frame,
+        // we need to reset both these values.
+        match self.node_type {
+            SpatialNodeType::StickyFrame(ref info) => {
+                // We don't translate the combined rect by the sticky offset, because sticky
+                // offsets actually adjust the node position itself, whereas scroll offsets
+                // only apply to contents inside the node.
+                state.parent_accumulated_scroll_offset =
+                    info.current_offset + state.parent_accumulated_scroll_offset;
+            }
+            SpatialNodeType::ScrollFrame(ref scrolling) => {
+                state.parent_accumulated_scroll_offset =
+                    scrolling.offset + state.parent_accumulated_scroll_offset;
+                state.nearest_scrolling_ancestor_offset = scrolling.offset;
+                state.nearest_scrolling_ancestor_viewport = scrolling.viewport_rect;
+            }
+            SpatialNodeType::ReferenceFrame(ref info) => {
+                state.parent_reference_frame_transform = self.world_viewport_transform;
+                state.parent_accumulated_scroll_offset = LayoutVector2D::zero();
+                state.coordinate_system_relative_transform =
+                    self.coordinate_system_relative_transform.clone();
+                let translation = -info.origin_in_parent_reference_frame;
+                state.nearest_scrolling_ancestor_viewport =
+                    state.nearest_scrolling_ancestor_viewport
+                       .translate(&translation);
+            }
+            SpatialNodeType::Empty => unreachable!("Empty node remaining in ClipScrollTree."),
+        }
+    }
+
+    pub fn scrollable_size(&self) -> LayoutSize {
+        match self.node_type {
+           SpatialNodeType::ScrollFrame(state) => state.scrollable_size,
+            _ => LayoutSize::zero(),
+        }
+    }
+
+    pub fn scroll(&mut self, scroll_location: ScrollLocation) -> bool {
+        let scrolling = match self.node_type {
+            SpatialNodeType::ScrollFrame(ref mut scrolling) => scrolling,
+            _ => return false,
+        };
+
+        let delta = match scroll_location {
+            ScrollLocation::Delta(delta) => delta,
+            ScrollLocation::Start => {
+                if scrolling.offset.y.round() >= 0.0 {
+                    // Nothing to do on this layer.
+                    return false;
+                }
+
+                scrolling.offset.y = 0.0;
+                return true;
+            }
+            ScrollLocation::End => {
+                let end_pos = -scrolling.scrollable_size.height;
+                if scrolling.offset.y.round() <= end_pos {
+                    // Nothing to do on this layer.
+                    return false;
+                }
+
+                scrolling.offset.y = end_pos;
+                return true;
+            }
+        };
+
+        let scrollable_width = scrolling.scrollable_size.width;
+        let scrollable_height = scrolling.scrollable_size.height;
+        let original_layer_scroll_offset = scrolling.offset;
+
+        if scrollable_width > 0. {
+            scrolling.offset.x = (scrolling.offset.x + delta.x)
+                .min(0.0)
+                .max(-scrollable_width)
+                .round();
+        }
+
+        if scrollable_height > 0. {
+            scrolling.offset.y = (scrolling.offset.y + delta.y)
+                .min(0.0)
+                .max(-scrollable_height)
+                .round();
+        }
+
+        scrolling.offset != original_layer_scroll_offset
+    }
+
+    pub fn scroll_offset(&self) -> LayoutVector2D {
+        match self.node_type {
+            SpatialNodeType::ScrollFrame(ref scrolling) => scrolling.offset,
+            _ => LayoutVector2D::zero(),
+        }
+    }
+
+    pub fn matches_external_id(&self, external_id: ExternalScrollId) -> bool {
+        match self.node_type {
+            SpatialNodeType::ScrollFrame(info) if info.external_id == Some(external_id) => true,
+            _ => false,
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct ScrollFrameInfo {
+    /// The rectangle of the viewport of this scroll frame. This is important for
+    /// positioning of items inside child StickyFrames.
+    pub viewport_rect: LayoutRect,
+
+    pub offset: LayoutVector2D,
+    pub scroll_sensitivity: ScrollSensitivity,
+
+    /// Amount that this ScrollFrame can scroll in both directions.
+    pub scrollable_size: LayoutSize,
+
+    /// An external id to identify this scroll frame to API clients. This
+    /// allows setting scroll positions via the API without relying on ClipsIds
+    /// which may change between frames.
+    pub external_id: Option<ExternalScrollId>,
+
+}
+
+/// Manages scrolling offset.
+impl ScrollFrameInfo {
+    pub fn new(
+        viewport_rect: LayoutRect,
+        scroll_sensitivity: ScrollSensitivity,
+        scrollable_size: LayoutSize,
+        external_id: Option<ExternalScrollId>,
+    ) -> ScrollFrameInfo {
+        ScrollFrameInfo {
+            viewport_rect,
+            offset: LayoutVector2D::zero(),
+            scroll_sensitivity,
+            scrollable_size,
+            external_id,
+        }
+    }
+
+    pub fn sensitive_to_input_events(&self) -> bool {
+        match self.scroll_sensitivity {
+            ScrollSensitivity::ScriptAndInputEvents => true,
+            ScrollSensitivity::Script => false,
+        }
+    }
+
+    pub fn combine_with_old_scroll_info(
+        self,
+        old_scroll_info: &ScrollFrameInfo
+    ) -> ScrollFrameInfo {
+        ScrollFrameInfo {
+            viewport_rect: self.viewport_rect,
+            offset: old_scroll_info.offset,
+            scroll_sensitivity: self.scroll_sensitivity,
+            scrollable_size: self.scrollable_size,
+            external_id: self.external_id,
+        }
+    }
+}
+
+/// Contains information about reference frames.
+#[derive(Copy, Clone, Debug)]
+pub struct ReferenceFrameInfo {
+    /// The transformation that establishes this reference frame, relative to the parent
+    /// reference frame. The origin of the reference frame is included in the transformation.
+    pub resolved_transform: LayoutFastTransform,
+
+    /// The source transform and perspective matrices provided by the stacking context
+    /// that forms this reference frame. We maintain the property binding information
+    /// here so that we can resolve the animated transform and update the tree each
+    /// frame.
+    pub source_transform: PropertyBinding<LayoutTransform>,
+    pub source_perspective: LayoutFastTransform,
+
+    /// The original, not including the transform and relative to the parent reference frame,
+    /// origin of this reference frame. This is already rolled into the `transform' property, but
+    /// we also store it here to properly transform the viewport for sticky positioning.
+    pub origin_in_parent_reference_frame: LayoutVector2D,
+
+    /// True if the resolved transform is invertible.
+    pub invertible: bool,
+}
+
+#[derive(Clone, Debug)]
+pub struct StickyFrameInfo {
+    pub frame_rect: LayoutRect,
+    pub margins: SideOffsets2D<Option<f32>>,
+    pub vertical_offset_bounds: StickyOffsetBounds,
+    pub horizontal_offset_bounds: StickyOffsetBounds,
+    pub previously_applied_offset: LayoutVector2D,
+    pub current_offset: LayoutVector2D,
+}
+
+impl StickyFrameInfo {
+    pub fn new(
+        frame_rect: LayoutRect,
+        margins: SideOffsets2D<Option<f32>>,
+        vertical_offset_bounds: StickyOffsetBounds,
+        horizontal_offset_bounds: StickyOffsetBounds,
+        previously_applied_offset: LayoutVector2D
+    ) -> StickyFrameInfo {
+        StickyFrameInfo {
+            frame_rect,
+            margins,
+            vertical_offset_bounds,
+            horizontal_offset_bounds,
+            previously_applied_offset,
+            current_offset: LayoutVector2D::zero(),
+        }
+    }
+}
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -2,17 +2,17 @@
  * 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/. */
 
 use api::{ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale, DeviceUintPoint};
 use api::{DeviceUintRect, DeviceUintSize, DocumentLayer, FilterOp, ImageFormat, LayoutRect};
 use api::{MixBlendMode, PipelineId};
 use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
 use clip::{ClipStore};
-use clip_scroll_tree::{ClipScrollTree, ClipScrollNodeIndex};
+use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
 use device::{FrameId, Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use gpu_cache::{GpuCache};
 use gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, TransformData, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
@@ -26,17 +26,17 @@ use std::{cmp, usize, f32, i32, mem};
 use texture_allocator::GuillotineAllocator;
 #[cfg(feature = "pathfinder")]
 use webrender_api::{DevicePixel, FontRenderMode};
 
 const MIN_TARGET_SIZE: u32 = 2048;
 
 #[derive(Debug)]
 pub struct ScrollbarPrimitive {
-    pub scroll_frame_index: ClipScrollNodeIndex,
+    pub scroll_frame_index: SpatialNodeIndex,
     pub prim_index: PrimitiveIndex,
     pub frame_rect: LayoutRect,
 }
 
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTargetIndex(pub usize);
--- a/gfx/webrender/tests/angle_shader_validation.rs
+++ b/gfx/webrender/tests/angle_shader_validation.rs
@@ -13,19 +13,21 @@ const VERTEX_SHADER: u32 = 0x8B31;
 
 struct Shader {
     name: &'static str,
     features: &'static [&'static str],
 }
 
 const SHADER_PREFIX: &str = "#define WR_MAX_VERTEX_TEXTURE_WIDTH 1024\n";
 
+const BRUSH_FEATURES: &[&str] = &["", "ALPHA_PASS"];
 const CLIP_FEATURES: &[&str] = &["TRANSFORM"];
 const CACHE_FEATURES: &[&str] = &[""];
-const PRIM_FEATURES: &[&str] = &["", "TRANSFORM"];
+const GRADIENT_FEATURES: &[&str] = &[ "", "DITHERING", "ALPHA_PASS", "DITHERING,ALPHA_PASS" ];
+const PRIM_FEATURES: &[&str] = &[""];
 
 const SHADERS: &[Shader] = &[
     // Clip mask shaders
     Shader {
         name: "cs_clip_rectangle",
         features: CLIP_FEATURES,
     },
     Shader {
@@ -38,59 +40,66 @@ const SHADERS: &[Shader] = &[
     },
     Shader {
         name: "cs_clip_line",
         features: CLIP_FEATURES,
     },
     // Cache shaders
     Shader {
         name: "cs_blur",
-        features: CACHE_FEATURES,
+        features: &[ "ALPHA_TARGET", "COLOR_TARGET" ],
     },
     Shader {
         name: "cs_border_segment",
         features: CACHE_FEATURES,
     },
     // Prim shaders
     Shader {
         name: "ps_split_composite",
         features: PRIM_FEATURES,
     },
     Shader {
         name: "ps_text_run",
-        features: PRIM_FEATURES,
+        features: &[ "", "GLYPH_TRANSFORM" ],
     },
     // Brush shaders
     Shader {
         name: "brush_yuv_image",
-        features: &["", "YUV_NV12", "YUV_PLANAR", "YUV_INTERLEAVED", "YUV_NV12,TEXTURE_RECT"],
+        features: &[
+            "",
+            "YUV_NV12",
+            "YUV_PLANAR",
+            "YUV_INTERLEAVED",
+            "TEXTURE_2D,YUV_NV12",
+            "YUV_NV12,ALPHA_PASS",
+        ],
     },
     Shader {
         name: "brush_solid",
-        features: &[],
+        features: BRUSH_FEATURES,
     },
     Shader {
         name: "brush_image",
-        features: &["", "ALPHA_PASS"],
+        features: BRUSH_FEATURES,
     },
     Shader {
         name: "brush_blend",
-        features: &[],
+        features: BRUSH_FEATURES,
     },
     Shader {
         name: "brush_mix_blend",
-        features: &[],
+        features: BRUSH_FEATURES,
     },
     Shader {
         name: "brush_radial_gradient",
-        features: &[ "DITHERING" ],
+        features: GRADIENT_FEATURES,
     },
     Shader {
         name: "brush_linear_gradient",
-        features: &[],
+        features: GRADIENT_FEATURES,
     },
 ];
 
 const VERSION_STRING: &str = "#version 300 es\n";
 
 #[test]
 fn validate_shaders() {
     mozangle::shaders::initialize().unwrap();
@@ -103,17 +112,17 @@ fn validate_shaders() {
         ShaderValidator::new(FRAGMENT_SHADER, ShaderSpec::Gles3, Output::Essl, &resources).unwrap();
 
     for shader in SHADERS {
         for config in shader.features {
             let mut features = String::new();
             features.push_str(SHADER_PREFIX);
 
             for feature in config.split(",") {
-                features.push_str(&format!("#define WR_FEATURE_{}", feature));
+                features.push_str(&format!("#define WR_FEATURE_{}\n", feature));
             }
 
             let (vs, fs) =
                 webrender::build_shader_strings(VERSION_STRING, &features, shader.name, &None);
 
             validate(&vs_validator, shader.name, vs);
             validate(&fs_validator, shader.name, fs);
         }
--- a/gfx/webrender_api/Cargo.toml
+++ b/gfx/webrender_api/Cargo.toml
@@ -19,13 +19,13 @@ byteorder = "1.2.1"
 ipc-channel = {version = "0.10.0", optional = true}
 euclid = { version = "0.17", features = ["serde"] }
 serde = { version = "=1.0.66", features = ["rc"] }
 serde_derive = { version = "=1.0.66", features = ["deserialize_in_place"] }
 serde_bytes = "0.10"
 time = "0.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
-core-foundation = "0.5"
-core-graphics = "0.13"
+core-foundation = "0.6"
+core-graphics = "0.14"
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -801,49 +801,51 @@ impl ComplexClipRegion {
 }
 
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ClipChainId(pub u64, pub PipelineId);
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum ClipId {
+    Spatial(usize, PipelineId),
     Clip(usize, PipelineId),
     ClipChain(ClipChainId),
 }
 
 const ROOT_REFERENCE_FRAME_CLIP_ID: usize = 0;
 const ROOT_SCROLL_NODE_CLIP_ID: usize = 1;
 
 impl ClipId {
     pub fn root_scroll_node(pipeline_id: PipelineId) -> ClipId {
-        ClipId::Clip(ROOT_SCROLL_NODE_CLIP_ID, pipeline_id)
+        ClipId::Spatial(ROOT_SCROLL_NODE_CLIP_ID, pipeline_id)
     }
 
     pub fn root_reference_frame(pipeline_id: PipelineId) -> ClipId {
-        ClipId::Clip(ROOT_REFERENCE_FRAME_CLIP_ID, pipeline_id)
+        ClipId::Spatial(ROOT_REFERENCE_FRAME_CLIP_ID, pipeline_id)
     }
 
     pub fn pipeline_id(&self) -> PipelineId {
         match *self {
+            ClipId::Spatial(_, pipeline_id) |
             ClipId::Clip(_, pipeline_id) |
             ClipId::ClipChain(ClipChainId(_, pipeline_id)) => pipeline_id,
         }
     }
 
     pub fn is_root_scroll_node(&self) -> bool {
         match *self {
-            ClipId::Clip(1, _) => true,
+            ClipId::Spatial(ROOT_SCROLL_NODE_CLIP_ID, _) => true,
             _ => false,
         }
     }
 
     pub fn is_root_reference_frame(&self) -> bool {
         match *self {
-            ClipId::Clip(1, _) => true,
+            ClipId::Spatial(ROOT_REFERENCE_FRAME_CLIP_ID, _) => true,
             _ => false,
         }
     }
 }
 
 /// An external identifier that uniquely identifies a scroll frame independent of its ClipId, which
 /// may change from frame to frame. This should be unique within a pipeline. WebRender makes no
 /// attempt to ensure uniqueness. The zero value is reserved for use by the root scroll node of
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -26,17 +26,22 @@ use {ScrollFrameDisplayItem, ScrollSensi
 use {StickyFrameDisplayItem, StickyOffsetBounds, TextDisplayItem, TransformStyle, YuvColorSpace};
 use {YuvData, YuvImageDisplayItem};
 
 // We don't want to push a long text-run. If a text-run is too long, split it into several parts.
 // This needs to be set to (renderer::MAX_VERTEX_TEXTURE_WIDTH - VECS_PER_TEXT_RUN) * 2
 pub const MAX_TEXT_RUN_LENGTH: usize = 2040;
 
 // We start at 2, because the root reference is always 0 and the root scroll node is always 1.
-const FIRST_CLIP_ID: usize = 2;
+// TODO(mrobinson): It would be a good idea to eliminate the root scroll frame which is only
+// used by Servo.
+const FIRST_SPATIAL_NODE_INDEX: usize = 2;
+
+// There are no default clips, so we start at the 0 index for clips.
+const FIRST_CLIP_NODE_INDEX: usize = 0;
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ItemRange<T> {
     start: usize,
     length: usize,
     _boo: PhantomData<T>,
 }
@@ -74,18 +79,20 @@ pub struct BuiltDisplayList {
 #[derive(Copy, Clone, Default, Deserialize, Serialize)]
 pub struct BuiltDisplayListDescriptor {
     /// The first IPC time stamp: before any work has been done
     builder_start_time: u64,
     /// The second IPC time stamp: after serialization
     builder_finish_time: u64,
     /// The third IPC time stamp: just before sending
     send_start_time: u64,
-    /// The amount of clips ids assigned while building this display list.
-    total_clip_ids: usize,
+    /// The amount of clipping nodes created while building this display list.
+    total_clip_nodes: usize,
+    /// The amount of spatial nodes created while building this display list.
+    total_spatial_nodes: usize,
 }
 
 pub struct BuiltDisplayListIter<'a> {
     list: &'a BuiltDisplayList,
     data: &'a [u8],
     cur_item: DisplayItem,
     cur_stops: ItemRange<GradientStop>,
     cur_glyphs: ItemRange<GlyphInstance>,
@@ -141,18 +148,22 @@ impl BuiltDisplayList {
     pub fn times(&self) -> (u64, u64, u64) {
         (
             self.descriptor.builder_start_time,
             self.descriptor.builder_finish_time,
             self.descriptor.send_start_time,
         )
     }
 
-    pub fn total_clip_ids(&self) -> usize {
-        self.descriptor.total_clip_ids
+    pub fn total_clip_nodes(&self) -> usize {
+        self.descriptor.total_clip_nodes
+    }
+
+    pub fn total_spatial_nodes(&self) -> usize {
+        self.descriptor.total_spatial_nodes
     }
 
     pub fn iter(&self) -> BuiltDisplayListIter {
         BuiltDisplayListIter::new(self)
     }
 
     pub fn get<'de, T: Deserialize<'de>>(&self, range: ItemRange<T>) -> AuxIter<T> {
         AuxIter::new(&self.data[range.start .. range.start + range.length])
@@ -512,36 +523,38 @@ impl<'de> Deserialize<'de> for BuiltDisp
         use display_item::CompletelySpecificDisplayItem::*;
         use display_item::{CompletelySpecificDisplayItem, GenericDisplayItem};
 
         let list = Vec::<GenericDisplayItem<CompletelySpecificDisplayItem>>
             ::deserialize(deserializer)?;
 
         let mut data = Vec::new();
         let mut temp = Vec::new();
-        let mut total_clip_ids = FIRST_CLIP_ID;
+        let mut total_clip_nodes = FIRST_CLIP_NODE_INDEX;
+        let mut total_spatial_nodes = FIRST_SPATIAL_NODE_INDEX;
         for complete in list {
             let item = DisplayItem {
                 item: match complete.item {
                     Clip(specific_item, complex_clips) => {
-                        total_clip_ids += 1;
+                        total_clip_nodes += 1;
                         DisplayListBuilder::push_iter_impl(&mut temp, complex_clips);
                         SpecificDisplayItem::Clip(specific_item)
                     },
                     ClipChain(specific_item, clip_chain_ids) => {
                         DisplayListBuilder::push_iter_impl(&mut temp, clip_chain_ids);
                         SpecificDisplayItem::ClipChain(specific_item)
                     }
                     ScrollFrame(specific_item, complex_clips) => {
-                        total_clip_ids += 2;
+                        total_spatial_nodes += 1;
+                        total_clip_nodes += 1;
                         DisplayListBuilder::push_iter_impl(&mut temp, complex_clips);
                         SpecificDisplayItem::ScrollFrame(specific_item)
                     },
                     StickyFrame(specific_item) => {
-                        total_clip_ids += 1;
+                        total_spatial_nodes += 1;
                         SpecificDisplayItem::StickyFrame(specific_item)
                     }
                     Rectangle(specific_item) => SpecificDisplayItem::Rectangle(specific_item),
                     ClearRectangle => SpecificDisplayItem::ClearRectangle,
                     Line(specific_item) => SpecificDisplayItem::Line(specific_item),
                     Text(specific_item, glyphs) => {
                         DisplayListBuilder::push_iter_impl(&mut temp, glyphs);
                         SpecificDisplayItem::Text(specific_item)
@@ -549,26 +562,26 @@ impl<'de> Deserialize<'de> for BuiltDisp
                     Image(specific_item) => SpecificDisplayItem::Image(specific_item),
                     YuvImage(specific_item) => SpecificDisplayItem::YuvImage(specific_item),
                     Border(specific_item) => SpecificDisplayItem::Border(specific_item),
                     BoxShadow(specific_item) => SpecificDisplayItem::BoxShadow(specific_item),
                     Gradient(specific_item) => SpecificDisplayItem::Gradient(specific_item),
                     RadialGradient(specific_item) =>
                         SpecificDisplayItem::RadialGradient(specific_item),
                     Iframe(specific_item) => {
-                        total_clip_ids += 1;
+                        total_clip_nodes += 1;
                         SpecificDisplayItem::Iframe(specific_item)
                     }
                     PushStackingContext(specific_item, filters) => {
                         DisplayListBuilder::push_iter_impl(&mut temp, filters);
                         SpecificDisplayItem::PushStackingContext(specific_item)
                     },
                     PopStackingContext => SpecificDisplayItem::PopStackingContext,
                     PushReferenceFrame(specific_item) => {
-                        total_clip_ids += 1;
+                        total_spatial_nodes += 1;
                         SpecificDisplayItem::PushReferenceFrame(specific_item)
                     }
                     PopReferenceFrame => SpecificDisplayItem::PopReferenceFrame,
                     SetGradientStops(stops) => {
                         DisplayListBuilder::push_iter_impl(&mut temp, stops);
                         SpecificDisplayItem::SetGradientStops
                     },
                     PushShadow(specific_item) => SpecificDisplayItem::PushShadow(specific_item),
@@ -583,17 +596,18 @@ impl<'de> Deserialize<'de> for BuiltDisp
         }
 
         Ok(BuiltDisplayList {
             data,
             descriptor: BuiltDisplayListDescriptor {
                 builder_start_time: 0,
                 builder_finish_time: 1,
                 send_start_time: 0,
-                total_clip_ids,
+                total_clip_nodes,
+                total_spatial_nodes,
             },
         })
     }
 }
 
 // This is a replacement for bincode::serialize_into(&vec)
 // The default implementation Write for Vec will basically
 // call extend_from_slice(). Serde ends up calling that for every
@@ -808,26 +822,28 @@ impl<'a, 'b> Read for UnsafeReader<'a, '
         Ok(())
     }
 }
 
 #[derive(Clone, Debug)]
 pub struct SaveState {
     dl_len: usize,
     clip_stack_len: usize,
-    next_clip_id: usize,
+    next_clip_index: usize,
+    next_spatial_index: usize,
     next_clip_chain_id: u64,
 }
 
 #[derive(Clone)]
 pub struct DisplayListBuilder {
     pub data: Vec<u8>,
     pub pipeline_id: PipelineId,
     clip_stack: Vec<ClipAndScrollInfo>,
-    next_clip_id: usize,
+    next_clip_index: usize,
+    next_spatial_index: usize,
     next_clip_chain_id: u64,
     builder_start_time: u64,
 
     /// The size of the content of this display list. This is used to allow scrolling
     /// outside the bounds of the display list items themselves.
     content_size: LayoutSize,
     save_state: Option<SaveState>,
 }
@@ -845,17 +861,18 @@ impl DisplayListBuilder {
         let start_time = precise_time_ns();
 
         DisplayListBuilder {
             data: Vec::with_capacity(capacity),
             pipeline_id,
             clip_stack: vec![
                 ClipAndScrollInfo::simple(ClipId::root_scroll_node(pipeline_id)),
             ],
-            next_clip_id: FIRST_CLIP_ID,
+            next_clip_index: FIRST_CLIP_NODE_INDEX,
+            next_spatial_index: FIRST_SPATIAL_NODE_INDEX,
             next_clip_chain_id: 0,
             builder_start_time: start_time,
             content_size,
             save_state: None,
         }
     }
 
     /// Return the content size for this display list
@@ -871,28 +888,30 @@ impl DisplayListBuilder {
     /// * Doesn't support nested saves.
     /// * Must call `clear_save()` if the restore becomes unnecessary.
     pub fn save(&mut self) {
         assert!(self.save_state.is_none(), "DisplayListBuilder doesn't support nested saves");
 
         self.save_state = Some(SaveState {
             clip_stack_len: self.clip_stack.len(),
             dl_len: self.data.len(),
-            next_clip_id: self.next_clip_id,
+            next_clip_index: self.next_clip_index,
+            next_spatial_index: self.next_spatial_index,
             next_clip_chain_id: self.next_clip_chain_id,
         });
     }
 
     /// Restores the state of the builder to when `save()` was last called.
     pub fn restore(&mut self) {
         let state = self.save_state.take().expect("No save to restore DisplayListBuilder from");
 
         self.clip_stack.truncate(state.clip_stack_len);
         self.data.truncate(state.dl_len);
-        self.next_clip_id = state.next_clip_id;
+        self.next_clip_index = state.next_clip_index;
+        self.next_spatial_index = state.next_spatial_index;
         self.next_clip_chain_id = state.next_clip_chain_id;
     }
 
     /// Discards the builder's save (indicating the attempted operation was successful).
     pub fn clear_save(&mut self) {
         self.save_state.take().expect("No save to clear in DisplayListBuilder");
     }
 
@@ -1283,17 +1302,17 @@ impl DisplayListBuilder {
     }
 
     pub fn push_reference_frame(
         &mut self,
         info: &LayoutPrimitiveInfo,
         transform: Option<PropertyBinding<LayoutTransform>>,
         perspective: Option<LayoutTransform>,
     ) -> ClipId {
-        let id = self.generate_clip_id();
+        let id = self.generate_spatial_index();
         let item = SpecificDisplayItem::PushReferenceFrame(PushReferenceFrameDisplayListItem {
             reference_frame: ReferenceFrame {
                 transform,
                 perspective,
                 id,
             },
         });
         self.push_item(item, info);
@@ -1333,19 +1352,24 @@ impl DisplayListBuilder {
     pub fn push_stops(&mut self, stops: &[GradientStop]) {
         if stops.is_empty() {
             return;
         }
         self.push_new_empty_item(SpecificDisplayItem::SetGradientStops);
         self.push_iter(stops);
     }
 
-    fn generate_clip_id(&mut self) -> ClipId {
-        self.next_clip_id += 1;
-        ClipId::Clip(self.next_clip_id - 1, self.pipeline_id)
+    fn generate_clip_index(&mut self) -> ClipId {
+        self.next_clip_index += 1;
+        ClipId::Clip(self.next_clip_index - 1, self.pipeline_id)
+    }
+
+    fn generate_spatial_index(&mut self) -> ClipId {
+        self.next_spatial_index += 1;
+        ClipId::Spatial(self.next_spatial_index - 1, self.pipeline_id)
     }
 
     fn generate_clip_chain_id(&mut self) -> ClipChainId {
         self.next_clip_chain_id += 1;
         ClipChainId(self.next_clip_chain_id - 1, self.pipeline_id)
     }
 
     pub fn define_scroll_frame<I>(
@@ -1381,18 +1405,18 @@ impl DisplayListBuilder {
         complex_clips: I,
         image_mask: Option<ImageMask>,
         scroll_sensitivity: ScrollSensitivity,
     ) -> ClipId
     where
         I: IntoIterator<Item = ComplexClipRegion>,
         I::IntoIter: ExactSizeIterator + Clone,
     {
-        let clip_id = self.generate_clip_id();
-        let scroll_frame_id = self.generate_clip_id();
+        let clip_id = self.generate_clip_index();
+        let scroll_frame_id = self.generate_spatial_index();
         let item = SpecificDisplayItem::ScrollFrame(ScrollFrameDisplayItem {
             clip_id,
             scroll_frame_id,
             external_id,
             image_mask,
             scroll_sensitivity,
         });
 
@@ -1446,17 +1470,17 @@ impl DisplayListBuilder {
         clip_rect: LayoutRect,
         complex_clips: I,
         image_mask: Option<ImageMask>,
     ) -> ClipId
     where
         I: IntoIterator<Item = ComplexClipRegion>,
         I::IntoIter: ExactSizeIterator + Clone,
     {
-        let id = self.generate_clip_id();
+        let id = self.generate_clip_index();
         let item = SpecificDisplayItem::Clip(ClipDisplayItem {
             id,
             image_mask,
         });
 
         let info = LayoutPrimitiveInfo::new(clip_rect);
 
         let scrollinfo = ClipAndScrollInfo::simple(parent);
@@ -1469,17 +1493,17 @@ impl DisplayListBuilder {
         &mut self,
         frame_rect: LayoutRect,
         margins: SideOffsets2D<Option<f32>>,
         vertical_offset_bounds: StickyOffsetBounds,
         horizontal_offset_bounds: StickyOffsetBounds,
         previously_applied_offset: LayoutVector2D,
 
     ) -> ClipId {
-        let id = self.generate_clip_id();
+        let id = self.generate_spatial_index();
         let item = SpecificDisplayItem::StickyFrame(StickyFrameDisplayItem {
             id,
             margins,
             vertical_offset_bounds,
             horizontal_offset_bounds,
             previously_applied_offset,
         });
 
@@ -1507,17 +1531,17 @@ impl DisplayListBuilder {
 
     pub fn push_iframe(
         &mut self,
         info: &LayoutPrimitiveInfo,
         pipeline_id: PipelineId,
         ignore_missing_pipeline: bool
     ) {
         let item = SpecificDisplayItem::Iframe(IframeDisplayItem {
-            clip_id: self.generate_clip_id(),
+            clip_id: self.generate_clip_index(),
             pipeline_id,
             ignore_missing_pipeline,
         });
         self.push_item(item, info);
     }
 
     pub fn push_shadow(&mut self, info: &LayoutPrimitiveInfo, shadow: Shadow) {
         self.push_item(SpecificDisplayItem::PushShadow(shadow), info);
@@ -1536,15 +1560,16 @@ impl DisplayListBuilder {
         (
             self.pipeline_id,
             self.content_size,
             BuiltDisplayList {
                 descriptor: BuiltDisplayListDescriptor {
                     builder_start_time: self.builder_start_time,
                     builder_finish_time: end_time,
                     send_start_time: 0,
-                    total_clip_ids: self.next_clip_id,
+                    total_clip_nodes: self.next_clip_index,
+                    total_spatial_nodes: self.next_spatial_index,
                 },
                 data: self.data,
             },
         )
     }
 }
--- a/gfx/webrender_bindings/Cargo.toml
+++ b/gfx/webrender_bindings/Cargo.toml
@@ -21,12 +21,12 @@ path = "../webrender"
 version = "0.57.2"
 default-features = false
 features = ["capture", "serialize_program"]
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
-core-foundation = "0.5"
-core-graphics = "0.13"
+core-foundation = "0.6"
+core-graphics = "0.14"
 foreign-types = "0.3.0"
 
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-0e9563688e575cd662570f54bc9d6f849040dbf8
+e600bfe68efac6416ce2e8091d7344744771f6db
--- a/gfx/wrench/Cargo.toml
+++ b/gfx/wrench/Cargo.toml
@@ -7,40 +7,40 @@ license = "MPL-2.0"
 
 [dependencies]
 base64 = "0.6"
 bincode = "1.0"
 byteorder = "1.0"
 env_logger = { version = "0.5", optional = true }
 euclid = "0.17"
 gleam = "0.5"
-glutin = "0.15"
+glutin = "0.17"
 app_units = "0.6"
 image = "0.19"
 clap = { version = "2", features = ["yaml"] }
 lazy_static = "1"
 log = "0.4"
 yaml-rust = { git = "https://github.com/vvuk/yaml-rust", features = ["preserve_order"] }
 serde_json = "1.0"
 ron = "0.1.5"
 time = "0.1"
 crossbeam = "0.2"
 osmesa-sys = { version = "0.1.2", optional = true }
 osmesa-src = { git = "https://github.com/jrmuizel/osmesa-src", optional = true, branch = "serialize" }
 webrender = {path = "../webrender", features=["capture","replay","debugger","png","profiler"]}
 webrender_api = {path = "../webrender_api", features=["serialize","deserialize"]}
-winit = "0.13"
+winit = "0.16"
 serde = {version = "1.0", features = ["derive"] }
 
 [target.'cfg(target_os = "macos")'.dependencies]
-core-graphics = "0.13"
-core-foundation = "0.5"
+core-graphics = "0.14"
+core-foundation = "0.6"
 
 [features]
 headless = [ "osmesa-sys", "osmesa-src" ]
 pathfinder = [ "webrender/pathfinder" ]
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
 mozangle = {version = "0.1.5", features = ["egl"]}
 
 [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
-font-loader = "0.6"
+font-loader = "0.7"
--- a/gfx/wrench/src/angle.rs
+++ b/gfx/wrench/src/angle.rs
@@ -1,13 +1,15 @@
 /* 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/. */
 
 use glutin::{self, ContextBuilder, CreationError};
+#[cfg(not(windows))]
+use glutin::dpi::PhysicalSize;
 use winit::{EventsLoop, Window, WindowBuilder};
 
 #[cfg(not(windows))]
 pub enum Context {}
 
 #[cfg(windows)]
 pub use ::egl::Context;
 
@@ -60,13 +62,13 @@ impl glutin::GlContext for Context {
     fn get_api(&self) -> glutin::Api {
         match *self {}
     }
 
     fn get_pixel_format(&self) -> glutin::PixelFormat {
         match *self {}
     }
 
-    fn resize(&self, _: u32, _: u32) {
+    fn resize(&self, _: PhysicalSize) {
         match *self {}
     }
 }
 
--- a/gfx/wrench/src/egl.rs
+++ b/gfx/wrench/src/egl.rs
@@ -10,16 +10,17 @@ use glutin::CreationError;
 use glutin::GlAttributes;
 use glutin::GlContext;
 use glutin::GlRequest;
 use glutin::PixelFormat;
 use glutin::PixelFormatRequirements;
 use glutin::ReleaseBehavior;
 use glutin::Robustness;
 use glutin::Api;
+use glutin::dpi::PhysicalSize;
 
 use std::ffi::{CStr, CString};
 use std::os::raw::c_int;
 use std::{mem, ptr};
 use std::cell::Cell;
 
 use mozangle::egl::ffi as egl;
 mod ffi {
@@ -201,17 +202,17 @@ impl GlContext for Context {
     }
 
     #[inline]
     fn get_pixel_format(&self) -> PixelFormat {
         self.pixel_format.clone()
     }
 
     #[inline]
-    fn resize(&self, _: u32, _: u32) {}
+    fn resize(&self, _: PhysicalSize) {}
 }
 
 unsafe impl Send for Context {}
 unsafe impl Sync for Context {}
 
 impl Drop for Context {
     fn drop(&mut self) {
         unsafe {
--- a/gfx/wrench/src/main.rs
+++ b/gfx/wrench/src/main.rs
@@ -58,16 +58,17 @@ mod yaml_frame_reader;
 mod yaml_frame_writer;
 mod yaml_helper;
 #[cfg(target_os = "macos")]
 mod cgfont_to_data;
 
 use binary_frame_reader::BinaryFrameReader;
 use gleam::gl;
 use glutin::GlContext;
+use glutin::dpi::{LogicalPosition, LogicalSize};
 use perf::PerfHarness;
 use png::save_flipped;
 use rawtest::RawtestHarness;
 use reftest::{ReftestHarness, ReftestOptions};
 #[cfg(feature = "headless")]
 use std::ffi::CString;
 #[cfg(feature = "headless")]
 use std::mem;
@@ -178,45 +179,49 @@ impl WindowWrapper {
             WindowWrapper::Angle(_, ref context, _) => context.swap_buffers().unwrap(),
             WindowWrapper::Headless(_, _) => {}
         }
     }
 
     fn get_inner_size(&self) -> DeviceUintSize {
         //HACK: `winit` needs to figure out its hidpi story...
         #[cfg(target_os = "macos")]
-        fn inner_size(window: &winit::Window) -> (u32, u32) {
-            let (w, h) = window.get_inner_size().unwrap();
-            let factor = window.hidpi_factor();
-            ((w as f32 * factor) as _, (h as f32 * factor) as _)
+        fn inner_size(window: &winit::Window) -> LogicalSize {
+            let LogicalSize { width, height } = window.get_inner_size().unwrap();
+            let factor = window.get_hidpi_factor();
+            LogicalSize::new(width * factor, height * factor)
         }
         #[cfg(not(target_os = "macos"))]
-        fn inner_size(window: &winit::Window) -> (u32, u32) {
+        fn inner_size(window: &winit::Window) -> LogicalSize {
             window.get_inner_size().unwrap()
         }
-        let (w, h) = match *self {
+        let LogicalSize { width, height } = match *self {
             WindowWrapper::Window(ref window, _) => inner_size(window.window()),
             WindowWrapper::Angle(ref window, ..) => inner_size(window),
-            WindowWrapper::Headless(ref context, _) => (context.width, context.height),
+            WindowWrapper::Headless(ref context, _) => LogicalSize::new(context.width as f64, context.height as f64),
         };
-        DeviceUintSize::new(w, h)
+        DeviceUintSize::new(width as u32, height as u32)
     }
 
     fn hidpi_factor(&self) -> f32 {
         match *self {
-            WindowWrapper::Window(ref window, _) => window.hidpi_factor(),
-            WindowWrapper::Angle(ref window, ..) => window.hidpi_factor(),
+            WindowWrapper::Window(ref window, _) => window.get_hidpi_factor() as f32,
+            WindowWrapper::Angle(ref window, ..) => window.get_hidpi_factor() as f32,
             WindowWrapper::Headless(_, _) => 1.0,
         }
     }
 
     fn resize(&mut self, size: DeviceUintSize) {
         match *self {
-            WindowWrapper::Window(ref mut window, _) => window.set_inner_size(size.width, size.height),
-            WindowWrapper::Angle(ref mut window, ..) => window.set_inner_size(size.width, size.height),
+            WindowWrapper::Window(ref mut window, _) => {
+                window.set_inner_size(LogicalSize::new(size.width as f64, size.height as f64))
+            },
+            WindowWrapper::Angle(ref mut window, ..) => {
+                window.set_inner_size(LogicalSize::new(size.width as f64, size.height as f64))
+            },
             WindowWrapper::Headless(_, _) => unimplemented!(), // requites Glutin update
         }
     }
 
     fn set_title(&mut self, title: &str) {
         match *self {
             WindowWrapper::Window(ref window, _) => window.set_title(title),
             WindowWrapper::Angle(ref window, ..) => window.set_title(title),
@@ -254,17 +259,17 @@ fn make_window(
                 .with_gl(glutin::GlRequest::GlThenGles {
                     opengl_version: (3, 2),
                     opengles_version: (3, 0),
                 })
                 .with_vsync(vsync);
             let window_builder = winit::WindowBuilder::new()
                 .with_title("WRench")
                 .with_multitouch()
-                .with_dimensions(size.width, size.height);
+                .with_dimensions(LogicalSize::new(size.width as f64, size.height as f64));
 
             let init = |context: &glutin::GlContext| {
                 unsafe {
                     context
                         .make_current()
                         .expect("unable to make context current!");
                 }
 
@@ -578,17 +583,17 @@ fn render<'a>(
             winit::Event::WindowEvent { event, .. } => match event {
                 winit::WindowEvent::CloseRequested => {
                     return winit::ControlFlow::Break;
                 }
                 winit::WindowEvent::Refresh |
                 winit::WindowEvent::Focused(..) => {
                     do_render = true;
                 }
-                winit::WindowEvent::CursorMoved { position: (x, y), .. } => {
+                winit::WindowEvent::CursorMoved { position: LogicalPosition { x, y }, .. } => {
                     cursor_position = WorldPoint::new(x as f32, y as f32);
                     do_render = true;
                 }
                 winit::WindowEvent::KeyboardInput {
                     input: winit::KeyboardInput {
                         state: winit::ElementState::Pressed,
                         virtual_keycode: Some(vk),
                         ..