diff --git a/direct-composition/src/main_windows.rs b/direct-composition/src/main_windows.rs
index b025f8f176..d4255c0c03 100644
--- a/direct-composition/src/main_windows.rs
+++ b/direct-composition/src/main_windows.rs
@@ -142,12 +142,21 @@ impl Rectangle {
             api::BorderRadius::uniform(20.),
             api::ClipMode::Clip
         );
-        let clip_id = builder.define_clip(rect, vec![region], None);
-        builder.push_clip_id(clip_id);
-
-        builder.push_rect(&api::PrimitiveInfo::new(rect), self.color);
+        let clip_id = builder.define_clip(
+            &api::SpaceAndClipInfo::root_scroll(pipeline_id),
+            rect,
+            vec![region],
+            None,
+        );
 
-        builder.pop_clip_id();
+        builder.push_rect(
+            &api::PrimitiveInfo::new(rect),
+            &api::SpaceAndClipInfo {
+                spatial_id: api::SpatialId::root_scroll_node(pipeline_id),
+                clip_id,
+            },
+            self.color,
+        );
 
         let mut transaction = api::Transaction::new();
         transaction.set_display_list(
diff --git a/examples/alpha_perf.rs b/examples/alpha_perf.rs
index 1c6e5389a7..f668d618a4 100644
--- a/examples/alpha_perf.rs
+++ b/examples/alpha_perf.rs
@@ -26,14 +26,16 @@ impl Example for App {
         builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
         _framebuffer_size: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = (0, 0).to(1920, 1080);
         let info = LayoutPrimitiveInfo::new(bounds);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
 
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
@@ -42,7 +44,7 @@ impl Example for App {
         );
 
         for _ in 0 .. self.rect_count {
-            builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 0.05));
+            builder.push_rect(&info, &space_and_clip, ColorF::new(1.0, 1.0, 1.0, 0.05));
         }
 
         builder.pop_stacking_context();
diff --git a/examples/animation.rs b/examples/animation.rs
index c2b625cca4..88afd8f370 100644
--- a/examples/animation.rs
+++ b/examples/animation.rs
@@ -40,6 +40,7 @@ impl App {
         bounds: LayoutRect,
         color: ColorF,
         builder: &mut DisplayListBuilder,
+        pipeline_id: PipelineId,
         property_key: PropertyBindingKey<LayoutTransform>,
         opacity_key: Option<PropertyBindingKey<f32>>,
     ) {
@@ -54,17 +55,17 @@ impl App {
             }
         };
 
-        let reference_frame_id = builder.push_reference_frame(
+        let spatial_id = builder.push_reference_frame(
             &LayoutRect::new(bounds.origin, LayoutSize::zero()),
+            SpatialId::root_scroll_node(pipeline_id),
             TransformStyle::Flat,
             Some(PropertyBinding::Binding(property_key, LayoutTransform::identity())),
             None,
         );
 
-        builder.push_clip_id(reference_frame_id);
-
         builder.push_stacking_context(
             &LayoutPrimitiveInfo::new(LayoutRect::zero()),
+            spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
@@ -72,26 +73,29 @@ impl App {
             RasterSpace::Screen,
         );
 
+        let space_and_clip = SpaceAndClipInfo {
+            spatial_id,
+            clip_id: ClipId::root(pipeline_id),
+        };
         let clip_bounds = LayoutRect::new(LayoutPoint::zero(), bounds.size);
         let complex_clip = ComplexClipRegion {
             rect: clip_bounds,
             radii: BorderRadius::uniform(30.0),
             mode: ClipMode::Clip,
         };
-        let clip_id = builder.define_clip(clip_bounds, vec![complex_clip], None);
-        builder.push_clip_id(clip_id);
+        let clip_id = builder.define_clip(&space_and_clip, clip_bounds, vec![complex_clip], None);
 
         // Fill it with a white rect
         builder.push_rect(
             &LayoutPrimitiveInfo::new(LayoutRect::new(LayoutPoint::zero(), bounds.size)),
+            &SpaceAndClipInfo {
+                spatial_id,
+                clip_id,
+            },
             color,
         );
 
-        builder.pop_clip_id();
-
         builder.pop_stacking_context();
-
-        builder.pop_clip_id();
         builder.pop_reference_frame();
     }
 }
@@ -106,22 +110,22 @@ impl Example for App {
         builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
         _framebuffer_size: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let opacity_key = self.opacity_key;
 
         let bounds = (150, 150).to(250, 250);
         let key0 = self.property_key0;
-        self.add_rounded_rect(bounds, ColorF::new(1.0, 0.0, 0.0, 0.5), builder, key0, Some(opacity_key));
+        self.add_rounded_rect(bounds, ColorF::new(1.0, 0.0, 0.0, 0.5), builder, pipeline_id, key0, Some(opacity_key));
 
         let bounds = (400, 400).to(600, 600);
         let key1 = self.property_key1;
-        self.add_rounded_rect(bounds, ColorF::new(0.0, 1.0, 0.0, 0.5), builder, key1, None);
+        self.add_rounded_rect(bounds, ColorF::new(0.0, 1.0, 0.0, 0.5), builder, pipeline_id, key1, None);
 
         let bounds = (200, 500).to(350, 580);
         let key2 = self.property_key2;
-        self.add_rounded_rect(bounds, ColorF::new(0.0, 0.0, 1.0, 0.5), builder, key2, None);
+        self.add_rounded_rect(bounds, ColorF::new(0.0, 0.0, 1.0, 0.5), builder, pipeline_id, key2, None);
     }
 
     fn on_event(&mut self, win_event: winit::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool {
diff --git a/examples/basic.rs b/examples/basic.rs
index af7994af5a..6bd63834fa 100644
--- a/examples/basic.rs
+++ b/examples/basic.rs
@@ -186,13 +186,17 @@ impl Example for App {
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
         _: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
+        let root_space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
+        let spatial_id = root_space_and_clip.spatial_id;
+
         builder.push_stacking_context(
             &info,
+            spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
@@ -217,14 +221,19 @@ impl Example for App {
             BorderRadius::uniform(20.0),
             ClipMode::Clip
         );
-        let id = builder.define_clip(bounds, vec![complex], Some(mask));
-        builder.push_clip_id(id);
+        let clip_id = builder.define_clip(&root_space_and_clip, bounds, vec![complex], Some(mask));
 
-        let info = LayoutPrimitiveInfo::new((100, 100).to(200, 200));
-        builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        builder.push_rect(
+            &LayoutPrimitiveInfo::new((100, 100).to(200, 200)),
+            &SpaceAndClipInfo { spatial_id, clip_id },
+            ColorF::new(0.0, 1.0, 0.0, 1.0),
+        );
 
-        let info = LayoutPrimitiveInfo::new((250, 100).to(350, 200));
-        builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        builder.push_rect(
+            &LayoutPrimitiveInfo::new((250, 100).to(350, 200)),
+            &SpaceAndClipInfo { spatial_id, clip_id },
+            ColorF::new(0.0, 1.0, 0.0, 1.0),
+        );
         let border_side = BorderSide {
             color: ColorF::new(0.0, 0.0, 1.0, 1.0),
             style: BorderStyle::Groove,
@@ -239,9 +248,12 @@ impl Example for App {
             do_aa: true,
         });
 
-        let info = LayoutPrimitiveInfo::new((100, 100).to(200, 200));
-        builder.push_border(&info, border_widths, border_details);
-        builder.pop_clip_id();
+        builder.push_border(
+            &LayoutPrimitiveInfo::new((100, 100).to(200, 200)),
+            &SpaceAndClipInfo { spatial_id, clip_id },
+            border_widths,
+            border_details,
+        );
 
         if false {
             // draw box shadow?
@@ -253,10 +265,10 @@ impl Example for App {
             let spread_radius = 0.0;
             let simple_border_radius = 8.0;
             let box_shadow_type = BoxShadowClipMode::Inset;
-            let info = LayoutPrimitiveInfo::with_clip_rect(rect, bounds);
 
             builder.push_box_shadow(
-                &info,
+                &LayoutPrimitiveInfo::with_clip_rect(rect, bounds),
+                &root_space_and_clip,
                 simple_box_bounds,
                 offset,
                 color,
diff --git a/examples/blob.rs b/examples/blob.rs
index 384df8ac06..5f7f856919 100644
--- a/examples/blob.rs
+++ b/examples/blob.rs
@@ -17,7 +17,7 @@ use rayon::prelude::*;
 use std::collections::HashMap;
 use std::sync::Arc;
 use webrender::api::{self, DisplayListBuilder, DocumentId, PipelineId, RenderApi, Transaction};
-use webrender::api::ColorF;
+use webrender::api::{ColorF, SpaceAndClipInfo};
 use webrender::euclid::size2;
 
 // This example shows how to implement a very basic BlobImageHandler that can only render
@@ -200,7 +200,7 @@ impl Example for App {
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
         _framebuffer_size: api::DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let blob_img1 = api.generate_blob_image_key();
@@ -220,9 +220,11 @@ impl Example for App {
         );
 
         let bounds = api::LayoutRect::new(api::LayoutPoint::zero(), builder.content_size());
-        let info = api::LayoutPrimitiveInfo::new(bounds);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
+
         builder.push_stacking_context(
-            &info,
+            &api::LayoutPrimitiveInfo::new(bounds),
+            space_and_clip.spatial_id,
             None,
             api::TransformStyle::Flat,
             api::MixBlendMode::Normal,
@@ -230,9 +232,9 @@ impl Example for App {
             api::RasterSpace::Screen,
         );
 
-        let info = api::LayoutPrimitiveInfo::new((30, 30).by(500, 500));
         builder.push_image(
-            &info,
+            &api::LayoutPrimitiveInfo::new((30, 30).by(500, 500)),
+            &space_and_clip,
             api::LayoutSize::new(500.0, 500.0),
             api::LayoutSize::new(0.0, 0.0),
             api::ImageRendering::Auto,
@@ -241,9 +243,9 @@ impl Example for App {
             ColorF::WHITE,
         );
 
-        let info = api::LayoutPrimitiveInfo::new((600, 600).by(200, 200));
         builder.push_image(
-            &info,
+            &api::LayoutPrimitiveInfo::new((600, 600).by(200, 200)),
+            &space_and_clip,
             api::LayoutSize::new(200.0, 200.0),
             api::LayoutSize::new(0.0, 0.0),
             api::ImageRendering::Auto,
diff --git a/examples/document.rs b/examples/document.rs
index affa33600d..b62744cd0c 100644
--- a/examples/document.rs
+++ b/examples/document.rs
@@ -90,7 +90,7 @@ impl Example for App {
         base_builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
         framebuffer_size: DeviceIntSize,
-        _: PipelineId,
+        _pipeline_id: PipelineId,
         _: DocumentId,
     ) {
         if self.documents.is_empty() {
@@ -102,6 +102,7 @@ impl Example for App {
         }
 
         for doc in &self.documents {
+            let space_and_clip = SpaceAndClipInfo::root_scroll(doc.pipeline_id);
             let mut builder = DisplayListBuilder::new(
                 doc.pipeline_id,
                 doc.content_rect.size,
@@ -113,6 +114,7 @@ impl Example for App {
 
             builder.push_stacking_context(
                 &LayoutPrimitiveInfo::new(doc.content_rect),
+                space_and_clip.spatial_id,
                 None,
                 TransformStyle::Flat,
                 MixBlendMode::Normal,
@@ -121,6 +123,7 @@ impl Example for App {
             );
             builder.push_rect(
                 &LayoutPrimitiveInfo::new(local_rect),
+                &space_and_clip,
                 doc.color,
             );
             builder.pop_stacking_context();
diff --git a/examples/frame_output.rs b/examples/frame_output.rs
index 84bc51dd9c..9a5962f518 100644
--- a/examples/frame_output.rs
+++ b/examples/frame_output.rs
@@ -107,6 +107,7 @@ impl App {
         );
 
         let info = LayoutPrimitiveInfo::new(document.content_rect);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
         let mut builder = DisplayListBuilder::new(
             document.pipeline_id,
             document.content_rect.size,
@@ -114,6 +115,7 @@ impl App {
 
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
@@ -121,7 +123,7 @@ impl App {
             RasterSpace::Screen,
         );
 
-        builder.push_rect(&info, ColorF::new(1.0, 1.0, 0.0, 1.0));
+        builder.push_rect(&info, &space_and_clip, ColorF::new(1.0, 1.0, 0.0, 1.0));
         builder.pop_stacking_context();
 
         txn.set_root_pipeline(pipeline_id);
@@ -145,7 +147,7 @@ impl Example for App {
         builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
         framebuffer_size: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         if self.output_document.is_none() {
@@ -155,8 +157,11 @@ impl Example for App {
         }
 
         let info = LayoutPrimitiveInfo::new((100, 100).to(200, 200));
+        let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
+
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
@@ -166,6 +171,7 @@ impl Example for App {
 
         builder.push_image(
             &info,
+            &space_and_clip,
             info.rect.size,
             LayoutSize::zero(),
             ImageRendering::Auto,
diff --git a/examples/iframe.rs b/examples/iframe.rs
index 82728a61df..484fdb5b82 100644
--- a/examples/iframe.rs
+++ b/examples/iframe.rs
@@ -35,10 +35,12 @@ impl Example for App {
 
         let sub_pipeline_id = PipelineId(pipeline_id.0, 42);
         let mut sub_builder = DisplayListBuilder::new(sub_pipeline_id, sub_bounds.size);
+        let mut space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
 
         let info = LayoutPrimitiveInfo::new(sub_bounds);
         sub_builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
@@ -47,7 +49,7 @@ impl Example for App {
         );
 
         // green rect visible == success
-        sub_builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        sub_builder.push_rect(&info, &space_and_clip, ColorF::new(0.0, 1.0, 0.0, 1.0));
         sub_builder.pop_stacking_context();
 
         let mut txn = Transaction::new();
@@ -60,19 +62,19 @@ impl Example for App {
         );
         api.send_transaction(document_id, txn);
 
-        let info = LayoutPrimitiveInfo::new(sub_bounds);
-        let reference_frame_id = builder.push_reference_frame(
+        space_and_clip.spatial_id = builder.push_reference_frame(
             &sub_bounds,
+            space_and_clip.spatial_id,
             TransformStyle::Flat,
             Some(PropertyBinding::Binding(PropertyBindingKey::new(42), LayoutTransform::identity())),
             None,
         );
-        builder.push_clip_id(reference_frame_id);
-
 
         // And this is for the root pipeline
+        let info = LayoutPrimitiveInfo::new(sub_bounds);
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
@@ -80,11 +82,9 @@ impl Example for App {
             RasterSpace::Screen,
         );
         // red rect under the iframe: if this is visible, things have gone wrong
-        builder.push_rect(&info, ColorF::new(1.0, 0.0, 0.0, 1.0));
-        builder.push_iframe(&info, sub_pipeline_id, false);
+        builder.push_rect(&info, &space_and_clip, ColorF::new(1.0, 0.0, 0.0, 1.0));
+        builder.push_iframe(&info, &space_and_clip, sub_pipeline_id, false);
         builder.pop_stacking_context();
-
-        builder.pop_clip_id();
         builder.pop_reference_frame();
     }
 }
diff --git a/examples/image_resize.rs b/examples/image_resize.rs
index 7a667b7eac..fbeec2c53f 100644
--- a/examples/image_resize.rs
+++ b/examples/image_resize.rs
@@ -26,7 +26,7 @@ impl Example for App {
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
         _framebuffer_size: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let (image_descriptor, image_data) = image_helper::make_checkerboard(32, 32);
@@ -39,8 +39,11 @@ impl Example for App {
 
         let bounds = (0, 0).to(512, 512);
         let info = LayoutPrimitiveInfo::new(bounds);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
+
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
@@ -56,6 +59,7 @@ impl Example for App {
         );
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             LayoutSize::zero(),
             ImageRendering::Auto,
@@ -70,6 +74,7 @@ impl Example for App {
         );
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             LayoutSize::zero(),
             ImageRendering::Pixelated,
diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs
index 9b2df6d7b5..c88ef0fead 100644
--- a/examples/multiwindow.rs
+++ b/examples/multiwindow.rs
@@ -187,11 +187,13 @@ impl Window {
         let layout_size = framebuffer_size.to_f32() / euclid::TypedScale::new(device_pixel_ratio);
         let mut txn = Transaction::new();
         let mut builder = DisplayListBuilder::new(self.pipeline_id, layout_size);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.pipeline_id);
 
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
@@ -203,7 +205,7 @@ impl Window {
             LayoutPoint::new(100.0, 100.0),
             LayoutSize::new(100.0, 200.0)
         ));
-        builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        builder.push_rect(&info, &space_and_clip, ColorF::new(0.0, 1.0, 0.0, 1.0));
 
         let text_bounds = LayoutRect::new(
             LayoutPoint::new(100.0, 50.0),
@@ -263,6 +265,7 @@ impl Window {
         let info = LayoutPrimitiveInfo::new(text_bounds);
         builder.push_text(
             &info,
+            &space_and_clip,
             &glyphs,
             self.font_instance_key,
             ColorF::new(1.0, 1.0, 0.0, 1.0),
diff --git a/examples/scrolling.rs b/examples/scrolling.rs
index 3575c081c5..86b1c6bc7d 100644
--- a/examples/scrolling.rs
+++ b/examples/scrolling.rs
@@ -27,14 +27,16 @@ impl Example for App {
         builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
         _framebuffer_size: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let info = LayoutPrimitiveInfo::new(
             LayoutRect::new(LayoutPoint::zero(), builder.content_size())
         );
+        let root_space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
         builder.push_stacking_context(
             &info,
+            root_space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
@@ -48,6 +50,7 @@ impl Example for App {
             let scrollbox = (0, 0).to(300, 400);
             builder.push_stacking_context(
                 &LayoutPrimitiveInfo::new((10, 10).by(0, 0)),
+                root_space_and_clip.spatial_id,
                 None,
                 TransformStyle::Flat,
                 MixBlendMode::Normal,
@@ -55,7 +58,8 @@ impl Example for App {
                 RasterSpace::Screen,
             );
             // set the scrolling clip
-            let clip_id = builder.define_scroll_frame(
+            let space_and_clip1 = builder.define_scroll_frame(
+                &root_space_and_clip,
                 None,
                 (0, 0).by(1000, 1000),
                 scrollbox,
@@ -63,30 +67,30 @@ impl Example for App {
                 None,
                 ScrollSensitivity::ScriptAndInputEvents,
             );
-            builder.push_clip_id(clip_id);
 
             // now put some content into it.
             // start with a white background
             let mut info = LayoutPrimitiveInfo::new((0, 0).to(1000, 1000));
             info.tag = Some((0, 1));
-            builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
+            builder.push_rect(&info, &space_and_clip1, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
             // let's make a 50x50 blue square as a visual reference
             let mut info = LayoutPrimitiveInfo::new((0, 0).to(50, 50));
             info.tag = Some((0, 2));
-            builder.push_rect(&info, ColorF::new(0.0, 0.0, 1.0, 1.0));
+            builder.push_rect(&info, &space_and_clip1, ColorF::new(0.0, 0.0, 1.0, 1.0));
 
             // and a 50x50 green square next to it with an offset clip
             // to see what that looks like
             let mut info =
                 LayoutPrimitiveInfo::with_clip_rect((50, 0).to(100, 50), (60, 10).to(110, 60));
             info.tag = Some((0, 3));
-            builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
+            builder.push_rect(&info, &space_and_clip1, ColorF::new(0.0, 1.0, 0.0, 1.0));
 
             // Below the above rectangles, set up a nested scrollbox. It's still in
             // the same stacking context, so note that the rects passed in need to
             // be relative to the stacking context.
-            let nested_clip_id = builder.define_scroll_frame(
+            let space_and_clip2 = builder.define_scroll_frame(
+                &space_and_clip1,
                 None,
                 (0, 100).to(300, 1000),
                 (0, 100).to(200, 300),
@@ -94,25 +98,25 @@ impl Example for App {
                 None,
                 ScrollSensitivity::ScriptAndInputEvents,
             );
-            builder.push_clip_id(nested_clip_id);
 
             // give it a giant gray background just to distinguish it and to easily
             // visually identify the nested scrollbox
             let mut info = LayoutPrimitiveInfo::new((-1000, -1000).to(5000, 5000));
             info.tag = Some((0, 4));
-            builder.push_rect(&info, ColorF::new(0.5, 0.5, 0.5, 1.0));
+            builder.push_rect(&info, &space_and_clip2, ColorF::new(0.5, 0.5, 0.5, 1.0));
 
             // add a teal square to visualize the scrolling/clipping behaviour
             // as you scroll the nested scrollbox
             let mut info = LayoutPrimitiveInfo::new((0, 200).to(50, 250));
             info.tag = Some((0, 5));
-            builder.push_rect(&info, ColorF::new(0.0, 1.0, 1.0, 1.0));
+            builder.push_rect(&info, &space_and_clip2, ColorF::new(0.0, 1.0, 1.0, 1.0));
 
             // Add a sticky frame. It will "stick" twice while scrolling, once
             // at a margin of 10px from the bottom, for 40 pixels of scrolling,
             // and once at a margin of 10px from the top, for 60 pixels of
             // scrolling.
             let sticky_id = builder.define_sticky_frame(
+                space_and_clip2.spatial_id,
                 (50, 350).by(50, 50),
                 SideOffsets2D::new(Some(10.0), None, Some(10.0), None),
                 StickyOffsetBounds::new(-40.0, 60.0),
@@ -120,21 +124,23 @@ impl Example for App {
                 LayoutVector2D::new(0.0, 0.0)
             );
 
-            builder.push_clip_id(sticky_id);
             let mut info = LayoutPrimitiveInfo::new((50, 350).by(50, 50));
             info.tag = Some((0, 6));
-            builder.push_rect(&info, ColorF::new(0.5, 0.5, 1.0, 1.0));
-            builder.pop_clip_id(); // sticky_id
+            builder.push_rect(
+                &info,
+                &SpaceAndClipInfo {
+                    spatial_id: sticky_id,
+                    clip_id: space_and_clip2.clip_id,
+                },
+                ColorF::new(0.5, 0.5, 1.0, 1.0),
+            );
 
             // just for good measure add another teal square further down and to
             // the right, which can be scrolled into view by the user
             let mut info = LayoutPrimitiveInfo::new((250, 350).to(300, 400));
             info.tag = Some((0, 7));
-            builder.push_rect(&info, ColorF::new(0.0, 1.0, 1.0, 1.0));
-
-            builder.pop_clip_id(); // nested_clip_id
+            builder.push_rect(&info, &space_and_clip2, ColorF::new(0.0, 1.0, 1.0, 1.0));
 
-            builder.pop_clip_id(); // clip_id
             builder.pop_stacking_context();
         }
 
diff --git a/examples/texture_cache_stress.rs b/examples/texture_cache_stress.rs
index 2eee1b3a8f..e8fb94a435 100644
--- a/examples/texture_cache_stress.rs
+++ b/examples/texture_cache_stress.rs
@@ -91,13 +91,16 @@ impl Example for App {
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
         _framebuffer_size: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = (0, 0).to(512, 512);
         let info = LayoutPrimitiveInfo::new(bounds);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
+
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
@@ -146,6 +149,7 @@ impl Example for App {
 
             builder.push_image(
                 &info,
+                &space_and_clip,
                 image_size,
                 LayoutSize::zero(),
                 ImageRendering::Auto,
@@ -163,6 +167,7 @@ impl Example for App {
             );
             builder.push_image(
                 &info,
+                &space_and_clip,
                 image_size,
                 LayoutSize::zero(),
                 ImageRendering::Auto,
@@ -180,6 +185,7 @@ impl Example for App {
         );
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             LayoutSize::zero(),
             ImageRendering::Auto,
diff --git a/examples/yuv.rs b/examples/yuv.rs
index b5370376ca..86d2b41247 100644
--- a/examples/yuv.rs
+++ b/examples/yuv.rs
@@ -88,13 +88,16 @@ impl Example for App {
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
         _framebuffer_size: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
+
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
@@ -161,6 +164,7 @@ impl Example for App {
         );
         builder.push_yuv_image(
             &info,
+            &space_and_clip,
             YuvData::NV12(yuv_chanel1, yuv_chanel2),
             ColorDepth::Color8,
             YuvColorSpace::Rec601,
@@ -173,6 +177,7 @@ impl Example for App {
         );
         builder.push_yuv_image(
             &info,
+            &space_and_clip,
             YuvData::PlanarYCbCr(yuv_chanel1, yuv_chanel2_1, yuv_chanel3),
             ColorDepth::Color8,
             YuvColorSpace::Rec601,
diff --git a/webrender/doc/CLIPPING_AND_POSITIONING.md b/webrender/doc/CLIPPING_AND_POSITIONING.md
index b6fd527fba..084dfebdd6 100644
--- a/webrender/doc/CLIPPING_AND_POSITIONING.md
+++ b/webrender/doc/CLIPPING_AND_POSITIONING.md
@@ -108,7 +108,7 @@ performance impacts on WebRender.
 # Clipping and Positioning in the Display List
 
 Each non-structural WebRender display list item has
- * A `ClipId` of a `SpatialNode` or `ClipNode` for positioning
+ * A `SpatialId` of a `SpatialNode` for positioning
  * A `ClipId` of a `ClipNode` or a `ClipChain` for clipping
  * An item-specific rectangular clip rectangle
 
@@ -120,28 +120,13 @@ independent of how the node is positioned and items can be clipped by any
 the item-specific clipping rectangle is applied directly to the item and should
 never result in the creation of a clip mask itself.
 
-Perhaps the most inconvenient holdover from the previous single-tree
-hierarchical design is that `SpatialNodes`, `ClipNodes`, and `ClipChains` all
-share a single `ClipId` id type. This means that the client must be a bit
-careful when using the API. For instance, when specifying the parent of
-`ClipNode` one can use the `ClipId` or another `ClipNode` or a `SpatialNode`,
-but not one for a `ClipChain`.
-
-WebRender's internal representation of clipping and positioning is not a perfect
-match to the display list representation of these concepts. This is due, again,
-to the evolutionary nature of the design. The general trend is that the display
-list gradually moves toward the internal representation. The most important of
-these incongruities is that while `ClipNodes`, sticky frames, and scroll frames
-are defined and simply return a `ClipId`, reference frames return a `ClipId` and
-also are pushed and popped like stacking contexts.
-
-## Converting `ClipId` to global `ClipScrollTree` indices
+## Converting user-exposed `ClipId`/`SpatialId` to internal indices
 
 WebRender must access `ClipNodes` and `SpatialNodes` quite a bit when building
-scenes and frames, so it tries to convert `ClipIds`, which are already
+scenes and frames, so it tries to convert `ClipId`/`SpatialId`, which are already
 per-pipeline indices, to global scene-wide indices.  Internally this is a
-conversion from `ClipId` into `SpatialNodeIndex` or
-`ClipChainIndex`. In order to make this conversion cheaper, the
+conversion from `ClipId` into `ClipNodeIndex` or `ClipChainIndex`, and from
+`SpatialId` into `SpatialNodeIndex`. In order to make this conversion cheaper, the
 `DisplayListFlattner` assigns offsets for each pipeline and node type in the
 scene-wide `ClipScrollTree`.
 
@@ -160,8 +145,7 @@ structure copies information necessary for hit testing from the
 new `ClipScrollTree` is under construction.
 
 # Ideas for the Future
-1. Expose the difference between ids for `SpatialNodes`, `ClipNodes`, and
-   `ClipChains` in the API.
+1. Expose the difference between `ClipId` and `ClipChainId` in the API.
 2. Prevent having to duplicate the `ClipScrollTree` for hit testing.
 3. Avoid having to create placeholder nodes in the `ClipScrollTree` while
    processing iframes.
diff --git a/webrender/src/clip.rs b/webrender/src/clip.rs
index 42a0e0eadb..bfba1ad729 100644
--- a/webrender/src/clip.rs
+++ b/webrender/src/clip.rs
@@ -204,6 +204,7 @@ pub struct ClipChainId(pub u32);
 // node, a bounds error will occur.
 impl ClipChainId {
     pub const NONE: Self = ClipChainId(u32::MAX);
+    pub const INVALID: Self = ClipChainId(0xDEADBEEF);
 }
 
 // A clip chain node is an id for a range of clip sources,
diff --git a/webrender/src/clip_scroll_tree.rs b/webrender/src/clip_scroll_tree.rs
index 51c1099955..726b21453b 100644
--- a/webrender/src/clip_scroll_tree.rs
+++ b/webrender/src/clip_scroll_tree.rs
@@ -3,8 +3,8 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ExternalScrollId, LayoutPoint, LayoutRect, LayoutVector2D};
-use api::{PipelineId, ScrollClamping, ScrollNodeState, ScrollLocation};
-use api::{TransformStyle, LayoutSize, LayoutTransform, PropertyBinding, ScrollSensitivity, WorldPoint};
+use api::{PipelineId, ScrollClamping, ScrollNodeState, ScrollLocation, ScrollSensitivity};
+use api::{LayoutSize, LayoutTransform, PropertyBinding, TransformStyle, WorldPoint};
 use gpu_types::TransformPalette;
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
@@ -46,6 +46,7 @@ impl CoordinateSystem {
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct SpatialNodeIndex(pub u32);
 
+//Note: these have to match ROOT_REFERENCE_FRAME_SPATIAL_ID and ROOT_SCROLL_NODE_SPATIAL_ID
 pub const ROOT_SPATIAL_NODE_INDEX: SpatialNodeIndex = SpatialNodeIndex(0);
 const TOPMOST_SCROLL_NODE_INDEX: SpatialNodeIndex = SpatialNodeIndex(1);
 
diff --git a/webrender/src/display_list_flattener.rs b/webrender/src/display_list_flattener.rs
index 8707bf4f78..9531b2eb7c 100644
--- a/webrender/src/display_list_flattener.rs
+++ b/webrender/src/display_list_flattener.rs
@@ -2,7 +2,7 @@
  * 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::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, ClipAndScrollInfo};
+use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter};
 use api::{ClipId, ColorF, ComplexClipRegion, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DisplayItemRef, ExtendMode, ExternalScrollId, AuHelpers};
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, RasterSpace, GradientStop};
@@ -10,7 +10,7 @@ use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, C
 use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
 use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, ScrollFrameDisplayItem, ScrollSensitivity};
-use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
+use api::{Shadow, SpaceAndClipInfo, SpatialId, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
 use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData};
 use app_units::Au;
 use clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore};
@@ -49,7 +49,7 @@ struct ClipNode {
 }
 
 impl ClipNode {
-    fn new(id: ClipChainId, count: usize) -> ClipNode {
+    fn new(id: ClipChainId, count: usize) -> Self {
         ClipNode {
             id,
             count,
@@ -57,16 +57,16 @@ impl ClipNode {
     }
 }
 
-/// 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 SpatialNodeIndex.
+/// A data structure that keeps track of mapping between API Ids for clips/spatials and the indices
+/// used internally in the ClipScrollTree to avoid having to do HashMap lookups. NodeIdToIndexMapper
+/// is responsible for mapping both ClipId to ClipChainIndex and SpatialId to SpatialNodeIndex.
 #[derive(Default)]
-pub struct ClipIdToIndexMapper {
+pub struct NodeIdToIndexMapper {
     clip_node_map: FastHashMap<ClipId, ClipNode>,
-    spatial_node_map: FastHashMap<ClipId, SpatialNodeIndex>,
+    spatial_node_map: FastHashMap<SpatialId, SpatialNodeIndex>,
 }
 
-impl ClipIdToIndexMapper {
+impl NodeIdToIndexMapper {
     pub fn add_clip_chain(
         &mut self,
         id: ClipId,
@@ -77,16 +77,7 @@ impl ClipIdToIndexMapper {
         debug_assert!(_old_value.is_none());
     }
 
-    pub fn map_to_parent_clip_chain(
-        &mut self,
-        id: ClipId,
-        parent_id: &ClipId,
-    ) {
-        let parent_node = self.clip_node_map[parent_id];
-        self.add_clip_chain(id, parent_node.id, parent_node.count);
-    }
-
-    pub fn map_spatial_node(&mut self, id: ClipId, index: SpatialNodeIndex) {
+    pub fn map_spatial_node(&mut self, id: SpatialId, index: SpatialNodeIndex) {
         let _old_value = self.spatial_node_map.insert(id, index);
         debug_assert!(_old_value.is_none());
     }
@@ -95,18 +86,12 @@ impl ClipIdToIndexMapper {
         self.clip_node_map[id]
     }
 
-    pub fn get_clip_chain_id(&self, id: &ClipId) -> ClipChainId {
-        self.clip_node_map[id].id
+    pub fn get_clip_chain_id(&self, id: ClipId) -> ClipChainId {
+        self.clip_node_map[&id].id
     }
 
-    pub fn get_spatial_node_index(&self, id: ClipId) -> SpatialNodeIndex {
-        match id {
-            ClipId::Clip(..) |
-            ClipId::Spatial(..) => {
-                self.spatial_node_map[&id]
-            }
-            ClipId::ClipChain(_) => panic!("Tried to use ClipChain as scroll node."),
-        }
+    pub fn get_spatial_node_index(&self, id: SpatialId) -> SpatialNodeIndex {
+        self.spatial_node_map[&id]
     }
 }
 
@@ -127,9 +112,9 @@ pub struct DisplayListFlattener<'a> {
     /// output textures.
     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,
+    /// The data structure that converting between ClipId/SpatialId and the various
+    /// index types that the ClipScrollTree uses.
+    id_to_index_mapper: NodeIdToIndexMapper,
 
     /// A stack of stacking context properties.
     sc_stack: Vec<FlattenedStackingContext>,
@@ -188,7 +173,7 @@ impl<'a> DisplayListFlattener<'a> {
             font_instances,
             config: *frame_builder_config,
             output_pipelines,
-            id_to_index_mapper: ClipIdToIndexMapper::default(),
+            id_to_index_mapper: NodeIdToIndexMapper::default(),
             hit_testing_runs: Vec::new(),
             pending_shadow_items: VecDeque::new(),
             sc_stack: Vec::new(),
@@ -453,11 +438,6 @@ impl<'a> DisplayListFlattener<'a> {
         frame_size: &LayoutSize,
     ) {
         let pipeline_id = pipeline.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);
 
         self.push_stacking_context(
             pipeline_id,
@@ -465,8 +445,8 @@ impl<'a> DisplayListFlattener<'a> {
             TransformStyle::Flat,
             true,
             true,
-            root_scroll_node,
-            None,
+            ROOT_SPATIAL_NODE_INDEX,
+            ClipChainId::NONE,
             RasterSpace::Screen,
         );
 
@@ -477,6 +457,10 @@ impl<'a> DisplayListFlattener<'a> {
         if self.scene.root_pipeline_id != Some(pipeline_id) {
             if let Some(pipeline) = self.scene.pipelines.get(&pipeline_id) {
                 if let Some(bg_color) = pipeline.background_color {
+                    let reference_frame_info = ScrollNodeAndClipChain::new(
+                        self.id_to_index_mapper.get_spatial_node_index(SpatialId::root_reference_frame(pipeline_id)),
+                        ClipChainId::NONE,
+                    );
                     let root_bounds = LayoutRect::new(LayoutPoint::zero(), *frame_size);
                     let info = LayoutPrimitiveInfo::new(root_bounds);
                     self.add_solid_rectangle(
@@ -488,7 +472,11 @@ impl<'a> DisplayListFlattener<'a> {
             }
         }
 
-        self.flatten_items(&mut pipeline.display_list.iter(), pipeline_id, LayoutVector2D::zero());
+        self.flatten_items(
+            &mut pipeline.display_list.iter(),
+            pipeline_id,
+            LayoutVector2D::zero(),
+        );
 
         self.pop_stacking_context();
     }
@@ -506,12 +494,10 @@ impl<'a> DisplayListFlattener<'a> {
                     None => break,
                 };
 
-                if SpecificDisplayItem::PopReferenceFrame == *item.item() {
-                    return;
-                }
-
-                if SpecificDisplayItem::PopStackingContext == *item.item() {
-                    return;
+                match item.item() {
+                    SpecificDisplayItem::PopReferenceFrame |
+                    SpecificDisplayItem::PopStackingContext => return,
+                    _ => (),
                 }
 
                 self.flatten_item(
@@ -533,8 +519,7 @@ impl<'a> DisplayListFlattener<'a> {
         &mut self,
         item: &DisplayItemRef,
         info: &StickyFrameDisplayItem,
-        clip_and_scroll: &ScrollNodeAndClipChain,
-        parent_id: &ClipId,
+        parent_node_index: SpatialNodeIndex,
         reference_frame_relative_offset: &LayoutVector2D,
     ) {
         let frame_rect = item.rect().translate(reference_frame_relative_offset);
@@ -547,20 +532,19 @@ impl<'a> DisplayListFlattener<'a> {
         );
 
         let index = self.clip_scroll_tree.add_sticky_frame(
-            clip_and_scroll.spatial_node_index, /* parent id */
+            parent_node_index,
             sticky_frame_info,
             info.id.pipeline_id(),
         );
         self.id_to_index_mapper.map_spatial_node(info.id, index);
-        self.id_to_index_mapper.map_to_parent_clip_chain(info.id, parent_id);
     }
 
     fn flatten_scroll_frame(
         &mut self,
         item: &DisplayItemRef,
         info: &ScrollFrameDisplayItem,
+        parent_node_index: SpatialNodeIndex,
         pipeline_id: PipelineId,
-        clip_and_scroll_ids: &ClipAndScrollInfo,
         reference_frame_relative_offset: &LayoutVector2D,
     ) {
         let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
@@ -574,20 +558,18 @@ impl<'a> DisplayListFlattener<'a> {
         // This is useful when calculating scroll extents for the
         // 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);
+        let frame_rect = clip_region.main.translate(reference_frame_relative_offset);
+        let content_size = item.rect().size;
 
-        debug_assert!(info.clip_id != info.scroll_frame_id);
-
-        self.add_clip_node(info.clip_id, clip_and_scroll_ids, clip_region);
+        self.add_clip_node(info.clip_id, item.space_and_clip_info(), clip_region);
 
         self.add_scroll_frame(
             info.scroll_frame_id,
-            info.clip_id,
+            parent_node_index,
             info.external_id,
             pipeline_id,
             &frame_rect,
-            &content_rect.size,
+            &content_size,
             info.scroll_sensitivity,
             ScrollFrameKind::Explicit,
         );
@@ -597,33 +579,34 @@ impl<'a> DisplayListFlattener<'a> {
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
-        item: &DisplayItemRef,
+        parent_spatial_node: SpatialNodeIndex,
+        origin: LayoutPoint,
         reference_frame: &ReferenceFrame,
-        clip_and_scroll_ids: &ClipAndScrollInfo,
         reference_frame_relative_offset: LayoutVector2D,
     ) {
         self.push_reference_frame(
             reference_frame.id,
-            Some(clip_and_scroll_ids.scroll_node_id),
-            clip_and_scroll_ids.clip_node_id,
+            Some(parent_spatial_node),
             pipeline_id,
             reference_frame.transform_style,
             reference_frame.transform,
             reference_frame.perspective,
-            reference_frame_relative_offset + item.rect().origin.to_vector(),
+            reference_frame_relative_offset + origin.to_vector(),
         );
 
         self.flatten_items(traversal, pipeline_id, LayoutVector2D::zero());
     }
 
+
     fn flatten_stacking_context(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
-        item: &DisplayItemRef,
         stacking_context: &StackingContext,
-        scroll_node_id: ClipId,
-        reference_frame_relative_offset: LayoutVector2D,
+        spatial_node_index: SpatialNodeIndex,
+        origin: LayoutPoint,
+        filters: ItemRange<FilterOp>,
+        reference_frame_relative_offset: &LayoutVector2D,
         is_backface_visible: bool,
     ) {
         // Avoid doing unnecessary work for empty stacking contexts.
@@ -636,26 +619,31 @@ impl<'a> DisplayListFlattener<'a> {
             // TODO(optimization?): self.traversal.display_list()
             let display_list = self.scene.get_display_list_for_pipeline(pipeline_id);
             CompositeOps::new(
-                stacking_context.filter_ops_for_compositing(display_list, item.filters()),
+                stacking_context.filter_ops_for_compositing(display_list, filters),
                 stacking_context.mix_blend_mode_for_compositing(),
             )
         };
 
+        let clip_chain_id = match stacking_context.clip_id {
+            Some(clip_id) => self.id_to_index_mapper.get_clip_chain_id(clip_id),
+            None => ClipChainId::NONE,
+        };
+
         self.push_stacking_context(
             pipeline_id,
             composition_operations,
             stacking_context.transform_style,
             is_backface_visible,
             false,
-            scroll_node_id,
-            stacking_context.clip_node_id,
+            spatial_node_index,
+            clip_chain_id,
             stacking_context.raster_space,
         );
 
         self.flatten_items(
             traversal,
             pipeline_id,
-            reference_frame_relative_offset + item.rect().origin.to_vector(),
+            *reference_frame_relative_offset + origin.to_vector(),
         );
 
         self.pop_stacking_context();
@@ -665,7 +653,7 @@ impl<'a> DisplayListFlattener<'a> {
         &mut self,
         item: &DisplayItemRef,
         info: &IframeDisplayItem,
-        clip_and_scroll_ids: &ClipAndScrollInfo,
+        spatial_node_index: SpatialNodeIndex,
         reference_frame_relative_offset: &LayoutVector2D,
     ) {
         let iframe_pipeline_id = info.pipeline_id;
@@ -678,21 +666,20 @@ impl<'a> DisplayListFlattener<'a> {
         };
 
         let clip_chain_index = self.add_clip_node(
-            info.clip_id,
-            clip_and_scroll_ids,
+            ClipId::root(iframe_pipeline_id),
+            item.space_and_clip_info(),
             ClipRegion::create_for_clip_node_with_local_clip(
                 item.clip_rect(),
-                reference_frame_relative_offset
+                reference_frame_relative_offset,
             ),
         );
         self.pipeline_clip_chain_stack.push(clip_chain_index);
 
         let bounds = item.rect();
         let origin = *reference_frame_relative_offset + bounds.origin.to_vector();
-        self.push_reference_frame(
-            ClipId::root_reference_frame(iframe_pipeline_id),
-            Some(info.clip_id),
-            None,
+        let spatial_node_index = self.push_reference_frame(
+            SpatialId::root_reference_frame(iframe_pipeline_id),
+            Some(spatial_node_index),
             iframe_pipeline_id,
             TransformStyle::Flat,
             None,
@@ -702,8 +689,8 @@ impl<'a> DisplayListFlattener<'a> {
 
         let iframe_rect = LayoutRect::new(LayoutPoint::zero(), bounds.size);
         self.add_scroll_frame(
-            ClipId::root_scroll_node(iframe_pipeline_id),
-            ClipId::root_reference_frame(iframe_pipeline_id),
+            SpatialId::root_scroll_node(iframe_pipeline_id),
+            spatial_node_index,
             Some(ExternalScrollId(0, iframe_pipeline_id)),
             iframe_pipeline_id,
             &iframe_rect,
@@ -726,10 +713,17 @@ impl<'a> DisplayListFlattener<'a> {
         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.map_clip_and_scroll(&clip_and_scroll_ids);
-
+        let space_and_clip = item.space_and_clip_info();
+        let clip_and_scroll = ScrollNodeAndClipChain::new(
+            self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id),
+            if space_and_clip.clip_id.is_valid() {
+                self.id_to_index_mapper.get_clip_chain_id(space_and_clip.clip_id)
+            } else {
+                ClipChainId::INVALID
+            },
+        );
         let prim_info = item.get_layout_primitive_info(&reference_frame_relative_offset);
+
         match *item.item() {
             SpecificDisplayItem::Image(ref info) => {
                 self.add_image(
@@ -861,10 +855,11 @@ impl<'a> DisplayListFlattener<'a> {
                 self.flatten_stacking_context(
                     &mut subtraversal,
                     pipeline_id,
-                    &item,
                     &info.stacking_context,
-                    clip_and_scroll_ids.scroll_node_id,
-                    reference_frame_relative_offset,
+                    clip_and_scroll.spatial_node_index,
+                    item.rect().origin,
+                    item.filters(),
+                    &reference_frame_relative_offset,
                     prim_info.is_backface_visible,
                 );
                 return Some(subtraversal);
@@ -874,20 +869,19 @@ impl<'a> DisplayListFlattener<'a> {
                 self.flatten_reference_frame(
                     &mut subtraversal,
                     pipeline_id,
-                    &item,
+                    clip_and_scroll.spatial_node_index,
+                    item.rect().origin,
                     &info.reference_frame,
-                    &clip_and_scroll_ids,
                     reference_frame_relative_offset,
                 );
                 return Some(subtraversal);
-
             }
             SpecificDisplayItem::Iframe(ref info) => {
                 self.flatten_iframe(
                     &item,
                     info,
-                    &clip_and_scroll_ids,
-                    &reference_frame_relative_offset
+                    clip_and_scroll.spatial_node_index,
+                    &reference_frame_relative_offset,
                 );
             }
             SpecificDisplayItem::Clip(ref info) => {
@@ -898,7 +892,7 @@ impl<'a> DisplayListFlattener<'a> {
                     info.image_mask,
                     &reference_frame_relative_offset,
                 );
-                self.add_clip_node(info.id, &clip_and_scroll_ids, clip_region);
+                self.add_clip_node(info.id, space_and_clip, clip_region);
             }
             SpecificDisplayItem::ClipChain(ref info) => {
                 // For a user defined clip-chain the parent (if specified) must
@@ -907,7 +901,7 @@ impl<'a> DisplayListFlattener<'a> {
                 // is used to provide a root clip chain for iframes.
                 let mut parent_clip_chain_id = match info.parent {
                     Some(id) => {
-                        self.id_to_index_mapper.get_clip_chain_id(&ClipId::ClipChain(id))
+                        self.id_to_index_mapper.get_clip_chain_id(ClipId::ClipChain(id))
                     }
                     None => {
                         self.pipeline_clip_chain_stack.last().cloned().unwrap()
@@ -923,11 +917,11 @@ impl<'a> DisplayListFlattener<'a> {
                 let mut clip_chain_id = parent_clip_chain_id;
 
                 // For each specified clip id
-                for item in self.get_clip_chain_items(pipeline_id, item.clip_chain_items()) {
+                for clip_item in self.get_clip_chain_items(pipeline_id, item.clip_chain_items()) {
                     // Map the ClipId to an existing clip chain node.
                     let item_clip_node = self
                         .id_to_index_mapper
-                        .get_clip_node(&item);
+                        .get_clip_node(&clip_item);
 
                     let mut clip_node_clip_chain_id = item_clip_node.id;
 
@@ -973,25 +967,25 @@ impl<'a> DisplayListFlattener<'a> {
                 self.flatten_scroll_frame(
                     &item,
                     info,
+                    clip_and_scroll.spatial_node_index,
                     pipeline_id,
-                    &clip_and_scroll_ids,
-                    &reference_frame_relative_offset
+                    &reference_frame_relative_offset,
                 );
             }
             SpecificDisplayItem::StickyFrame(ref info) => {
                 self.flatten_sticky_frame(
                     &item,
                     info,
-                    &clip_and_scroll,
-                    &clip_and_scroll_ids.scroll_node_id,
-                    &reference_frame_relative_offset
+                    clip_and_scroll.spatial_node_index,
+                    &reference_frame_relative_offset,
                 );
             }
 
             // Do nothing; these are dummy items for the display list parser
             SpecificDisplayItem::SetGradientStops => {}
 
-            SpecificDisplayItem::PopStackingContext | SpecificDisplayItem::PopReferenceFrame => {
+            SpecificDisplayItem::PopReferenceFrame |
+            SpecificDisplayItem::PopStackingContext => {
                 unreachable!("Should have returned in parent method.")
             }
             SpecificDisplayItem::PushShadow(shadow) => {
@@ -1030,13 +1024,12 @@ impl<'a> DisplayListFlattener<'a> {
                     .clip_interner
                     .intern(&item, || ());
 
-                clip_chain_id = self.clip_store
-                                    .add_clip_chain_node(
-                                        handle,
-                                        local_pos,
-                                        spatial_node_index,
-                                        clip_chain_id,
-                                    );
+                clip_chain_id = self.clip_store.add_clip_chain_node(
+                    handle,
+                    local_pos,
+                    spatial_node_index,
+                    clip_chain_id,
+                );
             }
 
             clip_chain_id
@@ -1218,16 +1211,10 @@ impl<'a> DisplayListFlattener<'a> {
         transform_style: TransformStyle,
         is_backface_visible: bool,
         is_pipeline_root: bool,
-        spatial_node: ClipId,
-        clipping_node: Option<ClipId>,
+        spatial_node_index: SpatialNodeIndex,
+        clip_chain_id: ClipChainId,
         requested_raster_space: RasterSpace,
     ) {
-        let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(spatial_node);
-        let clip_chain_id = match clipping_node {
-            Some(ref clipping_node) => self.id_to_index_mapper.get_clip_chain_id(clipping_node),
-            None => ClipChainId::NONE,
-        };
-
         // Check if this stacking context is the root of a pipeline, and the caller
         // has requested it as an output frame.
         let frame_output_pipeline_id = if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) {
@@ -1294,7 +1281,7 @@ impl<'a> DisplayListFlattener<'a> {
         // has a clip node. In the future, we may decide during
         // prepare step to skip the intermediate surface if the
         // clip node doesn't affect the stacking context rect.
-        let should_isolate = clipping_node.is_some();
+        let should_isolate = clip_chain_id != ClipChainId::NONE;
 
         // Push the SC onto the stack, so we know how to handle things in
         // pop_stacking_context.
@@ -1586,16 +1573,14 @@ impl<'a> DisplayListFlattener<'a> {
 
     pub fn push_reference_frame(
         &mut self,
-        reference_frame_id: ClipId,
-        parent_scroll_id: Option<ClipId>,
-        parent_clip_id: Option<ClipId>,
+        reference_frame_id: SpatialId,
+        parent_index: Option<SpatialNodeIndex>,
         pipeline_id: PipelineId,
         transform_style: TransformStyle,
         source_transform: Option<PropertyBinding<LayoutTransform>>,
         source_perspective: Option<LayoutTransform>,
         origin_in_parent_reference_frame: LayoutVector2D,
     ) -> SpatialNodeIndex {
-        let parent_index = parent_scroll_id.map(|id| self.id_to_index_mapper.get_spatial_node_index(id));
         let index = self.clip_scroll_tree.add_reference_frame(
             parent_index,
             transform_style,
@@ -1606,11 +1591,6 @@ impl<'a> DisplayListFlattener<'a> {
         );
         self.id_to_index_mapper.map_spatial_node(reference_frame_id, index);
 
-        match parent_clip_id.or(parent_scroll_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, ClipChainId::NONE, 0),
-        }
         index
     }
 
@@ -1625,9 +1605,10 @@ impl<'a> DisplayListFlattener<'a> {
             register_prim_chase_id(id);
         }
 
-        self.push_reference_frame(
-            ClipId::root_reference_frame(pipeline_id),
-            None,
+        self.id_to_index_mapper.add_clip_chain(ClipId::root(pipeline_id), ClipChainId::NONE, 0);
+
+        let spatial_node_index = self.push_reference_frame(
+            SpatialId::root_reference_frame(pipeline_id),
             None,
             pipeline_id,
             TransformStyle::Flat,
@@ -1637,8 +1618,8 @@ impl<'a> DisplayListFlattener<'a> {
         );
 
         self.add_scroll_frame(
-            ClipId::root_scroll_node(pipeline_id),
-            ClipId::root_reference_frame(pipeline_id),
+            SpatialId::root_scroll_node(pipeline_id),
+            spatial_node_index,
             Some(ExternalScrollId(0, pipeline_id)),
             pipeline_id,
             &LayoutRect::new(LayoutPoint::zero(), *viewport_size),
@@ -1651,7 +1632,7 @@ impl<'a> DisplayListFlattener<'a> {
     pub fn add_clip_node<I>(
         &mut self,
         new_node_id: ClipId,
-        parent: &ClipAndScrollInfo,
+        space_and_clip: &SpaceAndClipInfo,
         clip_region: ClipRegion<I>,
     ) -> ClipChainId
     where
@@ -1661,15 +1642,9 @@ impl<'a> DisplayListFlattener<'a> {
         // and the positioning node associated with those clip sources.
 
         // Map from parent ClipId to existing clip-chain.
-        let mut parent_clip_chain_index = self
-            .id_to_index_mapper
-            .get_clip_chain_id(&parent.clip_node_id());
+        let mut parent_clip_chain_index = self.id_to_index_mapper.get_clip_chain_id(space_and_clip.clip_id);
         // Map the ClipId for the positioning node to a spatial node index.
-        let spatial_node = self.id_to_index_mapper.get_spatial_node_index(parent.scroll_node_id);
-
-        // Add a mapping for this ClipId in case it's referenced as a positioning node.
-        self.id_to_index_mapper
-            .map_spatial_node(new_node_id, spatial_node);
+        let spatial_node = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id);
 
         let mut clip_count = 0;
 
@@ -1740,8 +1715,8 @@ impl<'a> DisplayListFlattener<'a> {
 
     pub fn add_scroll_frame(
         &mut self,
-        new_node_id: ClipId,
-        parent_id: ClipId,
+        new_node_id: SpatialId,
+        parent_node_index: SpatialNodeIndex,
         external_id: Option<ExternalScrollId>,
         pipeline_id: PipelineId,
         frame_rect: &LayoutRect,
@@ -1749,7 +1724,6 @@ impl<'a> DisplayListFlattener<'a> {
         scroll_sensitivity: ScrollSensitivity,
         frame_kind: ScrollFrameKind,
     ) -> SpatialNodeIndex {
-        let parent_node_index = self.id_to_index_mapper.get_spatial_node_index(parent_id);
         let node_index = self.clip_scroll_tree.add_scroll_frame(
             parent_node_index,
             external_id,
@@ -1760,7 +1734,6 @@ impl<'a> DisplayListFlattener<'a> {
             frame_kind,
         );
         self.id_to_index_mapper.map_spatial_node(new_node_id, node_index);
-        self.id_to_index_mapper.map_to_parent_clip_chain(new_node_id, &parent_id);
         node_index
     }
 
@@ -2477,17 +2450,6 @@ impl<'a> DisplayListFlattener<'a> {
         );
     }
 
-    pub fn map_clip_and_scroll(&mut self, info: &ClipAndScrollInfo) -> ScrollNodeAndClipChain {
-        ScrollNodeAndClipChain::new(
-            self.id_to_index_mapper.get_spatial_node_index(info.scroll_node_id),
-            self.id_to_index_mapper.get_clip_chain_id(&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 add_primitive_instance_to_3d_root(&mut self, instance: PrimitiveInstance) {
         // find the 3D root and append to the children list
         for sc in self.sc_stack.iter_mut().rev() {
diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs
index db99871cfd..2bcc88b09c 100644
--- a/webrender/src/render_backend.rs
+++ b/webrender/src/render_backend.rs
@@ -1589,8 +1589,8 @@ impl ToDebugString for SpecificDisplayItem {
             SpecificDisplayItem::PopAllShadows => String::from("pop_all_shadows"),
             SpecificDisplayItem::PopReferenceFrame => String::from("pop_reference_frame"),
             SpecificDisplayItem::PopStackingContext => String::from("pop_stacking_context"),
-            SpecificDisplayItem::PushReferenceFrame(..) => String::from("push_reference_frame"),
             SpecificDisplayItem::PushShadow(..) => String::from("push_shadow"),
+            SpecificDisplayItem::PushReferenceFrame(..) => String::from("push_reference_frame"),
             SpecificDisplayItem::PushStackingContext(..) => String::from("push_stacking_context"),
             SpecificDisplayItem::RadialGradient(..) => String::from("radial_gradient"),
             SpecificDisplayItem::Rectangle(..) => String::from("rectangle"),
diff --git a/webrender_api/src/display_item.rs b/webrender_api/src/display_item.rs
index abfc78b505..72dec8dce6 100644
--- a/webrender_api/src/display_item.rs
+++ b/webrender_api/src/display_item.rs
@@ -20,32 +20,6 @@ pub const MAX_BLUR_RADIUS: f32 = 300.;
 // a list of values nearby that this item consumes. The traversal
 // iterator should handle finding these.
 
-#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
-pub struct ClipAndScrollInfo {
-    pub scroll_node_id: ClipId,
-    pub clip_node_id: Option<ClipId>,
-}
-
-impl ClipAndScrollInfo {
-    pub fn simple(node_id: ClipId) -> ClipAndScrollInfo {
-        ClipAndScrollInfo {
-            scroll_node_id: node_id,
-            clip_node_id: None,
-        }
-    }
-
-    pub fn new(scroll_node_id: ClipId, clip_node_id: ClipId) -> ClipAndScrollInfo {
-        ClipAndScrollInfo {
-            scroll_node_id,
-            clip_node_id: Some(clip_node_id),
-        }
-    }
-
-    pub fn clip_node_id(&self) -> ClipId {
-        self.clip_node_id.unwrap_or(self.scroll_node_id)
-    }
-}
-
 /// A tag that can be used to identify items during hit testing. If the tag
 /// is missing then the item doesn't take part in hit testing at all. This
 /// is composed of two numbers. In Servo, the first is an identifier while the
@@ -60,8 +34,8 @@ pub type ItemTag = (u64, u16);
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct GenericDisplayItem<T> {
     pub item: T,
-    pub clip_and_scroll: ClipAndScrollInfo,
     pub layout: LayoutPrimitiveInfo,
+    pub space_and_clip: SpaceAndClipInfo,
 }
 
 pub type DisplayItem = GenericDisplayItem<SpecificDisplayItem>;
@@ -71,8 +45,8 @@ pub type DisplayItem = GenericDisplayItem<SpecificDisplayItem>;
 #[derive(Serialize)]
 pub struct SerializedDisplayItem<'a> {
     pub item: &'a SpecificDisplayItem,
-    pub clip_and_scroll: &'a ClipAndScrollInfo,
     pub layout: &'a LayoutPrimitiveInfo,
+    pub space_and_clip: &'a SpaceAndClipInfo,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
@@ -103,6 +77,29 @@ impl LayoutPrimitiveInfo {
 
 pub type LayoutPrimitiveInfo = PrimitiveInfo<LayoutPixel>;
 
+/// Per-primitive information about the nodes in the clip tree and
+/// the spatial tree that the primitive belongs to.
+///
+/// Note: this is a separate struct from `PrimitiveInfo` because
+/// it needs indirectional mapping during the DL flattening phase,
+/// turning into `ScrollNodeAndClipChain`.
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct SpaceAndClipInfo {
+    pub spatial_id: SpatialId,
+    pub clip_id: ClipId,
+}
+
+impl SpaceAndClipInfo {
+    /// Create a new space/clip info associated with the root
+    /// scroll frame.
+    pub fn root_scroll(pipeline_id: PipelineId) -> Self {
+        SpaceAndClipInfo {
+            spatial_id: SpatialId::root_scroll_node(pipeline_id),
+            clip_id: ClipId::root(pipeline_id),
+        }
+    }
+}
+
 #[repr(u64)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum SpecificDisplayItem {
@@ -121,10 +118,10 @@ pub enum SpecificDisplayItem {
     RadialGradient(RadialGradientDisplayItem),
     ClipChain(ClipChainItem),
     Iframe(IframeDisplayItem),
+    PushReferenceFrame(ReferenceFrameDisplayListItem),
+    PopReferenceFrame,
     PushStackingContext(PushStackingContextDisplayItem),
     PopStackingContext,
-    PushReferenceFrame(PushReferenceFrameDisplayListItem),
-    PopReferenceFrame,
     SetGradientStops,
     PushShadow(Shadow),
     PopAllShadows,
@@ -154,10 +151,10 @@ pub enum CompletelySpecificDisplayItem {
     Gradient(GradientDisplayItem),
     RadialGradient(RadialGradientDisplayItem),
     Iframe(IframeDisplayItem),
+    PushReferenceFrame(ReferenceFrameDisplayListItem),
+    PopReferenceFrame,
     PushStackingContext(PushStackingContextDisplayItem, Vec<FilterOp>),
     PopStackingContext,
-    PushReferenceFrame(PushReferenceFrameDisplayListItem),
-    PopReferenceFrame,
     SetGradientStops(Vec<GradientStop>),
     PushShadow(Shadow),
     PopAllShadows,
@@ -194,7 +191,7 @@ impl StickyOffsetBounds {
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct StickyFrameDisplayItem {
-    pub id: ClipId,
+    pub id: SpatialId,
 
     /// The margins that should be maintained between the edge of the parent viewport and this
     /// sticky frame. A margin of None indicates that the sticky frame should not stick at all
@@ -230,7 +227,7 @@ pub enum ScrollSensitivity {
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ScrollFrameDisplayItem {
     pub clip_id: ClipId,
-    pub scroll_frame_id: ClipId,
+    pub scroll_frame_id: SpatialId,
     pub external_id: Option<ExternalScrollId>,
     pub image_mask: Option<ImageMask>,
     pub scroll_sensitivity: ScrollSensitivity,
@@ -517,7 +514,7 @@ pub struct RadialGradientDisplayItem {
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
-pub struct PushReferenceFrameDisplayListItem {
+pub struct ReferenceFrameDisplayListItem {
     pub reference_frame: ReferenceFrame,
 }
 
@@ -532,7 +529,7 @@ pub struct ReferenceFrame {
     pub transform_style: TransformStyle,
     pub transform: Option<PropertyBinding<LayoutTransform>>,
     pub perspective: Option<LayoutTransform>,
-    pub id: ClipId,
+    pub id: SpatialId,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
@@ -544,7 +541,7 @@ pub struct PushStackingContextDisplayItem {
 pub struct StackingContext {
     pub transform_style: TransformStyle,
     pub mix_blend_mode: MixBlendMode,
-    pub clip_node_id: Option<ClipId>,
+    pub clip_id: Option<ClipId>,
     pub raster_space: RasterSpace,
 } // IMPLICIT: filters: Vec<FilterOp>
 
@@ -646,7 +643,6 @@ impl FilterOp {
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct IframeDisplayItem {
-    pub clip_id: ClipId,
     pub pipeline_id: PipelineId,
     pub ignore_missing_pipeline: bool,
 }
@@ -856,48 +852,82 @@ impl ComplexClipRegion {
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ClipChainId(pub u64, pub PipelineId);
 
+/// A reference to a clipping node defining how an item is clipped.
 #[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;
+const ROOT_CLIP_ID: usize = 0;
 
 impl ClipId {
-    pub fn root_scroll_node(pipeline_id: PipelineId) -> ClipId {
-        ClipId::Spatial(ROOT_SCROLL_NODE_CLIP_ID, pipeline_id)
+    /// Return the root clip ID - effectively doing no clipping.
+    pub fn root(pipeline_id: PipelineId) -> Self {
+        ClipId::Clip(ROOT_CLIP_ID, pipeline_id)
     }
 
-    pub fn root_reference_frame(pipeline_id: PipelineId) -> ClipId {
-        ClipId::Spatial(ROOT_REFERENCE_FRAME_CLIP_ID, pipeline_id)
+    /// Return an invalid clip ID - needed in places where we carry
+    /// one but need to not attempt to use it.
+    pub fn invalid() -> Self {
+        ClipId::Clip(!0, PipelineId::dummy())
     }
 
     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 {
+    pub fn is_root(&self) -> bool {
         match *self {
-            ClipId::Spatial(ROOT_SCROLL_NODE_CLIP_ID, _) => true,
-            _ => false,
+            ClipId::Clip(id, _) => id == ROOT_CLIP_ID,
+            ClipId::ClipChain(_) => false,
         }
     }
 
-    pub fn is_root_reference_frame(&self) -> bool {
+    pub fn is_valid(&self) -> bool {
         match *self {
-            ClipId::Spatial(ROOT_REFERENCE_FRAME_CLIP_ID, _) => true,
-            _ => false,
+            ClipId::Clip(id, _) => id != !0,
+            _ => true,
         }
     }
 }
 
+/// A reference to a spatial node defining item positioning.
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub struct SpatialId(pub usize, PipelineId);
+
+const ROOT_REFERENCE_FRAME_SPATIAL_ID: usize = 0;
+const ROOT_SCROLL_NODE_SPATIAL_ID: usize = 1;
+
+impl SpatialId {
+    pub fn new(spatial_node_index: usize, pipeline_id: PipelineId) -> Self {
+        SpatialId(spatial_node_index, pipeline_id)
+    }
+
+    pub fn root_reference_frame(pipeline_id: PipelineId) -> Self {
+        SpatialId(ROOT_REFERENCE_FRAME_SPATIAL_ID, pipeline_id)
+    }
+
+    pub fn root_scroll_node(pipeline_id: PipelineId) -> Self {
+        SpatialId(ROOT_SCROLL_NODE_SPATIAL_ID, pipeline_id)
+    }
+
+    pub fn pipeline_id(&self) -> PipelineId {
+        self.1
+    }
+
+    pub fn is_root_reference_frame(&self) -> bool {
+        self.0 == ROOT_REFERENCE_FRAME_SPATIAL_ID
+    }
+
+    pub fn is_root_scroll_node(&self) -> bool {
+        self.0 == ROOT_SCROLL_NODE_SPATIAL_ID
+    }
+}
+
 /// 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
diff --git a/webrender_api/src/display_list.rs b/webrender_api/src/display_list.rs
index e88bf5c804..f96db40ea3 100644
--- a/webrender_api/src/display_list.rs
+++ b/webrender_api/src/display_list.rs
@@ -15,16 +15,16 @@ use std::ops::Range;
 use std::{io, mem, ptr, slice};
 use time::precise_time_ns;
 use {AlphaType, BorderDetails, BorderDisplayItem, BorderRadius, BoxShadowClipMode, CacheMarkerDisplayItem};
-use {BoxShadowDisplayItem, ClipAndScrollInfo, ClipChainId, ClipChainItem, ClipDisplayItem, ClipId};
+use {BoxShadowDisplayItem, ClipChainId, ClipChainItem, ClipDisplayItem, ClipId};
 use {ColorF, ComplexClipRegion, DisplayItem, ExtendMode, ExternalScrollId, FilterOp};
 use {FontInstanceKey, GlyphInstance, GlyphOptions, RasterSpace, Gradient, GradientBuilder};
 use {GradientDisplayItem, GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask};
 use {ImageRendering, LayoutPoint, LayoutPrimitiveInfo, LayoutRect, LayoutSideOffsets, LayoutSize};
 use {LayoutTransform, LayoutVector2D, LineDisplayItem, LineOrientation, LineStyle, MixBlendMode};
-use {PipelineId, PropertyBinding, PushReferenceFrameDisplayListItem};
+use {PipelineId, PropertyBinding, ReferenceFrameDisplayListItem};
 use {PushStackingContextDisplayItem, RadialGradient, RadialGradientDisplayItem};
 use {RectangleDisplayItem, ReferenceFrame, ScrollFrameDisplayItem, ScrollSensitivity};
-use {SerializedDisplayItem, Shadow, SpecificDisplayItem};
+use {SerializedDisplayItem, Shadow, SpaceAndClipInfo, SpatialId, SpecificDisplayItem};
 use {StackingContext, StickyFrameDisplayItem, StickyOffsetBounds};
 use {TextDisplayItem, TransformStyle, YuvColorSpace, YuvData, YuvImageDisplayItem, ColorDepth};
 
@@ -32,13 +32,13 @@ use {TextDisplayItem, TransformStyle, YuvColorSpace, YuvData, YuvImageDisplayIte
 // 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.
+// See ROOT_REFERENCE_FRAME_SPATIAL_ID and ROOT_SCROLL_NODE_SPATIAL_ID
 // 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;
+// See ROOT_SCROLL_NODE_SPATIAL_ID
+const FIRST_CLIP_NODE_INDEX: usize = 1;
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
@@ -213,9 +213,8 @@ impl<'a> BuiltDisplayListIter<'a> {
             cur_item: DisplayItem {
                 // Dummy data, will be overwritten by `next`
                 item: SpecificDisplayItem::PopStackingContext,
-                clip_and_scroll:
-                    ClipAndScrollInfo::simple(ClipId::root_scroll_node(PipelineId::dummy())),
                 layout: LayoutPrimitiveInfo::new(LayoutRect::zero()),
+                space_and_clip: SpaceAndClipInfo::root_scroll(PipelineId::dummy())
             },
             cur_stops: ItemRange::default(),
             cur_glyphs: ItemRange::default(),
@@ -370,8 +369,8 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> {
         &self.iter.cur_item.layout.clip_rect
     }
 
-    pub fn clip_and_scroll(&self) -> ClipAndScrollInfo {
-        self.iter.cur_item.clip_and_scroll
+    pub fn space_and_clip_info(&self) -> &SpaceAndClipInfo {
+        &self.iter.cur_item.space_and_clip
     }
 
     pub fn item(&self) -> &SpecificDisplayItem {
@@ -490,13 +489,13 @@ impl Serialize for BuiltDisplayList {
                     SpecificDisplayItem::Gradient(v) => Gradient(v),
                     SpecificDisplayItem::RadialGradient(v) => RadialGradient(v),
                     SpecificDisplayItem::Iframe(v) => Iframe(v),
+                    SpecificDisplayItem::PushReferenceFrame(v) => PushReferenceFrame(v),
+                    SpecificDisplayItem::PopReferenceFrame => PopReferenceFrame,
                     SpecificDisplayItem::PushStackingContext(v) => PushStackingContext(
                         v,
                         item.iter.list.get(item.iter.cur_filters).collect()
                     ),
                     SpecificDisplayItem::PopStackingContext => PopStackingContext,
-                    SpecificDisplayItem::PushReferenceFrame(v) => PushReferenceFrame(v),
-                    SpecificDisplayItem::PopReferenceFrame => PopReferenceFrame,
                     SpecificDisplayItem::SetGradientStops => SetGradientStops(
                         item.iter.list.get(item.iter.cur_stops).collect()
                     ),
@@ -505,8 +504,8 @@ impl Serialize for BuiltDisplayList {
                     SpecificDisplayItem::PushCacheMarker(m) => PushCacheMarker(m),
                     SpecificDisplayItem::PopCacheMarker => PopCacheMarker,
                 },
-                clip_and_scroll: display_item.clip_and_scroll,
                 layout: display_item.layout,
+                space_and_clip: display_item.space_and_clip,
             };
             seq.serialize_element(&serial_di)?
         }
@@ -551,7 +550,7 @@ impl<'de> Deserialize<'de> for BuiltDisplayList {
                         total_clip_nodes += 1;
                         DisplayListBuilder::push_iter_impl(&mut temp, complex_clips);
                         SpecificDisplayItem::ScrollFrame(specific_item)
-                    },
+                    }
                     StickyFrame(specific_item) => {
                         total_spatial_nodes += 1;
                         SpecificDisplayItem::StickyFrame(specific_item)
@@ -574,16 +573,16 @@ impl<'de> Deserialize<'de> for BuiltDisplayList {
                         total_clip_nodes += 1;
                         SpecificDisplayItem::Iframe(specific_item)
                     }
+                    PushReferenceFrame(v) => {
+                        total_spatial_nodes += 1;
+                        SpecificDisplayItem::PushReferenceFrame(v)
+                    }
+                    PopReferenceFrame => SpecificDisplayItem::PopReferenceFrame,
                     PushStackingContext(specific_item, filters) => {
                         DisplayListBuilder::push_iter_impl(&mut temp, filters);
                         SpecificDisplayItem::PushStackingContext(specific_item)
                     },
                     PopStackingContext => SpecificDisplayItem::PopStackingContext,
-                    PushReferenceFrame(specific_item) => {
-                        total_spatial_nodes += 1;
-                        SpecificDisplayItem::PushReferenceFrame(specific_item)
-                    }
-                    PopReferenceFrame => SpecificDisplayItem::PopReferenceFrame,
                     SetGradientStops(stops) => {
                         DisplayListBuilder::push_iter_impl(&mut temp, stops);
                         SpecificDisplayItem::SetGradientStops
@@ -593,8 +592,8 @@ impl<'de> Deserialize<'de> for BuiltDisplayList {
                     PushCacheMarker(marker) => SpecificDisplayItem::PushCacheMarker(marker),
                     PopCacheMarker => SpecificDisplayItem::PopCacheMarker,
                 },
-                clip_and_scroll: complete.clip_and_scroll,
                 layout: complete.layout,
+                space_and_clip: complete.space_and_clip,
             };
             serialize_fast(&mut data, &item);
             // the aux data is serialized after the item, hence the temporary
@@ -832,7 +831,6 @@ impl<'a, 'b> Read for UnsafeReader<'a, 'b> {
 #[derive(Clone, Debug)]
 pub struct SaveState {
     dl_len: usize,
-    clip_stack_len: usize,
     next_clip_index: usize,
     next_spatial_index: usize,
     next_clip_chain_id: u64,
@@ -842,7 +840,6 @@ pub struct SaveState {
 pub struct DisplayListBuilder {
     pub data: Vec<u8>,
     pub pipeline_id: PipelineId,
-    clip_stack: Vec<ClipAndScrollInfo>,
     next_clip_index: usize,
     next_spatial_index: usize,
     next_clip_chain_id: u64,
@@ -869,9 +866,6 @@ impl DisplayListBuilder {
         DisplayListBuilder {
             data: Vec::with_capacity(capacity),
             pipeline_id,
-            clip_stack: vec![
-                ClipAndScrollInfo::simple(ClipId::root_scroll_node(pipeline_id)),
-            ],
             next_clip_index: FIRST_CLIP_NODE_INDEX,
             next_spatial_index: FIRST_SPATIAL_NODE_INDEX,
             next_clip_chain_id: 0,
@@ -897,7 +891,6 @@ impl DisplayListBuilder {
         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_index: self.next_clip_index,
             next_spatial_index: self.next_spatial_index,
@@ -909,7 +902,6 @@ impl DisplayListBuilder {
     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_index = state.next_clip_index;
         self.next_spatial_index = state.next_spatial_index;
@@ -969,36 +961,31 @@ impl DisplayListBuilder {
     /// NOTE: It is usually preferable to use the specialized methods to push
     /// display items. Pushing unexpected or invalid items here may
     /// result in WebRender panicking or behaving in unexpected ways.
-    pub fn push_item(&mut self, item: &SpecificDisplayItem, layout: &LayoutPrimitiveInfo) {
-        serialize_fast(
-            &mut self.data,
-            SerializedDisplayItem {
-                item,
-                clip_and_scroll: self.clip_stack.last().unwrap(),
-                layout,
-            },
-        )
-    }
-
-    fn push_item_with_clip_scroll_info(
+    #[inline]
+    pub fn push_item(
         &mut self,
         item: &SpecificDisplayItem,
         layout: &LayoutPrimitiveInfo,
-        clip_and_scroll: &ClipAndScrollInfo
+        space_and_clip: &SpaceAndClipInfo,
     ) {
         serialize_fast(
             &mut self.data,
             SerializedDisplayItem {
                 item,
-                clip_and_scroll,
                 layout,
+                space_and_clip,
             },
         )
     }
 
+    #[inline]
     fn push_new_empty_item(&mut self, item: &SpecificDisplayItem) {
-        let layout = &LayoutPrimitiveInfo::new(LayoutRect::zero());
-        self.push_item(item, layout)
+        let pipeline_id = self.pipeline_id;
+        self.push_item(
+            item,
+            &LayoutPrimitiveInfo::new(LayoutRect::zero()),
+            &SpaceAndClipInfo::root_scroll(pipeline_id),
+        )
     }
 
     fn push_iter_impl<I>(data: &mut Vec<u8>, iter_source: I)
@@ -1046,18 +1033,28 @@ impl DisplayListBuilder {
         Self::push_iter_impl(&mut self.data, iter);
     }
 
-    pub fn push_rect(&mut self, layout: &LayoutPrimitiveInfo, color: ColorF) {
+    pub fn push_rect(
+        &mut self,
+        layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
+        color: ColorF,
+    ) {
         let item = SpecificDisplayItem::Rectangle(RectangleDisplayItem { color });
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
-    pub fn push_clear_rect(&mut self, layout: &LayoutPrimitiveInfo) {
-        self.push_item(&SpecificDisplayItem::ClearRectangle, layout);
+    pub fn push_clear_rect(
+        &mut self,
+        layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
+    ) {
+        self.push_item(&SpecificDisplayItem::ClearRectangle, layout, space_and_clip);
     }
 
     pub fn push_line(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         wavy_line_thickness: f32,
         orientation: LineOrientation,
         color: &ColorF,
@@ -1070,12 +1067,13 @@ impl DisplayListBuilder {
             style,
         });
 
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
     pub fn push_image(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         image_rendering: ImageRendering,
@@ -1092,13 +1090,14 @@ impl DisplayListBuilder {
             color,
         });
 
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
     /// Push a yuv image. All planar data in yuv image should use the same buffer type.
     pub fn push_yuv_image(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         yuv_data: YuvData,
         color_depth: ColorDepth,
         color_space: YuvColorSpace,
@@ -1110,12 +1109,13 @@ impl DisplayListBuilder {
             color_space,
             image_rendering,
         });
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
     pub fn push_text(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         glyphs: &[GlyphInstance],
         font_key: FontInstanceKey,
         color: ColorF,
@@ -1128,7 +1128,7 @@ impl DisplayListBuilder {
         });
 
         for split_glyphs in glyphs.chunks(MAX_TEXT_RUN_LENGTH) {
-            self.push_item(&item, layout);
+            self.push_item(&item, layout, space_and_clip);
             self.push_iter(split_glyphs);
         }
     }
@@ -1166,17 +1166,19 @@ impl DisplayListBuilder {
     pub fn push_border(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         widths: LayoutSideOffsets,
         details: BorderDetails,
     ) {
         let item = SpecificDisplayItem::Border(BorderDisplayItem { details, widths });
 
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
     pub fn push_box_shadow(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         box_bounds: LayoutRect,
         offset: LayoutVector2D,
         color: ColorF,
@@ -1195,7 +1197,7 @@ impl DisplayListBuilder {
             clip_mode,
         });
 
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
     /// Pushes a linear gradient to be displayed.
@@ -1215,6 +1217,7 @@ impl DisplayListBuilder {
     pub fn push_gradient(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         gradient: Gradient,
         tile_size: LayoutSize,
         tile_spacing: LayoutSize,
@@ -1225,7 +1228,7 @@ impl DisplayListBuilder {
             tile_spacing,
         });
 
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
     /// Pushes a radial gradient to be displayed.
@@ -1234,6 +1237,7 @@ impl DisplayListBuilder {
     pub fn push_radial_gradient(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         gradient: RadialGradient,
         tile_size: LayoutSize,
         tile_spacing: LayoutSize,
@@ -1244,18 +1248,20 @@ impl DisplayListBuilder {
             tile_spacing,
         });
 
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
     pub fn push_reference_frame(
         &mut self,
         rect: &LayoutRect,
+        parent: SpatialId,
         transform_style: TransformStyle,
         transform: Option<PropertyBinding<LayoutTransform>>,
         perspective: Option<LayoutTransform>,
-    ) -> ClipId {
+    ) -> SpatialId {
         let id = self.generate_spatial_index();
-        let item = SpecificDisplayItem::PushReferenceFrame(PushReferenceFrameDisplayListItem {
+
+        let item = SpecificDisplayItem::PushReferenceFrame(ReferenceFrameDisplayListItem {
             reference_frame: ReferenceFrame {
                 transform_style,
                 transform,
@@ -1263,7 +1269,12 @@ impl DisplayListBuilder {
                 id,
             },
         });
-        self.push_item(&item, &LayoutPrimitiveInfo::new(*rect));
+
+        let layout = LayoutPrimitiveInfo::new(*rect);
+        self.push_item(&item, &layout, &SpaceAndClipInfo {
+            spatial_id: parent,
+            clip_id: ClipId::invalid(),
+        });
         id
     }
 
@@ -1287,7 +1298,8 @@ impl DisplayListBuilder {
     pub fn push_stacking_context(
         &mut self,
         layout: &LayoutPrimitiveInfo,
-        clip_node_id: Option<ClipId>,
+        spatial_id: SpatialId,
+        clip_id: Option<ClipId>,
         transform_style: TransformStyle,
         mix_blend_mode: MixBlendMode,
         filters: &[FilterOp],
@@ -1297,12 +1309,15 @@ impl DisplayListBuilder {
             stacking_context: StackingContext {
                 transform_style,
                 mix_blend_mode,
-                clip_node_id,
+                clip_id,
                 raster_space,
             },
         });
 
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, &SpaceAndClipInfo {
+            spatial_id,
+            clip_id: ClipId::invalid(),
+        });
         self.push_iter(filters);
     }
 
@@ -1323,9 +1338,9 @@ impl DisplayListBuilder {
         ClipId::Clip(self.next_clip_index - 1, self.pipeline_id)
     }
 
-    fn generate_spatial_index(&mut self) -> ClipId {
+    fn generate_spatial_index(&mut self) -> SpatialId {
         self.next_spatial_index += 1;
-        ClipId::Spatial(self.next_spatial_index - 1, self.pipeline_id)
+        SpatialId::new(self.next_spatial_index - 1, self.pipeline_id)
     }
 
     fn generate_clip_chain_id(&mut self) -> ClipChainId {
@@ -1335,38 +1350,14 @@ impl DisplayListBuilder {
 
     pub fn define_scroll_frame<I>(
         &mut self,
+        parent_space_and_clip: &SpaceAndClipInfo,
         external_id: Option<ExternalScrollId>,
         content_rect: LayoutRect,
         clip_rect: LayoutRect,
         complex_clips: I,
         image_mask: Option<ImageMask>,
         scroll_sensitivity: ScrollSensitivity,
-    ) -> ClipId
-    where
-        I: IntoIterator<Item = ComplexClipRegion>,
-        I::IntoIter: ExactSizeIterator + Clone,
-    {
-        let parent = self.clip_stack.last().unwrap().scroll_node_id;
-        self.define_scroll_frame_with_parent(
-            parent,
-            external_id,
-            content_rect,
-            clip_rect,
-            complex_clips,
-            image_mask,
-            scroll_sensitivity)
-    }
-
-    pub fn define_scroll_frame_with_parent<I>(
-        &mut self,
-        parent: ClipId,
-        external_id: Option<ExternalScrollId>,
-        content_rect: LayoutRect,
-        clip_rect: LayoutRect,
-        complex_clips: I,
-        image_mask: Option<ImageMask>,
-        scroll_sensitivity: ScrollSensitivity,
-    ) -> ClipId
+    ) -> SpaceAndClipInfo
     where
         I: IntoIterator<Item = ComplexClipRegion>,
         I::IntoIter: ExactSizeIterator + Clone,
@@ -1381,14 +1372,17 @@ impl DisplayListBuilder {
             scroll_sensitivity,
         });
 
-        self.push_item_with_clip_scroll_info(
+        self.push_item(
             &item,
             &LayoutPrimitiveInfo::with_clip_rect(content_rect, clip_rect),
-            &ClipAndScrollInfo::simple(parent),
+            parent_space_and_clip,
         );
         self.push_iter(complex_clips);
 
-        scroll_frame_id
+        SpaceAndClipInfo {
+            spatial_id: scroll_frame_id,
+            clip_id,
+        }
     }
 
     pub fn define_clip_chain<I>(
@@ -1408,45 +1402,7 @@ impl DisplayListBuilder {
 
     pub fn define_clip<I>(
         &mut self,
-        clip_rect: LayoutRect,
-        complex_clips: I,
-        image_mask: Option<ImageMask>,
-    ) -> ClipId
-    where
-        I: IntoIterator<Item = ComplexClipRegion>,
-        I::IntoIter: ExactSizeIterator + Clone,
-    {
-        let clip_and_scroll = self.clip_stack.last().unwrap().clone();
-        self.define_clip_impl(
-            clip_and_scroll,
-            clip_rect,
-            complex_clips,
-            image_mask,
-        )
-    }
-
-    pub fn define_clip_with_parent<I>(
-        &mut self,
-        parent: ClipId,
-        clip_rect: LayoutRect,
-        complex_clips: I,
-        image_mask: Option<ImageMask>,
-    ) -> ClipId
-    where
-        I: IntoIterator<Item = ComplexClipRegion>,
-        I::IntoIter: ExactSizeIterator + Clone,
-    {
-        self.define_clip_impl(
-            ClipAndScrollInfo::simple(parent),
-            clip_rect,
-            complex_clips,
-            image_mask,
-        )
-    }
-
-    fn define_clip_impl<I>(
-        &mut self,
-        scrollinfo: ClipAndScrollInfo,
+        parent_space_and_clip: &SpaceAndClipInfo,
         clip_rect: LayoutRect,
         complex_clips: I,
         image_mask: Option<ImageMask>,
@@ -1461,21 +1417,24 @@ impl DisplayListBuilder {
             image_mask,
         });
 
-        let layout = LayoutPrimitiveInfo::new(clip_rect);
-
-        self.push_item_with_clip_scroll_info(&item, &layout, &scrollinfo);
+        self.push_item(
+            &item,
+            &LayoutPrimitiveInfo::new(clip_rect),
+            parent_space_and_clip,
+        );
         self.push_iter(complex_clips);
         id
     }
 
     pub fn define_sticky_frame(
         &mut self,
+        parent_spatial_id: SpatialId,
         frame_rect: LayoutRect,
         margins: SideOffsets2D<Option<f32>>,
         vertical_offset_bounds: StickyOffsetBounds,
         horizontal_offset_bounds: StickyOffsetBounds,
         previously_applied_offset: LayoutVector2D,
-    ) -> ClipId {
+    ) -> SpatialId {
         let id = self.generate_spatial_index();
         let item = SpecificDisplayItem::StickyFrame(StickyFrameDisplayItem {
             id,
@@ -1485,44 +1444,38 @@ impl DisplayListBuilder {
             previously_applied_offset,
         });
 
-        let layout = LayoutPrimitiveInfo::new(frame_rect);
-        self.push_item(&item, &layout);
+        self.push_item(
+            &item,
+            &LayoutPrimitiveInfo::new(frame_rect),
+            &SpaceAndClipInfo {
+                spatial_id: parent_spatial_id,
+                clip_id: ClipId::invalid(),
+            },
+        );
         id
     }
 
-    pub fn push_clip_id(&mut self, id: ClipId) {
-        self.clip_stack.push(ClipAndScrollInfo::simple(id));
-    }
-
-    pub fn push_clip_and_scroll_info(&mut self, layout: ClipAndScrollInfo) {
-        self.clip_stack.push(layout);
-    }
-
-    pub fn pop_clip_id(&mut self) {
-        self.clip_stack.pop();
-        if let Some(save_state) = self.save_state.as_ref() {
-            assert!(self.clip_stack.len() >= save_state.clip_stack_len,
-                    "Cannot pop clips that were pushed before the DisplayListBuilder save.");
-        }
-        assert!(!self.clip_stack.is_empty());
-    }
-
     pub fn push_iframe(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         pipeline_id: PipelineId,
         ignore_missing_pipeline: bool
     ) {
         let item = SpecificDisplayItem::Iframe(IframeDisplayItem {
-            clip_id: self.generate_clip_index(),
             pipeline_id,
             ignore_missing_pipeline,
         });
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
-    pub fn push_shadow(&mut self, layout: &LayoutPrimitiveInfo, shadow: Shadow) {
-        self.push_item(&SpecificDisplayItem::PushShadow(shadow), layout);
+    pub fn push_shadow(
+        &mut self,
+        layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
+        shadow: Shadow,
+    ) {
+        self.push_item(&SpecificDisplayItem::PushShadow(shadow), layout, space_and_clip);
     }
 
     pub fn pop_all_shadows(&mut self) {
@@ -1533,6 +1486,7 @@ impl DisplayListBuilder {
         assert!(self.save_state.is_none(), "Finalized DisplayListBuilder with a pending save");
 
         let end_time = precise_time_ns();
+
         (
             self.pipeline_id,
             self.content_size,
diff --git a/wrench/reftests/scrolling/fixed-position-scrolling-clip.yaml b/wrench/reftests/scrolling/fixed-position-scrolling-clip.yaml
index fe012db818..63573e5742 100644
--- a/wrench/reftests/scrolling/fixed-position-scrolling-clip.yaml
+++ b/wrench/reftests/scrolling/fixed-position-scrolling-clip.yaml
@@ -7,22 +7,22 @@ root:
       scroll-offset: [0, 50]
       items:
       # The rectangles below should stay in place even when the parent scroll area scrolls,
-      # because they are use the root reference frame as their scroll node (fixed position).
+      # because they use the root reference frame as their scroll node (fixed position).
       # On the other hand, the clip item here will scroll with its parent scroll area. Normally
       # fixed position items  would only be clipped by their reference frame (in this case the
-      # root), but ince  these items specify an auxiliary clip, they will be clipped by their
+      # root), but since these items specify an auxiliary clip, they will be clipped by their
       # sibling clip (42).
-      - type: clip
-        bounds: [10, 60, 50, 50]
-        id: 42
-      - type: stacking-context
-        bounds: [10, 10, 100, 100]
-        items:
-          - type: rect
-            bounds: [0, 0, 100, 50]
-            color: green
-            clip-and-scroll: [root-reference-frame, 42]
-          - type: rect
-            bounds: [0, 50, 100, 50]
-            color: red
-            clip-and-scroll: [root-reference-frame, 42]
+        - type: clip
+          bounds: [10, 60, 50, 50]
+          id: 42
+        - type: stacking-context
+          bounds: [10, 10, 100, 100]
+          items:
+            - type: rect
+              bounds: [0, 0, 100, 50]
+              color: green
+              clip-and-scroll: [root-reference-frame, 42]
+            - type: rect
+              bounds: [0, 50, 100, 50]
+              color: red
+              clip-and-scroll: [root-reference-frame, 42]
diff --git a/wrench/reftests/scrolling/fixed-position.yaml b/wrench/reftests/scrolling/fixed-position.yaml
index 540267c5b3..6fa099b5e3 100644
--- a/wrench/reftests/scrolling/fixed-position.yaml
+++ b/wrench/reftests/scrolling/fixed-position.yaml
@@ -2,18 +2,14 @@ root:
   bounds: [0, 0, 1024, 10000]
   scroll-offset: [0, 100]
   items:
-    # This stacking context should not scroll out of view because it is fixed position.
     - type: stacking-context
       bounds: [0, 0, 50, 50]
       items:
+        # This item should not scroll out of view because it is fixed position.
         - type: rect
           bounds: [0, 0, 50, 50]
           color: green
           clip-and-scroll: root-reference-frame
-    # Even though there is a fixed position stacking context, it should scroll,
-    # because it is fixed relative to its reference frame. The reference frame
-    # of this stacking context is the stacking context parent because it has
-    # a transformation.
     - type: stacking-context
       bounds: [0, 0, 50, 50]
       transform: translate(60, 100)
@@ -22,17 +18,21 @@ root:
         - type: stacking-context
           bounds: [0, 0, 50, 50]
           items:
+            # Even though there is a custom clip-scroll ID, it should scroll,
+            # because it is fixed relative to its reference frame. The reference frame
+            # of this stacking context is the stacking context parent because it has
+            # a transformation.
             - type: rect
               bounds: [0, 0, 50, 50]
               color: green
               clip-and-scroll: 100
-    # This is similar to the previous case, but ensures that this still works
-    # even with an identity transform.
     - type: stacking-context
       bounds: [120, 0, 50, 200]
       transform: translate(0, 0)
       id: 101
       items:
+        # This is similar to the previous case, but ensures that this still works
+        # even with an identity transform.
         - type: stacking-context
           bounds: [0, 0, 50, 200]
           items:
@@ -40,12 +40,12 @@ root:
               bounds: [0, 100, 50, 50]
               color: green
               clip-and-scroll: 101
-    # This is similar to the previous case, but for perspective.
     - type: stacking-context
       bounds: [180, 0, 50, 200]
       perspective: 1
       id: 102
       items:
+        # This is similar to the previous case, but for perspective.
         - type: stacking-context
           bounds: [0, 0, 50, 200]
           items:
diff --git a/wrench/reftests/scrolling/scroll-layer-with-mask.yaml b/wrench/reftests/scrolling/scroll-layer-with-mask.yaml
index dc4ee4410f..77cee7abe6 100644
--- a/wrench/reftests/scrolling/scroll-layer-with-mask.yaml
+++ b/wrench/reftests/scrolling/scroll-layer-with-mask.yaml
@@ -6,7 +6,6 @@ root:
       # ensure that offsets within reference frames are handled
       # properly when drawing the mask.
       bounds: [10, 10, 100, 100]
-      scroll-policy: scrollable
       items:
       - type: scroll-frame
         bounds: [0, 0, 100, 100]
@@ -15,15 +14,14 @@ root:
           rect: [0, 0, 100, 100]
           repeat: false
         items:
-          - type: rect
-            bounds: [0, 0, 100, 100]
-            color: green
+        - type: rect
+          bounds: [0, 0, 100, 100]
+          color: green
 
     # The same test, but this time ensure that scroll offsets don't affect the masking.
     -
       type: stacking-context
       bounds: [100, 0, 100, 100]
-      scroll-policy: scrollable
       items:
       - type: scroll-frame
         bounds: [10, 10, 100, 300]
diff --git a/wrench/reftests/scrolling/translate-nested.yaml b/wrench/reftests/scrolling/translate-nested.yaml
index 8337379deb..74bd647aa7 100644
--- a/wrench/reftests/scrolling/translate-nested.yaml
+++ b/wrench/reftests/scrolling/translate-nested.yaml
@@ -1,28 +1,28 @@
 ---
-root: 
-  items: 
-    - 
+root:
+  items:
+    -
       bounds: [8, 8, 500, 500]
       type: "stacking-context"
-      items: 
-        - 
+      items:
+        -
           bounds: [0, 0, 200, 200]
           type: clip
           id: 2
           items:
-          -
-            bounds: [0, 0, 200, 200]
-            type: rect
-            color: red
-          -
-            bounds: [0, 0, 200, 200]
-            type: "stacking-context"
-            transform: translate(100, 0)
-            items:
-              -
-                bounds: [-100, 0, 200, 200]
-                clip-rect: [-300, -300, 900, 900]
-                type: rect
-                color: green
+            -
+              bounds: [0, 0, 200, 200]
+              type: rect
+              color: red
+            -
+              bounds: [0, 0, 200, 200]
+              type: "stacking-context"
+              transform: translate(100, 0)
+              items:
+                -
+                  bounds: [-100, 0, 200, 200]
+                  clip-rect: [-300, -300, 900, 900]
+                  type: rect
+                  color: green
   id: [0, 0]
 pipelines: []
diff --git a/wrench/script/headless.py b/wrench/script/headless.py
index 3052ea2be2..ced6c1a133 100755
--- a/wrench/script/headless.py
+++ b/wrench/script/headless.py
@@ -49,27 +49,6 @@ def is_linux():
     return sys.platform.startswith('linux')
 
 
-def debugger():
-    if "DEBUGGER" in os.environ:
-        return os.environ["DEBUGGER"]
-    return None
-
-
-def use_gdb():
-    return debugger() in ['gdb', 'cgdb', 'rust-gdb']
-
-
-def use_rr():
-    return debugger() == 'rr'
-
-
-def optimized_build():
-    if "OPTIMIZED" in os.environ:
-        opt = os.environ["OPTIMIZED"]
-        return opt not in ["0", "false"]
-    return True
-
-
 def set_osmesa_env(bin_path):
     """Set proper LD_LIBRARY_PATH and DRIVE for software rendering on Linux and OSX"""
     if is_linux():
@@ -88,34 +67,11 @@ def set_osmesa_env(bin_path):
 
 extra_flags = os.getenv('CARGOFLAGS', None)
 extra_flags = extra_flags.split(' ') if extra_flags else []
-build_cmd = ['cargo', 'build'] + extra_flags + ['--verbose', '--features', 'headless']
-if optimized_build():
-    build_cmd += ['--release']
-
-objdir = ''
-if optimized_build():
-    objdir = '../target/release/'
-else:
-    objdir = '../target/debug/'
-
-subprocess.check_call(build_cmd)
-
-set_osmesa_env(objdir)
-
-dbg_cmd = []
-if use_rr():
-    dbg_cmd = ['rr', 'record']
-elif use_gdb():
-    dbg_cmd = [debugger(), '--args']
-elif debugger():
-    print("Unknown debugger: " + debugger())
-    sys.exit(1)
-
+subprocess.check_call(['cargo', 'build'] + extra_flags + ['--release', '--verbose', '--features', 'headless'])
+set_osmesa_env('../target/release/')
 # TODO(gw): We have an occasional accuracy issue or bug (could be WR or OSMesa)
 #           where the output of a previous test that uses intermediate targets can
 #           cause 1.0 / 255.0 pixel differences in a subsequent test. For now, we
 #           run tests with no-scissor mode, which ensures a complete target clear
 #           between test runs. But we should investigate this further...
-cmd = dbg_cmd + [objdir + '/wrench', '--no-scissor', '-h'] + sys.argv[1:]
-print('Running: `' + ' '.join(cmd) + '`')
-subprocess.check_call(cmd)
+subprocess.check_call(['../target/release/wrench', '--no-scissor', '-h'] + sys.argv[1:])
diff --git a/wrench/src/rawtest.rs b/wrench/src/rawtest.rs
index 343f2e13fe..5063e49c39 100644
--- a/wrench/src/rawtest.rs
+++ b/wrench/src/rawtest.rs
@@ -109,6 +109,7 @@ impl<'a> RawtestHarness<'a> {
         // setup some malicious image size parameters
         builder.push_image(
             &info,
+            &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
             size(151., 56.0),
             size(151.0, 56.0),
             ImageRendering::Auto,
@@ -170,12 +171,18 @@ impl<'a> RawtestHarness<'a> {
 
         let image_size = size(1510., 111256.);
 
-        let clip_id = builder.define_clip(rect(40., 41., 200., 201.), vec![], None);
+        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
+        let clip_id = builder.define_clip(
+            &root_space_and_clip,
+            rect(40., 41., 200., 201.),
+            vec![],
+            None,
+        );
 
-        builder.push_clip_id(clip_id);
         // setup some malicious image size parameters
         builder.push_image(
             &info,
+            &SpaceAndClipInfo { clip_id, spatial_id: root_space_and_clip.spatial_id },
             image_size * 2.,
             image_size,
             ImageRendering::Auto,
@@ -191,8 +198,6 @@ impl<'a> RawtestHarness<'a> {
             }
         );
 
-        builder.pop_clip_id();
-
         let mut epoch = Epoch(0);
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
@@ -242,7 +247,7 @@ impl<'a> RawtestHarness<'a> {
     }
 
     fn test_insufficient_blob_visible_area(&mut self) {
-        println!("\tinsufficient blob visible area.");
+        println!("\tinsufficient blob visible area...");
 
         // This test compares two almost identical display lists containing the a blob
         // image. The only difference is that one of the display lists specifies a visible
@@ -261,6 +266,7 @@ impl<'a> RawtestHarness<'a> {
         let layout_size = LayoutSize::new(800.0, 800.0);
         let image_size = size(800.0, 800.0);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 0.0, 800.0, 800.0));
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let mut txn = Transaction::new();
@@ -281,6 +287,7 @@ impl<'a> RawtestHarness<'a> {
 
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             image_size,
             ImageRendering::Auto,
@@ -317,6 +324,7 @@ impl<'a> RawtestHarness<'a> {
 
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             image_size,
             ImageRendering::Auto,
@@ -349,6 +357,7 @@ impl<'a> RawtestHarness<'a> {
             DeviceIntPoint::new(0, window_size.height - test_size.height),
             test_size,
         );
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
 
         // This exposes a crash in tile decomposition
         let mut txn = Transaction::new();
@@ -371,6 +380,7 @@ impl<'a> RawtestHarness<'a> {
         // setup some malicious image size parameters
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             image_size,
             ImageRendering::Auto,
@@ -396,6 +406,7 @@ impl<'a> RawtestHarness<'a> {
         // setup some malicious image size parameters
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             image_size,
             ImageRendering::Auto,
@@ -426,6 +437,7 @@ impl<'a> RawtestHarness<'a> {
         // setup some malicious image size parameters
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             image_size,
             ImageRendering::Auto,
@@ -461,6 +473,8 @@ impl<'a> RawtestHarness<'a> {
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
+
         let mut txn = Transaction::new();
         {
             let api = &self.wrench.api;
@@ -487,6 +501,7 @@ impl<'a> RawtestHarness<'a> {
 
         builder.push_image(
             &info,
+            &space_and_clip,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
@@ -510,6 +525,7 @@ impl<'a> RawtestHarness<'a> {
         let info = LayoutPrimitiveInfo::new(rect(1.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
+            &space_and_clip,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
@@ -534,10 +550,6 @@ impl<'a> RawtestHarness<'a> {
         assert!(pixels_first != pixels_second);
 
         // cleanup
-        txn = Transaction::new();
-        txn.delete_blob_image(blob_img);
-        self.wrench.api.update_resources(txn.resource_updates);
-
         *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
     }
 
@@ -553,6 +565,8 @@ impl<'a> RawtestHarness<'a> {
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
+
         let mut txn = Transaction::new();
         let (blob_img, blob_img2) = {
             let api = &self.wrench.api;
@@ -599,6 +613,7 @@ impl<'a> RawtestHarness<'a> {
         let push_images = |builder: &mut DisplayListBuilder| {
             builder.push_image(
                 &info,
+                &space_and_clip,
                 size(200.0, 200.0),
                 size(0.0, 0.0),
                 ImageRendering::Auto,
@@ -608,6 +623,7 @@ impl<'a> RawtestHarness<'a> {
             );
             builder.push_image(
                 &info2,
+                &space_and_clip,
                 size(200.0, 200.0),
                 size(0.0, 0.0),
                 ImageRendering::Auto,
@@ -666,11 +682,6 @@ impl<'a> RawtestHarness<'a> {
         assert_eq!(img2_requested.load(Ordering::SeqCst), 2);
 
         // cleanup
-        txn = Transaction::new();
-        txn.delete_blob_image(blob_img);
-        txn.delete_blob_image(blob_img2);
-        self.wrench.api.update_resources(txn.resource_updates);
-
         *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
     }
 
@@ -685,6 +696,7 @@ impl<'a> RawtestHarness<'a> {
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
         let mut txn = Transaction::new();
 
         let blob_img = {
@@ -704,6 +716,7 @@ impl<'a> RawtestHarness<'a> {
 
         builder.push_image(
             &info,
+            &space_and_clip,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
@@ -731,6 +744,7 @@ impl<'a> RawtestHarness<'a> {
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
+            &space_and_clip,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
@@ -756,6 +770,7 @@ impl<'a> RawtestHarness<'a> {
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
+            &space_and_clip,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
@@ -769,11 +784,6 @@ impl<'a> RawtestHarness<'a> {
 
         assert!(pixels_first == pixels_second);
         assert!(pixels_first != pixels_third);
-
-        // cleanup
-        txn = Transaction::new();
-        txn.delete_blob_image(blob_img);
-        self.wrench.api.update_resources(txn.resource_updates);
     }
 
     // Ensures that content doing a save-restore produces the same results as not
@@ -792,54 +802,71 @@ impl<'a> RawtestHarness<'a> {
         let mut do_test = |should_try_and_fail| {
             let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
-            let clip = builder.define_clip(
+            let spatial_id = SpatialId::root_scroll_node(self.wrench.root_pipeline_id);
+            let clip_id = builder.define_clip(
+                &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
                 rect(110., 120., 200., 200.),
                 None::<ComplexClipRegion>,
                 None
             );
-            builder.push_clip_id(clip);
-            builder.push_rect(&PrimitiveInfo::new(rect(100., 100., 100., 100.)),
-                              ColorF::new(0.0, 0.0, 1.0, 1.0));
+            builder.push_rect(
+                &PrimitiveInfo::new(rect(100., 100., 100., 100.)),
+                &SpaceAndClipInfo { spatial_id, clip_id },
+                ColorF::new(0.0, 0.0, 1.0, 1.0),
+            );
 
             if should_try_and_fail {
                 builder.save();
-                let clip = builder.define_clip(
+                let clip_id = builder.define_clip(
+                    &SpaceAndClipInfo { spatial_id, clip_id },
                     rect(80., 80., 90., 90.),
                     None::<ComplexClipRegion>,
                     None
                 );
-                builder.push_clip_id(clip);
-                builder.push_rect(&PrimitiveInfo::new(rect(110., 110., 50., 50.)),
-                                  ColorF::new(0.0, 1.0, 0.0, 1.0));
-                builder.push_shadow(&PrimitiveInfo::new(rect(100., 100., 100., 100.)),
-                                    Shadow {
-                                        offset: LayoutVector2D::new(1.0, 1.0),
-                                        blur_radius: 1.0,
-                                        color: ColorF::new(0.0, 0.0, 0.0, 1.0),
-                                    });
-                builder.push_line(&PrimitiveInfo::new(rect(110., 110., 50., 2.)),
-                                  0.0, LineOrientation::Horizontal,
-                                  &ColorF::new(0.0, 0.0, 0.0, 1.0), LineStyle::Solid);
+                let space_and_clip = SpaceAndClipInfo {
+                    spatial_id,
+                    clip_id
+                };
+                builder.push_rect(
+                    &PrimitiveInfo::new(rect(110., 110., 50., 50.)),
+                    &space_and_clip,
+                    ColorF::new(0.0, 1.0, 0.0, 1.0),
+                );
+                builder.push_shadow(
+                    &PrimitiveInfo::new(rect(100., 100., 100., 100.)),
+                    &space_and_clip,
+                    Shadow {
+                        offset: LayoutVector2D::new(1.0, 1.0),
+                        blur_radius: 1.0,
+                        color: ColorF::new(0.0, 0.0, 0.0, 1.0),
+                    },
+                );
+                builder.push_line(
+                    &PrimitiveInfo::new(rect(110., 110., 50., 2.)),
+                    &space_and_clip,
+                    0.0, LineOrientation::Horizontal,
+                    &ColorF::new(0.0, 0.0, 0.0, 1.0),
+                    LineStyle::Solid,
+                );
                 builder.restore();
             }
 
             {
                 builder.save();
-                let clip = builder.define_clip(
+                let clip_id = builder.define_clip(
+                    &SpaceAndClipInfo { spatial_id, clip_id },
                     rect(80., 80., 100., 100.),
                     None::<ComplexClipRegion>,
                     None
                 );
-                builder.push_clip_id(clip);
-                builder.push_rect(&PrimitiveInfo::new(rect(150., 150., 100., 100.)),
-                                  ColorF::new(0.0, 0.0, 1.0, 1.0));
-
-                builder.pop_clip_id();
+                builder.push_rect(
+                    &PrimitiveInfo::new(rect(150., 150., 100., 100.)),
+                    &SpaceAndClipInfo { spatial_id, clip_id },
+                    ColorF::new(0.0, 0.0, 1.0, 1.0),
+                );
                 builder.clear_save();
             }
 
-            builder.pop_clip_id();
-
             let txn = Transaction::new();
 
             self.submit_dl(&mut Epoch(0), layout_size, builder, &txn.resource_updates);
@@ -866,6 +893,7 @@ impl<'a> RawtestHarness<'a> {
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
 
         let mut do_test = |shadow_is_red| {
             let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
@@ -875,15 +903,22 @@ impl<'a> RawtestHarness<'a> {
                 ColorF::new(0.0, 1.0, 0.0, 1.0)
             };
 
-            builder.push_shadow(&PrimitiveInfo::new(rect(100., 100., 100., 100.)),
+            builder.push_shadow(
+                &PrimitiveInfo::new(rect(100., 100., 100., 100.)),
+                &space_and_clip,
                 Shadow {
                     offset: LayoutVector2D::new(1.0, 1.0),
                     blur_radius: 1.0,
                     color: shadow_color,
-                });
-            builder.push_line(&PrimitiveInfo::new(rect(110., 110., 50., 2.)),
-                              0.0, LineOrientation::Horizontal,
-                              &ColorF::new(0.0, 0.0, 0.0, 1.0), LineStyle::Solid);
+                },
+            );
+            builder.push_line(
+                &PrimitiveInfo::new(rect(110., 110., 50., 2.)),
+                &space_and_clip,
+                0.0, LineOrientation::Horizontal,
+                &ColorF::new(0.0, 0.0, 0.0, 1.0),
+                LineStyle::Solid,
+            );
             builder.pop_all_shadows();
 
             let txn = Transaction::new();
@@ -923,6 +958,7 @@ impl<'a> RawtestHarness<'a> {
 
         builder.push_image(
             &LayoutPrimitiveInfo::new(rect(300.0, 70.0, 150.0, 50.0)),
+            &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
             size(150.0, 50.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
@@ -991,7 +1027,11 @@ impl<'a> RawtestHarness<'a> {
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(LayoutRect::new(LayoutPoint::zero(),
                                                             LayoutSize::new(100.0, 100.0)));
-        builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        builder.push_rect(
+            &info,
+            &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
+            ColorF::new(0.0, 1.0, 0.0, 1.0),
+        );
 
         let mut txn = Transaction::new();
         txn.set_root_pipeline(self.wrench.root_pipeline_id);
@@ -1017,11 +1057,12 @@ impl<'a> RawtestHarness<'a> {
         let layout_size = LayoutSize::new(400., 400.);
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
+
         // Add a rectangle that covers the entire scene.
         let mut info = LayoutPrimitiveInfo::new(LayoutRect::new(LayoutPoint::zero(), layout_size));
         info.tag = Some((0, 1));
-        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
-
+        builder.push_rect(&info, &space_and_clip, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
         // Add a simple 100x100 rectangle at 100,0.
         let mut info = LayoutPrimitiveInfo::new(LayoutRect::new(
@@ -1029,7 +1070,7 @@ impl<'a> RawtestHarness<'a> {
             LayoutSize::new(100., 100.)
         ));
         info.tag = Some((0, 2));
-        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
+        builder.push_rect(&info, &space_and_clip, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
         let make_rounded_complex_clip = |rect: &LayoutRect, radius: f32| -> ComplexClipRegion {
             ComplexClipRegion::new(
@@ -1039,30 +1080,46 @@ impl<'a> RawtestHarness<'a> {
             )
         };
 
-
         // Add a rectangle that is clipped by a rounded rect clip item.
         let rect = LayoutRect::new(LayoutPoint::new(100., 100.), LayoutSize::new(100., 100.));
-        let clip_id = builder.define_clip(rect, vec![make_rounded_complex_clip(&rect, 20.)], None);
-        builder.push_clip_id(clip_id);
-        let mut info = LayoutPrimitiveInfo::new(rect);
-        info.tag = Some((0, 4));
-        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
-        builder.pop_clip_id();
-
+        let temp_clip_id = builder.define_clip(
+            &space_and_clip,
+            rect,
+            vec![make_rounded_complex_clip(&rect, 20.)],
+            None,
+        );
+        builder.push_rect(
+            &LayoutPrimitiveInfo {
+                tag: Some((0, 4)),
+                .. LayoutPrimitiveInfo::new(rect)
+            },
+            &SpaceAndClipInfo {
+                clip_id: temp_clip_id,
+                spatial_id: space_and_clip.spatial_id,
+            },
+            ColorF::new(1.0, 1.0, 1.0, 1.0),
+        );
 
         // Add a rectangle that is clipped by a ClipChain containing a rounded rect.
         let rect = LayoutRect::new(LayoutPoint::new(200., 100.), LayoutSize::new(100., 100.));
-        let clip_id = builder.define_clip(rect, vec![make_rounded_complex_clip(&rect, 20.)], None);
+        let clip_id = builder.define_clip(
+            &space_and_clip,
+            rect,
+            vec![make_rounded_complex_clip(&rect, 20.)],
+            None,
+        );
         let clip_chain_id = builder.define_clip_chain(None, vec![clip_id]);
-        builder.push_clip_and_scroll_info(ClipAndScrollInfo::new(
-            ClipId::root_scroll_node(self.wrench.root_pipeline_id),
-            ClipId::ClipChain(clip_chain_id),
-        ));
-        let mut info = LayoutPrimitiveInfo::new(rect);
-        info.tag = Some((0, 5));
-        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
-        builder.pop_clip_id();
-
+        builder.push_rect(
+            &LayoutPrimitiveInfo {
+                tag: Some((0, 5)),
+                .. LayoutPrimitiveInfo::new(rect)
+            },
+            &SpaceAndClipInfo {
+                clip_id: ClipId::ClipChain(clip_chain_id),
+                spatial_id: space_and_clip.spatial_id,
+            },
+            ColorF::new(1.0, 1.0, 1.0, 1.0),
+        );
 
         let mut epoch = Epoch(0);
         let txn = Transaction::new();
diff --git a/wrench/src/yaml_frame_reader.rs b/wrench/src/yaml_frame_reader.rs
index 9ba89e9d5d..fa16b8968e 100644
--- a/wrench/src/yaml_frame_reader.rs
+++ b/wrench/src/yaml_frame_reader.rs
@@ -224,7 +224,11 @@ pub struct YamlFrameReader {
 
     /// A HashMap that allows specifying a numeric id for clip and clip chains in YAML
     /// and having each of those ids correspond to a unique ClipId.
-    clip_id_map: HashMap<u64, ClipId>,
+    user_clip_id_map: HashMap<u64, ClipId>,
+    user_spatial_id_map: HashMap<u64, SpatialId>,
+
+    clip_id_stack: Vec<ClipId>,
+    spatial_id_stack: Vec<SpatialId>,
 }
 
 impl YamlFrameReader {
@@ -243,9 +247,12 @@ impl YamlFrameReader {
             fonts: HashMap::new(),
             font_instances: HashMap::new(),
             font_render_mode: None,
-            image_map: HashMap::new(),
-            clip_id_map: HashMap::new(),
             allow_mipmaps: false,
+            image_map: HashMap::new(),
+            user_clip_id_map: HashMap::new(),
+            user_spatial_id_map: HashMap::new(),
+            clip_id_stack: Vec::new(),
+            spatial_id_stack: Vec::new(),
         }
     }
 
@@ -263,6 +270,13 @@ impl YamlFrameReader {
         wrench.api.update_resources(txn.resource_updates);
     }
 
+    fn top_space_and_clip(&self) -> SpaceAndClipInfo {
+        SpaceAndClipInfo {
+            spatial_id: *self.spatial_id_stack.last().unwrap(),
+            clip_id: *self.clip_id_stack.last().unwrap(),
+        }
+    }
+
     pub fn yaml_path(&self) -> &PathBuf {
         &self.yaml_path
     }
@@ -316,13 +330,21 @@ impl YamlFrameReader {
         yaml: &Yaml
     ) {
         // Don't allow referencing clips between pipelines for now.
-        self.clip_id_map.clear();
+        self.user_clip_id_map.clear();
+        self.user_spatial_id_map.clear();
+        self.clip_id_stack.clear();
+        self.clip_id_stack.push(ClipId::root(pipeline_id));
+        self.spatial_id_stack.clear();
+        self.spatial_id_stack.push(SpatialId::root_scroll_node(pipeline_id));
 
         let content_size = self.get_root_size_from_yaml(wrench, yaml);
         let mut builder = DisplayListBuilder::new(pipeline_id, content_size);
         let mut info = LayoutPrimitiveInfo::new(LayoutRect::zero());
         self.add_stacking_context_from_yaml(&mut builder, wrench, yaml, true, &mut info);
         self.display_lists.push(builder.finalize());
+
+        assert_eq!(self.clip_id_stack.len(), 1);
+        assert_eq!(self.spatial_id_stack.len(), 1);
     }
 
     fn to_complex_clip_region(&mut self, item: &Yaml) -> ComplexClipRegion {
@@ -362,50 +384,84 @@ impl YamlFrameReader {
         }
     }
 
-    pub fn u64_to_clip_id(&self, number: u64, pipeline_id: PipelineId) -> ClipId {
-        match number {
-            0 => ClipId::root_reference_frame(pipeline_id),
-            1 => ClipId::root_scroll_node(pipeline_id),
-            _ => self.clip_id_map[&number],
+    pub fn to_clip_id(&self, item: &Yaml, pipeline_id: PipelineId) -> Option<ClipId> {
+        match *item {
+            Yaml::Integer(value) => Some(self.user_clip_id_map[&(value as u64)]),
+            Yaml::String(ref id_string) if id_string == "root_clip" =>
+                Some(ClipId::root(pipeline_id)),
+            _ => None,
         }
-
     }
 
-    pub fn to_clip_id(&self, item: &Yaml, pipeline_id: PipelineId) -> Option<ClipId> {
+    pub fn to_spatial_id(&self, item: &Yaml, pipeline_id: PipelineId) -> SpatialId {
         match *item {
-            Yaml::Integer(value) => Some(self.u64_to_clip_id(value as u64, pipeline_id)),
+            Yaml::Integer(value) => self.user_spatial_id_map[&(value as u64)],
             Yaml::String(ref id_string) if id_string == "root-reference-frame" =>
-                Some(ClipId::root_reference_frame(pipeline_id)),
+                SpatialId::root_reference_frame(pipeline_id),
             Yaml::String(ref id_string) if id_string == "root-scroll-node" =>
-                Some(ClipId::root_scroll_node(pipeline_id)),
-            _ => None,
+                SpatialId::root_scroll_node(pipeline_id),
+            _ => {
+                println!("Unable to parse SpatialId {:?}", item);
+                SpatialId::root_reference_frame(pipeline_id)
+            }
         }
     }
 
     pub fn add_clip_id_mapping(&mut self, numeric_id: u64, real_id: ClipId) {
-        assert!(numeric_id != 0, "id=0 is reserved for the root reference frame");
-        assert!(numeric_id != 1, "id=1 is reserved for the root scroll node");
-        self.clip_id_map.insert(numeric_id, real_id);
+        assert_ne!(numeric_id, 0, "id=0 is reserved for the root clip");
+        self.user_clip_id_map.insert(numeric_id, real_id);
+    }
+
+    pub fn add_spatial_id_mapping(&mut self, numeric_id: u64, real_id: SpatialId) {
+        assert_ne!(numeric_id, 0, "id=0 is reserved for the root reference frame");
+        assert_ne!(numeric_id, 1, "id=1 is reserved for the root scroll node");
+        self.user_spatial_id_map.insert(numeric_id, real_id);
     }
 
     fn to_clip_and_scroll_info(
         &self,
         item: &Yaml,
         pipeline_id: PipelineId
-    ) -> Option<ClipAndScrollInfo> {
+    ) -> (Option<ClipId>, Option<SpatialId>) {
+        // Note: this is tricky. In the old code the single ID could be used either way
+        // for clip/scroll. In the new code we are trying to reflect that and not break
+        // all the reftests. Ultimately we'd want the clip and scroll nodes to be
+        // provided separately in YAML.
         match *item {
+            Yaml::BadValue => (None, None),
             Yaml::Array(ref array) if array.len() == 2 => {
-                let scroll_id = match self.to_clip_id(&array[0], pipeline_id) {
-                    Some(id) => id,
-                    None => return None,
-                };
-                let clip_id = match self.to_clip_id(&array[1], pipeline_id) {
-                    Some(id) => id,
-                    None => return None,
-                };
-                Some(ClipAndScrollInfo::new(scroll_id, clip_id))
+                let scroll_id = self.to_spatial_id(&array[0], pipeline_id);
+                let clip_id = self.to_clip_id(&array[1], pipeline_id);
+                (clip_id, Some(scroll_id))
+            }
+            Yaml::String(ref id_string) if id_string == "root-reference-frame" => {
+                let scroll_id = SpatialId::root_reference_frame(pipeline_id);
+                let clip_id = ClipId::root(pipeline_id);
+                (Some(clip_id), Some(scroll_id))
+            }
+            Yaml::String(ref id_string) if id_string == "root-scroll-node" => {
+                let scroll_id = SpatialId::root_scroll_node(pipeline_id);
+                (None, Some(scroll_id))
+            }
+            Yaml::String(ref id_string) if id_string == "root_clip" => {
+                let clip_id = ClipId::root(pipeline_id);
+                (Some(clip_id), None)
+            }
+            Yaml::Integer(value) => {
+                let scroll_id = self.user_spatial_id_map
+                    .get(&(value as u64))
+                    .cloned();
+                let clip_id = self.user_clip_id_map
+                    .get(&(value as u64))
+                    .cloned();
+                assert!(scroll_id.is_some() || clip_id.is_some(),
+                    "clip-and-scroll index not found: {}", value);
+                (clip_id, scroll_id)
+            }
+            _ => {
+                println!("Unable to parse clip/scroll {:?}", item);
+                (None, None)
             }
-            _ => self.to_clip_id(item, pipeline_id).map(|id| ClipAndScrollInfo::simple(id)),
         }
     }
 
@@ -709,7 +765,7 @@ impl YamlFrameReader {
             .as_rect()
             .expect("rect type must have bounds");
         let color = item["color"].as_colorf().unwrap_or(ColorF::WHITE);
-        dl.push_rect(&info, color);
+        dl.push_rect(&info, &self.top_space_and_clip(), color);
     }
 
     fn handle_clear_rect(
@@ -721,7 +777,7 @@ impl YamlFrameReader {
         info.rect = item["bounds"]
             .as_rect()
             .expect("clear-rect type must have bounds");
-        dl.push_clear_rect(&info);
+        dl.push_clear_rect(&info, &self.top_space_and_clip());
     }
 
     fn handle_line(
@@ -777,6 +833,7 @@ impl YamlFrameReader {
 
         dl.push_line(
             &info,
+            &self.top_space_and_clip(),
             wavy_line_thickness,
             orientation,
             &color,
@@ -803,7 +860,7 @@ impl YamlFrameReader {
         let tile_size = item["tile-size"].as_size().unwrap_or(bounds.size);
         let tile_spacing = item["tile-spacing"].as_size().unwrap_or(LayoutSize::zero());
 
-        dl.push_gradient(&info, gradient, tile_size, tile_spacing);
+        dl.push_gradient(&info, &self.top_space_and_clip(), gradient, tile_size, tile_spacing);
     }
 
     fn handle_radial_gradient(
@@ -825,7 +882,13 @@ impl YamlFrameReader {
         let tile_size = item["tile-size"].as_size().unwrap_or(bounds.size);
         let tile_spacing = item["tile-spacing"].as_size().unwrap_or(LayoutSize::zero());
 
-        dl.push_radial_gradient(&info, gradient, tile_size, tile_spacing);
+        dl.push_radial_gradient(
+            &info,
+            &self.top_space_and_clip(),
+            gradient,
+            tile_size,
+            tile_spacing,
+        );
     }
 
     fn handle_border(
@@ -987,7 +1050,7 @@ impl YamlFrameReader {
             None
         };
         if let Some(details) = border_details {
-            dl.push_border(&info, widths, details);
+            dl.push_border(&info, &self.top_space_and_clip(), widths, details);
         }
     }
 
@@ -1028,6 +1091,7 @@ impl YamlFrameReader {
 
         dl.push_box_shadow(
             &info,
+            &self.top_space_and_clip(),
             box_bounds,
             offset,
             color,
@@ -1090,6 +1154,7 @@ impl YamlFrameReader {
 
         dl.push_yuv_image(
             &info,
+            &self.top_space_and_clip(),
             yuv_data,
             color_depth,
             color_space,
@@ -1150,7 +1215,16 @@ impl YamlFrameReader {
                 item
             ),
         };
-        dl.push_image(&info, stretch_size, tile_spacing, rendering, alpha_type, image_key, ColorF::WHITE);
+        dl.push_image(
+            &info,
+            &self.top_space_and_clip(),
+            stretch_size,
+            tile_spacing,
+            rendering,
+            alpha_type,
+            image_key,
+            ColorF::WHITE,
+        );
     }
 
     fn handle_text(
@@ -1265,7 +1339,14 @@ impl YamlFrameReader {
         };
         info.rect = rect;
 
-        dl.push_text(&info, &glyphs, font_instance_key, color, None);
+        dl.push_text(
+            &info,
+            &self.top_space_and_clip(),
+            &glyphs,
+            font_instance_key,
+            color,
+            None,
+        );
     }
 
     fn handle_iframe(
@@ -1277,7 +1358,7 @@ impl YamlFrameReader {
         info.rect = item["bounds"].as_rect().expect("iframe must have bounds");
         let pipeline_id = item["id"].as_pipeline_id().unwrap();
         let ignore = item["ignore_missing_pipeline"].as_bool().unwrap_or(true);
-        dl.push_iframe(&info, pipeline_id, ignore);
+        dl.push_iframe(&info, &self.top_space_and_clip(), pipeline_id, ignore);
     }
 
     pub fn get_complex_clip_for_item(&mut self, yaml: &Yaml) -> Option<ComplexClipRegion> {
@@ -1332,12 +1413,15 @@ impl YamlFrameReader {
                 continue;
             }
 
-            let clip_scroll_info = self.to_clip_and_scroll_info(
+            let (set_clip_id, set_scroll_id) = self.to_clip_and_scroll_info(
                 &item["clip-and-scroll"],
                 dl.pipeline_id
             );
-            if let Some(clip_scroll_info) = clip_scroll_info {
-                dl.push_clip_and_scroll_info(clip_scroll_info);
+            if let Some(clip_id) = set_clip_id {
+                self.clip_id_stack.push(clip_id);
+            }
+            if let Some(scroll_id) = set_scroll_id {
+                self.spatial_id_stack.push(scroll_id);
             }
 
             let complex_clip = self.get_complex_clip_for_item(item);
@@ -1348,8 +1432,13 @@ impl YamlFrameReader {
                 match item_type {
                     "clip" | "clip-chain" | "scroll-frame" => {},
                     _ => {
-                        let id = dl.define_clip(clip_rect, vec![complex_clip], None);
-                        dl.push_clip_id(id);
+                        let id = dl.define_clip(
+                            &self.top_space_and_clip(),
+                            clip_rect,
+                            vec![complex_clip],
+                            None,
+                        );
+                        self.clip_id_stack.push(id);
                         pushed_clip = true;
                     }
                 }
@@ -1385,14 +1474,14 @@ impl YamlFrameReader {
             }
 
             if pushed_clip {
-                dl.pop_clip_id();
-
+                self.clip_id_stack.pop().unwrap();
             }
-
-            if clip_scroll_info.is_some() {
-                dl.pop_clip_id();
+            if set_clip_id.is_some() {
+                self.clip_id_stack.pop().unwrap();
+            }
+            if set_scroll_id.is_some() {
+                self.spatial_id_stack.pop().unwrap();
             }
-
         }
     }
 
@@ -1419,7 +1508,8 @@ impl YamlFrameReader {
             id
         });
 
-        let real_id = dl.define_scroll_frame(
+        let space_and_clip = dl.define_scroll_frame(
+            &self.top_space_and_clip(),
             external_id,
             content_rect,
             clip_rect,
@@ -1428,13 +1518,16 @@ impl YamlFrameReader {
             ScrollSensitivity::ScriptAndInputEvents,
         );
         if let Some(numeric_id) = numeric_id {
-            self.add_clip_id_mapping(numeric_id, real_id);
+            self.add_spatial_id_mapping(numeric_id, space_and_clip.spatial_id);
+            self.add_clip_id_mapping(numeric_id, space_and_clip.clip_id);
         }
 
         if !yaml["items"].is_badvalue() {
-            dl.push_clip_id(real_id);
+            self.spatial_id_stack.push(space_and_clip.spatial_id);
+            self.clip_id_stack.push(space_and_clip.clip_id);
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
-            dl.pop_clip_id();
+            self.clip_id_stack.pop().unwrap();
+            self.spatial_id_stack.pop().unwrap();
         }
     }
 
@@ -1448,6 +1541,7 @@ impl YamlFrameReader {
         let numeric_id = yaml["id"].as_i64().map(|id| id as u64);
 
         let real_id = dl.define_sticky_frame(
+            *self.spatial_id_stack.last().unwrap(),
             bounds,
             SideOffsets2D::new(
                 yaml["margin-top"].as_f32(),
@@ -1461,13 +1555,13 @@ impl YamlFrameReader {
         );
 
         if let Some(numeric_id) = numeric_id {
-            self.add_clip_id_mapping(numeric_id, real_id);
+            self.add_spatial_id_mapping(numeric_id, real_id);
         }
 
         if !yaml["items"].is_badvalue() {
-            dl.push_clip_id(real_id);
+            self.spatial_id_stack.push(real_id);
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
-            dl.pop_clip_id();
+            self.spatial_id_stack.pop().unwrap();
         }
     }
 
@@ -1483,6 +1577,7 @@ impl YamlFrameReader {
 
         dl.push_shadow(
             &info,
+            &self.top_space_and_clip(),
             Shadow {
                 blur_radius,
                 offset,
@@ -1499,8 +1594,9 @@ impl YamlFrameReader {
         let numeric_id = yaml["id"].as_i64().expect("clip chains must have an id");
         let clip_ids: Vec<ClipId> = yaml["clips"]
             .as_vec_u64()
-            .unwrap_or_else(Vec::new)
-            .iter().map(|id| self.u64_to_clip_id(*id, builder.pipeline_id))
+            .unwrap_or_default()
+            .iter()
+            .map(|id| self.user_clip_id_map[id])
             .collect();
 
         let parent = self.to_clip_id(&yaml["parent"], builder.pipeline_id);
@@ -1520,15 +1616,22 @@ impl YamlFrameReader {
         let complex_clips = self.to_complex_clip_regions(&yaml["complex"]);
         let image_mask = self.to_image_mask(&yaml["image-mask"], wrench);
 
-        let real_id = dl.define_clip(clip_rect, complex_clips, image_mask);
+        let space_and_clip = self.top_space_and_clip();
+        let real_id = dl.define_clip(
+            &space_and_clip,
+            clip_rect,
+            complex_clips,
+            image_mask,
+        );
         if let Some(numeric_id) = numeric_id {
             self.add_clip_id_mapping(numeric_id as u64, real_id);
+            self.add_spatial_id_mapping(numeric_id as u64, space_and_clip.spatial_id);
         }
 
         if !yaml["items"].is_badvalue() {
-            dl.push_clip_id(real_id);
+            self.clip_id_stack.push(real_id);
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
-            dl.pop_clip_id();
+            self.clip_id_stack.pop().unwrap();
         }
     }
 
@@ -1544,7 +1647,7 @@ impl YamlFrameReader {
         dl: &mut DisplayListBuilder,
         wrench: &mut Wrench,
         yaml: &Yaml,
-    ) -> ClipId {
+    ) -> SpatialId {
         let default_bounds = LayoutRect::new(LayoutPoint::zero(), wrench.window_size_f32());
         let bounds = yaml["bounds"].as_rect().unwrap_or(default_bounds);
         let default_transform_origin = LayoutPoint::new(
@@ -1578,6 +1681,7 @@ impl YamlFrameReader {
 
         let reference_frame_id = dl.push_reference_frame(
             &bounds,
+            *self.spatial_id_stack.last().unwrap(),
             transform_style,
             transform.into(),
             perspective,
@@ -1585,7 +1689,7 @@ impl YamlFrameReader {
 
         let numeric_id = yaml["id"].as_i64();
         if let Some(numeric_id) = numeric_id {
-            self.add_clip_id_mapping(numeric_id as u64, reference_frame_id);
+            self.add_spatial_id_mapping(numeric_id as u64, reference_frame_id);
         }
 
         reference_frame_id
@@ -1597,14 +1701,14 @@ impl YamlFrameReader {
         wrench: &mut Wrench,
         yaml: &Yaml,
     ) {
-        let reference_frame_id = self.push_reference_frame(dl, wrench, yaml);
+        let real_id = self.push_reference_frame(dl, wrench, yaml);
+        self.spatial_id_stack.push(real_id);
 
         if !yaml["items"].is_badvalue() {
-            dl.push_clip_id(reference_frame_id);
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
-            dl.pop_clip_id();
         }
 
+        self.spatial_id_stack.pop().unwrap();
         dl.pop_reference_frame();
     }
 
@@ -1617,21 +1721,21 @@ impl YamlFrameReader {
         info: &mut LayoutPrimitiveInfo,
     ) {
         let default_bounds = LayoutRect::new(LayoutPoint::zero(), wrench.window_size_f32());
-        let bounds = yaml["bounds"].as_rect().unwrap_or(default_bounds);
-        info.rect = bounds;
-        info.clip_rect = bounds;
+        let mut bounds = yaml["bounds"].as_rect().unwrap_or(default_bounds);
 
         let reference_frame_id = if !yaml["transform"].is_badvalue() ||
             !yaml["perspective"].is_badvalue() {
             let reference_frame_id = self.push_reference_frame(dl, wrench, yaml);
-            info.rect.origin = LayoutPoint::zero();
-            info.clip_rect.origin = LayoutPoint::zero();
+            self.spatial_id_stack.push(reference_frame_id);
+            bounds.origin = LayoutPoint::zero();
             Some(reference_frame_id)
         } else {
             None
         };
 
+        // note: this API is deprecated, use the standard clip-and-scroll field instead
         let clip_node_id = self.to_clip_id(&yaml["clip-node"], dl.pipeline_id);
+
         let transform_style = yaml["transform-style"]
             .as_transform_style()
             .unwrap_or(TransformStyle::Flat);
@@ -1651,12 +1755,12 @@ impl YamlFrameReader {
 
         let filters = yaml["filters"].as_vec_filter_op().unwrap_or(vec![]);
 
-        if let Some(reference_frame_id) = reference_frame_id {
-            dl.push_clip_id(reference_frame_id);
-        }
+        info.rect = bounds;
+        info.clip_rect = bounds;
 
         dl.push_stacking_context(
             &info,
+            *self.spatial_id_stack.last().unwrap(),
             clip_node_id,
             transform_style,
             mix_blend_mode,
@@ -1671,10 +1775,7 @@ impl YamlFrameReader {
         dl.pop_stacking_context();
 
         if reference_frame_id.is_some() {
-            dl.pop_clip_id();
-        }
-
-        if reference_frame_id.is_some() {
+            self.spatial_id_stack.pop().unwrap();
             dl.pop_reference_frame();
         }
     }
diff --git a/wrench/src/yaml_frame_writer.rs b/wrench/src/yaml_frame_writer.rs
index 098dba158b..f514cef85f 100644
--- a/wrench/src/yaml_frame_writer.rs
+++ b/wrench/src/yaml_frame_writer.rs
@@ -17,7 +17,7 @@ use super::CURRENT_FRAME_NUMBER;
 use time;
 use webrender;
 use webrender::api::*;
-use webrender::api::SpecificDisplayItem::*;
+use webrender::api::SpecificDisplayItem as Sdi;
 use webrender::api::channel::Payload;
 use yaml_helper::StringEnum;
 use yaml_rust::{Yaml, YamlEmitter};
@@ -206,7 +206,7 @@ fn write_reference_frame(
         matrix4d_node(parent, "perspective", &perspective);
     }
 
-    usize_node(parent, "id", clip_id_mapper.add_id(reference_frame.id));
+    usize_node(parent, "id", clip_id_mapper.add_spatial_id(reference_frame.id));
 }
 
 fn write_stacking_context(
@@ -214,7 +214,6 @@ fn write_stacking_context(
     sc: &StackingContext,
     properties: &SceneProperties,
     filter_iter: AuxIter<FilterOp>,
-    clip_id_mapper: &ClipIdMapper,
 ) {
     enum_node(parent, "transform-style", sc.transform_style);
 
@@ -228,10 +227,6 @@ fn write_stacking_context(
     };
     str_node(parent, "raster-space", &raster_space);
 
-    if let Some(clip_node_id) = sc.clip_node_id {
-        yaml_node(parent, "clip-node", clip_id_mapper.map_id(&clip_node_id));
-    }
-
     // mix_blend_mode
     if sc.mix_blend_mode != MixBlendMode::Normal {
         enum_node(parent, "mix-blend-mode", sc.mix_blend_mode)
@@ -767,18 +762,22 @@ impl YamlFrameWriter {
                 );
             }
 
-            yaml_node(&mut v, "clip-and-scroll", clip_id_mapper.map_info(&base.clip_and_scroll()));
+            let space_and_clip = base.space_and_clip_info();
+            let clip_id = if space_and_clip.clip_id.is_root() { None } else { Some(space_and_clip.clip_id) };
+            yaml_node(&mut v, "clip-and-scroll",
+                clip_id_mapper.map_clip_and_scroll_ids(clip_id, space_and_clip.spatial_id)
+            );
             bool_node(&mut v, "backface-visible", base.is_backface_visible());
 
             match *base.item() {
-                Rectangle(item) => {
+                Sdi::Rectangle(item) => {
                     str_node(&mut v, "type", "rect");
                     color_node(&mut v, "color", item.color);
                 }
-                ClearRectangle => {
+                Sdi::ClearRectangle => {
                     str_node(&mut v, "type", "clear-rect");;
                 }
-                Line(item) => {
+                Sdi::Line(item) => {
                     str_node(&mut v, "type", "line");
                     if let LineStyle::Wavy = item.style {
                         f32_node(&mut v, "thickness", item.wavy_line_thickness);
@@ -787,7 +786,7 @@ impl YamlFrameWriter {
                     color_node(&mut v, "color", item.color);
                     str_node(&mut v, "style", item.style.as_str());
                 }
-                Text(item) => {
+                Sdi::Text(item) => {
                     let gi = display_list.get(base.glyphs());
                     let mut indices: Vec<u32> = vec![];
                     let mut offsets: Vec<f32> = vec![];
@@ -841,7 +840,7 @@ impl YamlFrameWriter {
                         }
                     }
                 }
-                Image(item) => {
+                Sdi::Image(item) => {
                     if let Some(path) = self.path_for_image(item.image_key) {
                         path_node(&mut v, "image", &path);
                     }
@@ -864,12 +863,12 @@ impl YamlFrameWriter {
                         AlphaType::Alpha => str_node(&mut v, "alpha-type", "alpha"),
                     };
                 }
-                YuvImage(_) => {
+                Sdi::YuvImage(_) => {
                     str_node(&mut v, "type", "yuv-image");
                     // TODO
                     println!("TODO YAML YuvImage");
                 }
-                Border(item) => {
+                Sdi::Border(item) => {
                     str_node(&mut v, "type", "border");
                     match item.details {
                         BorderDetails::Normal(ref details) => {
@@ -984,7 +983,7 @@ impl YamlFrameWriter {
                         }
                     }
                 }
-                BoxShadow(item) => {
+                Sdi::BoxShadow(item) => {
                     str_node(&mut v, "type", "box-shadow");
                     rect_node(&mut v, "box-bounds", &item.box_bounds);
                     vector_node(&mut v, "offset", &item.offset);
@@ -1000,7 +999,7 @@ impl YamlFrameWriter {
                     };
                     str_node(&mut v, "clip-mode", clip_mode);
                 }
-                Gradient(item) => {
+                Sdi::Gradient(item) => {
                     str_node(&mut v, "type", "gradient");
                     point_node(&mut v, "start", &item.gradient.start_point);
                     point_node(&mut v, "end", &item.gradient.end_point);
@@ -1018,7 +1017,7 @@ impl YamlFrameWriter {
                         item.gradient.extend_mode == ExtendMode::Repeat,
                     );
                 }
-                RadialGradient(item) => {
+                Sdi::RadialGradient(item) => {
                     str_node(&mut v, "type", "radial-gradient");
                     size_node(&mut v, "tile-size", &item.tile_size);
                     size_node(&mut v, "tile-spacing", &item.tile_spacing);
@@ -1029,12 +1028,12 @@ impl YamlFrameWriter {
                         display_list
                     );
                 }
-                Iframe(item) => {
+                Sdi::Iframe(item) => {
                     str_node(&mut v, "type", "iframe");
                     u32_vec_node(&mut v, "id", &[item.pipeline_id.0, item.pipeline_id.1]);
                     bool_node(&mut v, "ignore_missing_pipeline", item.ignore_missing_pipeline);
                 }
-                PushStackingContext(item) => {
+                Sdi::PushStackingContext(item) => {
                     str_node(&mut v, "type", "stacking-context");
                     let filters = display_list.get(base.filters());
                     write_stacking_context(
@@ -1042,29 +1041,28 @@ impl YamlFrameWriter {
                         &item.stacking_context,
                         &scene.properties,
                         filters,
-                        clip_id_mapper,
                     );
 
                     let mut sub_iter = base.sub_iter();
                     self.write_display_list(&mut v, display_list, scene, &mut sub_iter, clip_id_mapper);
                     continue_traversal = Some(sub_iter);
                 }
-                PushReferenceFrame(item) => {
+                Sdi::PushReferenceFrame(item) => {
                     str_node(&mut v, "type", "reference-frame");
                     write_reference_frame(
                         &mut v,
                         &item.reference_frame,
                         &scene.properties,
-                        clip_id_mapper
+                        clip_id_mapper,
                     );
 
                     let mut sub_iter = base.sub_iter();
                     self.write_display_list(&mut v, display_list, scene, &mut sub_iter, clip_id_mapper);
                     continue_traversal = Some(sub_iter);
                 }
-                Clip(item) => {
+                Sdi::Clip(item) => {
                     str_node(&mut v, "type", "clip");
-                    usize_node(&mut v, "id", clip_id_mapper.add_id(item.id));
+                    usize_node(&mut v, "id", clip_id_mapper.add_clip_id(item.id));
 
                     let (complex_clips, complex_clip_count) = base.complex_clip();
                     if let Some(complex) = self.make_complex_clips_node(
@@ -1079,25 +1077,25 @@ impl YamlFrameWriter {
                         yaml_node(&mut v, "image-mask", mask_yaml);
                     }
                 }
-                ClipChain(item) => {
+                Sdi::ClipChain(item) => {
                     str_node(&mut v, "type", "clip-chain");
 
                     let id = ClipId::ClipChain(item.id);
-                    u32_node(&mut v, "id", clip_id_mapper.add_id(id) as u32);
+                    u32_node(&mut v, "id", clip_id_mapper.add_clip_id(id) as u32);
 
                     let clip_ids = display_list.get(base.clip_chain_items()).map(|clip_id| {
-                        clip_id_mapper.map_id(&clip_id)
+                        clip_id_mapper.map_clip_id(&clip_id)
                     }).collect();
                     yaml_node(&mut v, "clips", Yaml::Array(clip_ids));
 
                     if let Some(parent) = item.parent {
                         let parent = ClipId::ClipChain(parent);
-                        yaml_node(&mut v, "parent", clip_id_mapper.map_id(&parent));
+                        yaml_node(&mut v, "parent", clip_id_mapper.map_clip_id(&parent));
                     }
                 }
-                ScrollFrame(item) => {
+                Sdi::ScrollFrame(item) => {
                     str_node(&mut v, "type", "scroll-frame");
-                    usize_node(&mut v, "id", clip_id_mapper.add_id(item.scroll_frame_id));
+                    usize_node(&mut v, "id", clip_id_mapper.add_spatial_id(item.scroll_frame_id));
                     size_node(&mut v, "content-size", &base.rect().size);
                     rect_node(&mut v, "bounds", &base.clip_rect());
 
@@ -1114,9 +1112,9 @@ impl YamlFrameWriter {
                         yaml_node(&mut v, "image-mask", mask_yaml);
                     }
                 }
-                StickyFrame(item) => {
+                Sdi::StickyFrame(item) => {
                     str_node(&mut v, "type", "sticky-frame");
-                    usize_node(&mut v, "id", clip_id_mapper.add_id(item.id));
+                    usize_node(&mut v, "id", clip_id_mapper.add_spatial_id(item.id));
                     rect_node(&mut v, "bounds", &base.clip_rect());
 
                     if let Some(margin) = item.margins.top {
@@ -1151,21 +1149,22 @@ impl YamlFrameWriter {
                     yaml_node(&mut v, "previously-applied-offset", Yaml::Array(applied));
                 }
 
-                PopCacheMarker => return,
-                PushCacheMarker(_) => {
+                Sdi::PopReferenceFrame |
+                Sdi::PopStackingContext => return,
+
+                Sdi::PopCacheMarker => return,
+                Sdi::PushCacheMarker(_) => {
                     str_node(&mut v, "type", "cache-marker");
                 }
 
-                PopStackingContext => return,
-                PopReferenceFrame => return,
-                SetGradientStops => panic!("dummy item yielded?"),
-                PushShadow(shadow) => {
+                Sdi::SetGradientStops => panic!("dummy item yielded?"),
+                Sdi::PushShadow(shadow) => {
                     str_node(&mut v, "type", "shadow");
                     vector_node(&mut v, "offset", &shadow.offset);
                     color_node(&mut v, "color", shadow.color);
                     f32_node(&mut v, "blur-radius", shadow.blur_radius);
                 }
-                PopAllShadows => {
+                Sdi::PopAllShadows => {
                     str_node(&mut v, "type", "pop-all-shadows");
                 }
             }
@@ -1247,43 +1246,62 @@ impl webrender::ApiRecordingReceiver for YamlFrameWriterReceiver {
 }
 
 /// This structure allows mapping both `Clip` and `ClipExternalId`
-/// `ClipIds` onto one set of numeric ids. This prevents ids
-/// from clashing in the yaml output.
+/// `ClipIds` onto one set of numeric ids. It also handles `SpatialId`
+/// in a separate map. This prevents ids from clashing in the yaml output.
 struct ClipIdMapper {
-    hash_map: HashMap<ClipId, usize>,
+    clip_map: HashMap<ClipId, usize>,
+    spatial_map: HashMap<SpatialId, usize>,
     current_clip_id: usize,
+    current_spatial_id: usize,
 }
 
 impl ClipIdMapper {
-    fn new() -> ClipIdMapper {
+    fn new() -> Self {
         ClipIdMapper {
-            hash_map: HashMap::new(),
-            current_clip_id: 2,
+            clip_map: HashMap::new(),
+            spatial_map: HashMap::new(),
+            current_clip_id: 1, // see FIRST_CLIP_NODE_INDEX
+            current_spatial_id: 2, // see FIRST_SPATIAL_NODE_INDEX
         }
     }
 
-    fn add_id(&mut self, id: ClipId) -> usize {
-        self.hash_map.insert(id, self.current_clip_id);
+    fn add_clip_id(&mut self, id: ClipId) -> usize {
+        self.clip_map.insert(id, self.current_clip_id);
         self.current_clip_id += 1;
         self.current_clip_id - 1
     }
 
-    fn map_id(&self, id: &ClipId) -> Yaml {
+    fn add_spatial_id(&mut self, id: SpatialId) -> usize {
+        self.spatial_map.insert(id, self.current_spatial_id);
+        self.current_spatial_id += 1;
+        self.current_spatial_id - 1
+    }
+
+    fn map_spatial_id(&self, id: &SpatialId) -> Yaml {
         if id.is_root_reference_frame() {
             Yaml::String("root-reference-frame".to_owned())
         } else if id.is_root_scroll_node() {
             Yaml::String("root-scroll-node".to_owned())
         } else {
-            Yaml::Integer(*self.hash_map.get(id).unwrap() as i64)
+            Yaml::Integer(self.spatial_map[id] as i64)
+        }
+    }
+
+    fn map_clip_id(&self, id: &ClipId) -> Yaml {
+        assert!(id.is_valid());
+        if id.is_root() {
+            Yaml::String("root_clip".to_owned())
+        } else {
+            Yaml::Integer(self.clip_map[id] as i64)
         }
     }
 
-    fn map_info(&self, info: &ClipAndScrollInfo) -> Yaml {
-        let scroll_node_yaml = self.map_id(&info.scroll_node_id);
-        match info.clip_node_id {
+    fn map_clip_and_scroll_ids(&self, clip_id: Option<ClipId>, spatial_id: SpatialId) -> Yaml {
+        let scroll_node_yaml = self.map_spatial_id(&spatial_id);
+        match clip_id {
             Some(ref clip_node_id) => Yaml::Array(vec![
                 scroll_node_yaml,
-                self.map_id(&clip_node_id)
+                self.map_clip_id(&clip_node_id)
             ]),
             None => scroll_node_yaml,
         }