Bug 1451453 - Update webrender to commit 092ada1154b72fe71d2f227a5df0239586d2323a. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Mon, 09 Apr 2018 08:26:07 -0400
changeset 779187 caffb9ad0e0e9d2b3f963e688d3227cfc6c3d720
parent 779146 30d72755b1749953d438199456f1524ce84ab5e5
child 779188 0f043f6ab67436663a296c120cf31586a5e02052
push id105686
push userkgupta@mozilla.com
push dateMon, 09 Apr 2018 12:29:08 +0000
reviewersjrmuizel
bugs1451453
milestone61.0a1
Bug 1451453 - Update webrender to commit 092ada1154b72fe71d2f227a5df0239586d2323a. r?jrmuizel MozReview-Commit-ID: 9djydrUnoWq
gfx/webrender/examples/alpha_perf.rs
gfx/webrender/examples/animation.rs
gfx/webrender/examples/basic.rs
gfx/webrender/examples/blob.rs
gfx/webrender/examples/document.rs
gfx/webrender/examples/frame_output.rs
gfx/webrender/examples/iframe.rs
gfx/webrender/examples/image_resize.rs
gfx/webrender/examples/multiwindow.rs
gfx/webrender/examples/scrolling.rs
gfx/webrender/examples/texture_cache_stress.rs
gfx/webrender/examples/yuv.rs
gfx/webrender/res/base.glsl
gfx/webrender/res/brush.glsl
gfx/webrender/res/clip_scroll.glsl
gfx/webrender/res/clip_shared.glsl
gfx/webrender/res/cs_clip_border.glsl
gfx/webrender/res/cs_clip_box_shadow.glsl
gfx/webrender/res/cs_clip_image.glsl
gfx/webrender/res/cs_clip_line.glsl
gfx/webrender/res/cs_clip_rectangle.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/render_task.glsl
gfx/webrender/res/resource_cache.glsl
gfx/webrender/res/shared.glsl
gfx/webrender/res/snap.glsl
gfx/webrender/res/transform.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/border.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/device.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/ellipse.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/hit_test.rs
gfx/webrender/src/image.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/platform/macos/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/scene_builder.rs
gfx/webrender/src/shade.rs
gfx/webrender/src/spring.rs
gfx/webrender/src/tiling.rs
gfx/webrender/src/util.rs
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/src/yaml_frame_reader.rs
gfx/wrench/src/yaml_frame_writer.rs
--- a/gfx/webrender/examples/alpha_perf.rs
+++ b/gfx/webrender/examples/alpha_perf.rs
@@ -28,16 +28,17 @@ impl Example for App {
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = (0, 0).to(1920, 1080);
         let info = LayoutPrimitiveInfo::new(bounds);
 
         builder.push_stacking_context(
             &info,
+            None,
             ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
         );
 
--- a/gfx/webrender/examples/animation.rs
+++ b/gfx/webrender/examples/animation.rs
@@ -44,16 +44,17 @@ impl Example for App {
 
         let filters = vec![
             FilterOp::Opacity(PropertyBinding::Binding(self.opacity_key), self.opacity),
         ];
 
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
+            None,
             ScrollPolicy::Scrollable,
             Some(PropertyBinding::Binding(self.property_key)),
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             filters,
         );
 
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -186,16 +186,17 @@ impl Example for App {
         _: DeviceUintSize,
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
+            None,
             ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
         );
 
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -244,16 +244,17 @@ impl Example for App {
             api::ImageData::new_blob_image(serialize_blob(api::ColorU::new(50, 150, 50, 255))),
             None,
         );
 
         let bounds = api::LayoutRect::new(api::LayoutPoint::zero(), builder.content_size());
         let info = api::LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
+            None,
             api::ScrollPolicy::Scrollable,
             None,
             api::TransformStyle::Flat,
             None,
             api::MixBlendMode::Normal,
             Vec::new(),
         );
 
--- a/gfx/webrender/examples/document.rs
+++ b/gfx/webrender/examples/document.rs
@@ -107,16 +107,17 @@ impl Example for App {
             );
             let local_rect = LayoutRect::new(
                 LayoutPoint::zero(),
                 doc.content_rect.size,
             );
 
             builder.push_stacking_context(
                 &LayoutPrimitiveInfo::new(doc.content_rect),
+                None,
                 ScrollPolicy::Fixed,
                 None,
                 TransformStyle::Flat,
                 None,
                 MixBlendMode::Normal,
                 Vec::new(),
             );
             builder.push_rect(
--- a/gfx/webrender/examples/frame_output.rs
+++ b/gfx/webrender/examples/frame_output.rs
@@ -95,16 +95,17 @@ impl App {
         let info = LayoutPrimitiveInfo::new(document.content_rect);
         let mut builder = DisplayListBuilder::new(
             document.pipeline_id,
             document.content_rect.size,
         );
 
         builder.push_stacking_context(
             &info,
+            None,
             ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
         );
 
@@ -142,16 +143,17 @@ impl Example for App {
             let device_pixel_ratio = framebuffer_size.width as f32 /
                 builder.content_size().width;
             self.init_output_document(api, DeviceUintSize::new(200, 200), device_pixel_ratio);
         }
 
         let info = LayoutPrimitiveInfo::new((100, 100).to(200, 200));
         builder.push_stacking_context(
             &info,
+            None,
             ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
         );
 
--- a/gfx/webrender/examples/iframe.rs
+++ b/gfx/webrender/examples/iframe.rs
@@ -33,16 +33,17 @@ impl Example for App {
         let sub_bounds = (0, 0).to(sub_size.width as i32, sub_size.height as i32);
 
         let sub_pipeline_id = PipelineId(pipeline_id.0, 42);
         let mut sub_builder = DisplayListBuilder::new(sub_pipeline_id, sub_bounds.size);
 
         let info = LayoutPrimitiveInfo::new(sub_bounds);
         sub_builder.push_stacking_context(
             &info,
+            None,
             ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
         );
 
@@ -58,16 +59,17 @@ impl Example for App {
             sub_builder.finalize(),
             true,
         );
         api.send_transaction(document_id, txn);
 
         // And this is for the root pipeline
         builder.push_stacking_context(
             &info,
+            None,
             ScrollPolicy::Scrollable,
             Some(PropertyBinding::Binding(PropertyBindingKey::new(42))),
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
         );
         // red rect under the iframe: if this is visible, things have gone wrong
--- a/gfx/webrender/examples/image_resize.rs
+++ b/gfx/webrender/examples/image_resize.rs
@@ -35,16 +35,17 @@ impl Example for App {
             image_data,
             None,
         );
 
         let bounds = (0, 0).to(512, 512);
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
+            None,
             ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
         );
 
--- a/gfx/webrender/examples/multiwindow.rs
+++ b/gfx/webrender/examples/multiwindow.rs
@@ -175,16 +175,17 @@ 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 bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
+            None,
             ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
         );
 
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -28,30 +28,32 @@ impl Example for App {
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let info = LayoutPrimitiveInfo::new(
             LayoutRect::new(LayoutPoint::zero(), builder.content_size())
         );
         builder.push_stacking_context(
             &info,
+            None,
             ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
         );
 
         if true {
             // scrolling and clips stuff
             // let's make a scrollbox
             let scrollbox = (0, 0).to(300, 400);
             builder.push_stacking_context(
                 &LayoutPrimitiveInfo::new((10, 10).by(0, 0)),
+                None,
                 ScrollPolicy::Scrollable,
                 None,
                 TransformStyle::Flat,
                 None,
                 MixBlendMode::Normal,
                 Vec::new(),
             );
             // set the scrolling clip
@@ -158,33 +160,31 @@ impl Example for App {
                     glutin::VirtualKeyCode::Right => (-10.0, 0.0),
                     glutin::VirtualKeyCode::Left => (10.0, 0.0),
                     _ => return false,
                 };
 
                 txn.scroll(
                     ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
                     self.cursor_position,
-                    ScrollEventPhase::Start,
                 );
             }
             glutin::WindowEvent::CursorMoved { position: (x, y), .. } => {
                 self.cursor_position = WorldPoint::new(x as f32, y as f32);
             }
             glutin::WindowEvent::MouseWheel { delta, .. } => {
                 const LINE_HEIGHT: f32 = 38.0;
                 let (dx, dy) = match delta {
                     glutin::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
                     glutin::MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
                 };
 
                 txn.scroll(
                     ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
                     self.cursor_position,
-                    ScrollEventPhase::Start,
                 );
             }
             glutin::WindowEvent::MouseInput { .. } => {
                 let results = api.hit_test(
                     document_id,
                     None,
                     self.cursor_position,
                     HitTestFlags::FIND_ALL
--- a/gfx/webrender/examples/texture_cache_stress.rs
+++ b/gfx/webrender/examples/texture_cache_stress.rs
@@ -87,16 +87,17 @@ impl Example for App {
         _framebuffer_size: DeviceUintSize,
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = (0, 0).to(512, 512);
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
+            None,
             ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
         );
 
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -82,16 +82,17 @@ impl Example for App {
         _framebuffer_size: DeviceUintSize,
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
+            None,
             ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
         );
 
--- a/gfx/webrender/res/base.glsl
+++ b/gfx/webrender/res/base.glsl
@@ -28,11 +28,10 @@
 #endif
 
 #ifdef WR_VERTEX_SHADER
     #define varying out
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
     precision highp float;
-
     #define varying in
 #endif
--- a/gfx/webrender/res/brush.glsl
+++ b/gfx/webrender/res/brush.glsl
@@ -98,24 +98,24 @@ void main(void) {
             local_segment_rect,
             brush_prim.local_clip_rect,
             float(brush.z),
             scroll_node,
             pic_task,
             brush_prim.local_rect
         );
 
-        // TODO(gw): vLocalBounds may be referenced by
+        // TODO(gw): transform bounds may be referenced by
         //           the fragment shader when running in
         //           the alpha pass, even on non-transformed
         //           items. For now, just ensure it has no
         //           effect. We can tidy this up as we move
         //           more items to be brush shaders.
 #ifdef WR_FEATURE_ALPHA_PASS
-        vLocalBounds = vec4(vec2(-1000000.0), vec2(1000000.0));
+        init_transform_vs(vec4(vec2(-1000000.0), vec2(1000000.0)));
 #endif
     } else {
         bvec4 edge_mask = notEqual(brush.edge_mask & ivec4(1, 2, 4, 8), ivec4(0));
         bool do_perspective_interpolation = (brush.flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0;
 
         vi = write_transform_vertex(
             local_segment_rect,
             brush_prim.local_rect,
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/clip_scroll.glsl
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifdef WR_VERTEX_SHADER
+#define VECS_PER_CLIP_SCROLL_NODE   9
+
+uniform HIGHP_SAMPLER_FLOAT sampler2D sClipScrollNodes;
+
+struct ClipScrollNode {
+    mat4 transform;
+    mat4 inv_transform;
+    bool is_axis_aligned;
+};
+
+ClipScrollNode fetch_clip_scroll_node(int index) {
+    ClipScrollNode node;
+
+    // Create a UV base coord for each 8 texels.
+    // This is required because trying to use an offset
+    // of more than 8 texels doesn't work on some versions
+    // of OSX.
+    ivec2 uv = get_fetch_uv(index, VECS_PER_CLIP_SCROLL_NODE);
+    ivec2 uv0 = ivec2(uv.x + 0, uv.y);
+    ivec2 uv1 = ivec2(uv.x + 8, uv.y);
+
+    node.transform[0] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(0, 0));
+    node.transform[1] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(1, 0));
+    node.transform[2] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(2, 0));
+    node.transform[3] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(3, 0));
+
+    node.inv_transform[0] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(4, 0));
+    node.inv_transform[1] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(5, 0));
+    node.inv_transform[2] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(6, 0));
+    node.inv_transform[3] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(7, 0));
+
+    vec4 misc = TEXEL_FETCH(sClipScrollNodes, uv1, 0, ivec2(0, 0));
+    node.is_axis_aligned = misc.x == 0.0;
+
+    return node;
+}
+
+// Return the intersection of the plane (set up by "normal" and "point")
+// with the ray (set up by "ray_origin" and "ray_dir"),
+// writing the resulting scaler into "t".
+bool ray_plane(vec3 normal, vec3 pt, vec3 ray_origin, vec3 ray_dir, out float t)
+{
+    float denom = dot(normal, ray_dir);
+    if (abs(denom) > 1e-6) {
+        vec3 d = pt - ray_origin;
+        t = dot(d, normal) / denom;
+        return t >= 0.0;
+    }
+
+    return false;
+}
+
+// Apply the inverse transform "inv_transform"
+// to the reference point "ref" in CSS space,
+// producing a local point on a ClipScrollNode plane,
+// set by a base point "a" and a normal "n".
+vec4 untransform(vec2 ref, vec3 n, vec3 a, mat4 inv_transform) {
+    vec3 p = vec3(ref, -10000.0);
+    vec3 d = vec3(0, 0, 1.0);
+
+    float t = 0.0;
+    // get an intersection of the ClipScrollNode plane with Z axis vector,
+    // originated from the "ref" point
+    ray_plane(n, a, p, d, t);
+    float z = p.z + d.z * t; // Z of the visible point on the ClipScrollNode
+
+    vec4 r = inv_transform * vec4(ref, z, 1.0);
+    return r;
+}
+
+// Given a CSS space position, transform it back into the ClipScrollNode space.
+vec4 get_node_pos(vec2 pos, ClipScrollNode node) {
+    // get a point on the scroll node plane
+    vec4 ah = node.transform * vec4(0.0, 0.0, 0.0, 1.0);
+    vec3 a = ah.xyz / ah.w;
+
+    // get the normal to the scroll node plane
+    vec3 n = transpose(mat3(node.inv_transform)) * vec3(0.0, 0.0, 1.0);
+    return untransform(pos, n, a, node.inv_transform);
+}
+
+#endif //WR_VERTEX_SHADER
+
--- a/gfx/webrender/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include rect,clip_scroll,render_task,resource_cache,snap,transform
+
 #ifdef WR_VERTEX_SHADER
 
 #define SEGMENT_ALL         0
 #define SEGMENT_CORNER_TL   1
 #define SEGMENT_CORNER_TR   2
 #define SEGMENT_CORNER_BL   3
 #define SEGMENT_CORNER_BR   4
 
@@ -46,34 +48,62 @@ RectWithSize intersect_rect(RectWithSize
     return RectWithSize(p.xy, max(vec2(0.0), p.zw - p.xy));
 }
 
 // The transformed vertex function that always covers the whole clip area,
 // which is the intersection of all clip instances of a given primitive
 ClipVertexInfo write_clip_tile_vertex(RectWithSize local_clip_rect,
                                       ClipScrollNode scroll_node,
                                       ClipArea area) {
-    vec2 actual_pos = area.screen_origin + aPosition.xy * area.common_data.task_rect.size;
+    vec2 device_pos = area.screen_origin + aPosition.xy * area.common_data.task_rect.size;
+    vec2 actual_pos = device_pos;
+
+    if (scroll_node.is_axis_aligned) {
+        vec4 snap_positions = compute_snap_positions(
+            scroll_node.transform,
+            local_clip_rect
+        );
+
+        vec2 snap_offsets = compute_snap_offset_impl(
+            device_pos,
+            scroll_node.transform,
+            local_clip_rect,
+            RectWithSize(snap_positions.xy, snap_positions.zw - snap_positions.xy),
+            snap_positions
+        );
+
+        actual_pos -= snap_offsets;
+    }
 
     vec4 node_pos;
 
     // Select the local position, based on whether we are rasterizing this
     // clip mask in local- or sccreen-space.
     if (area.local_space) {
         node_pos = vec4(actual_pos / uDevicePixelRatio, 0.0, 1.0);
     } else {
         node_pos = get_node_pos(actual_pos / uDevicePixelRatio, scroll_node);
     }
 
     // compute the point position inside the scroll node, in CSS space
-    vec2 vertex_pos = actual_pos +
+    vec2 vertex_pos = device_pos +
                       area.common_data.task_rect.p0 -
                       area.screen_origin;
 
     gl_Position = uTransform * vec4(vertex_pos, 0.0, 1);
 
-    vLocalBounds = vec4(local_clip_rect.p0, local_clip_rect.p0 + local_clip_rect.size);
+    init_transform_vs(vec4(local_clip_rect.p0, local_clip_rect.p0 + local_clip_rect.size));
 
     ClipVertexInfo vi = ClipVertexInfo(node_pos.xyw, actual_pos, local_clip_rect);
     return vi;
 }
 
 #endif //WR_VERTEX_SHADER
+
+#ifdef WR_FRAGMENT_SHADER
+
+//Note: identical to prim_shared
+float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) {
+    vec2 dir_to_p0 = p0 - p;
+    return dot(normalize(perp_dir), dir_to_p0);
+}
+
+#endif //WR_FRAGMENT_SHADER
--- a/gfx/webrender/res/cs_clip_border.glsl
+++ b/gfx/webrender/res/cs_clip_border.glsl
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include shared,prim_shared,clip_shared
+#include shared,clip_shared
 
 varying vec3 vPos;
 
 flat varying vec2 vClipCenter;
 
 flat varying vec4 vPoint_Tangent0;
 flat varying vec4 vPoint_Tangent1;
 flat varying vec3 vDotParams;
--- a/gfx/webrender/res/cs_clip_box_shadow.glsl
+++ b/gfx/webrender/res/cs_clip_box_shadow.glsl
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include shared,prim_shared,clip_shared
+#include shared,clip_shared
 
 varying vec3 vPos;
 varying vec2 vUv;
 flat varying vec4 vUvBounds;
 flat varying float vLayer;
 flat varying vec4 vEdge;
 flat varying vec4 vUvBounds_NoClamp;
 flat varying float vClipMode;
@@ -98,21 +98,17 @@ void main(void) {
 void main(void) {
     vec2 local_pos = vPos.xy / vPos.z;
 
     vec2 uv = clamp(vUv.xy, vec2(0.0), vEdge.xy);
     uv += max(vec2(0.0), vUv.xy - vEdge.zw);
     uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
     uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
 
-    float in_shadow_rect = point_inside_rect(
-        local_pos,
-        vLocalBounds.xy,
-        vLocalBounds.zw
-    );
+    float in_shadow_rect = init_transform_rough_fs(local_pos);
 
     float texel = TEX_SAMPLE(sColor0, vec3(uv, vLayer)).r;
 
     float alpha = mix(texel, 1.0 - texel, vClipMode);
 
     oFragColor = vec4(mix(vClipMode, alpha, in_shadow_rect));
 }
 #endif
--- a/gfx/webrender/res/cs_clip_image.glsl
+++ b/gfx/webrender/res/cs_clip_image.glsl
@@ -1,15 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include shared,prim_shared,clip_shared
+#include shared,clip_shared
 
 varying vec3 vPos;
+varying vec3 vClipMaskImageUv;
+
 flat varying vec4 vClipMaskUvRect;
 flat varying vec4 vClipMaskUvInnerRect;
 flat varying float vLayer;
 
 #ifdef WR_VERTEX_SHADER
 struct ImageMaskData {
     RectWithSize local_rect;
 };
@@ -31,31 +33,31 @@ void main(void) {
 
     ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
                                                scroll_node,
                                                area);
 
     vPos = vi.local_pos;
     vLayer = res.layer;
 
-    vClipMaskUv = vec3((vPos.xy / vPos.z - local_rect.p0) / local_rect.size, 0.0);
+    vClipMaskImageUv = vec3((vPos.xy / vPos.z - local_rect.p0) / local_rect.size, 0.0);
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vClipMaskUvRect = vec4(res.uv_rect.p0, res.uv_rect.p1 - res.uv_rect.p0) / texture_size.xyxy;
     // applying a half-texel offset to the UV boundaries to prevent linear samples from the outside
     vec4 inner_rect = vec4(res.uv_rect.p0, res.uv_rect.p1);
     vClipMaskUvInnerRect = (inner_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy;
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 void main(void) {
     float alpha = init_transform_fs(vPos.xy / vPos.z);
 
     bool repeat_mask = false; //TODO
-    vec2 clamped_mask_uv = repeat_mask ? fract(vClipMaskUv.xy) :
-        clamp(vClipMaskUv.xy, vec2(0.0, 0.0), vec2(1.0, 1.0));
+    vec2 clamped_mask_uv = repeat_mask ? fract(vClipMaskImageUv.xy) :
+        clamp(vClipMaskImageUv.xy, vec2(0.0, 0.0), vec2(1.0, 1.0));
     vec2 source_uv = clamp(clamped_mask_uv * vClipMaskUvRect.zw + vClipMaskUvRect.xy,
         vClipMaskUvInnerRect.xy, vClipMaskUvInnerRect.zw);
     float clip_alpha = texture(sColor0, vec3(source_uv, vLayer)).r; //careful: texture has type A8
 
     oFragColor = vec4(alpha * clip_alpha, 1.0, 1.0, 1.0);
 }
 #endif
--- a/gfx/webrender/res/cs_clip_line.glsl
+++ b/gfx/webrender/res/cs_clip_line.glsl
@@ -1,13 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include shared,prim_shared,clip_shared
+#include shared,clip_shared
+
+#define LINE_STYLE_SOLID        0
+#define LINE_STYLE_DOTTED       1
+#define LINE_STYLE_DASHED       2
+#define LINE_STYLE_WAVY         3
 
 varying vec3 vLocalPos;
 
 flat varying int vStyle;
 flat varying float vAxisSelect;
 flat varying vec4 vParams;
 flat varying vec2 vLocalOrigin;
 
--- a/gfx/webrender/res/cs_clip_rectangle.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.glsl
@@ -1,43 +1,45 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include shared,prim_shared,clip_shared,ellipse
+#include shared,clip_shared,ellipse
 
 varying vec3 vPos;
 flat varying float vClipMode;
 flat varying vec4 vClipCenter_Radius_TL;
 flat varying vec4 vClipCenter_Radius_TR;
 flat varying vec4 vClipCenter_Radius_BL;
 flat varying vec4 vClipCenter_Radius_BR;
 
 #ifdef WR_VERTEX_SHADER
 struct ClipRect {
     RectWithSize rect;
     vec4 mode;
 };
 
 ClipRect fetch_clip_rect(ivec2 address) {
     vec4 data[2] = fetch_from_resource_cache_2_direct(address);
-    return ClipRect(RectWithSize(data[0].xy, data[0].zw), data[1]);
+    ClipRect rect = ClipRect(RectWithSize(data[0].xy, data[0].zw), data[1]);
+    return rect;
 }
 
 struct ClipCorner {
     RectWithSize rect;
     vec4 outer_inner_radius;
 };
 
 // index is of type float instead of int because using an int led to shader
 // miscompilations with a macOS 10.12 Intel driver.
 ClipCorner fetch_clip_corner(ivec2 address, float index) {
     address += ivec2(2 + 2 * int(index), 0);
     vec4 data[2] = fetch_from_resource_cache_2_direct(address);
-    return ClipCorner(RectWithSize(data[0].xy, data[0].zw), data[1]);
+    ClipCorner corner = ClipCorner(RectWithSize(data[0].xy, data[0].zw), data[1]);
+    return corner;
 }
 
 struct ClipData {
     ClipRect rect;
     ClipCorner top_left;
     ClipCorner top_right;
     ClipCorner bottom_left;
     ClipCorner bottom_right;
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -1,31 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include rect
+#include rect,clip_scroll,render_task,resource_cache,snap,transform
 
 #define EXTEND_MODE_CLAMP  0
 #define EXTEND_MODE_REPEAT 1
 
-#define LINE_STYLE_SOLID        0
-#define LINE_STYLE_DOTTED       1
-#define LINE_STYLE_DASHED       2
-#define LINE_STYLE_WAVY         3
-
 #define SUBPX_DIR_NONE        0
 #define SUBPX_DIR_HORIZONTAL  1
 #define SUBPX_DIR_VERTICAL    2
 
 #define RASTER_LOCAL            0
 #define RASTER_SCREEN           1
 
-#define EPSILON     0.0001
-
 uniform sampler2DArray sCacheA8;
 uniform sampler2DArray sCacheRGBA8;
 
 // An A8 target for standalone tasks that is available to all passes.
 uniform sampler2DArray sSharedCacheA8;
 
 uniform sampler2D sGradients;
 
@@ -36,274 +29,37 @@ vec2 clamp_rect(vec2 pt, RectWithSize re
 float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) {
     vec2 dir_to_p0 = p0 - p;
     return dot(normalize(perp_dir), dir_to_p0);
 }
 
 // TODO: convert back to RectWithEndPoint if driver issues are resolved, if ever.
 flat varying vec4 vClipMaskUvBounds;
 varying vec3 vClipMaskUv;
-flat varying vec4 vLocalBounds;
 
-// TODO(gw): This is here temporarily while we have
-//           both GPU store and cache. When the GPU
-//           store code is removed, we can change the
-//           PrimitiveInstance instance structure to
-//           use 2x unsigned shorts as vertex attributes
-//           instead of an int, and encode the UV directly
-//           in the vertices.
-ivec2 get_resource_cache_uv(int address) {
-    return ivec2(address % WR_MAX_VERTEX_TEXTURE_WIDTH,
-                 address / WR_MAX_VERTEX_TEXTURE_WIDTH);
-}
-
-uniform HIGHP_SAMPLER_FLOAT sampler2D sResourceCache;
-
-vec4[2] fetch_from_resource_cache_2_direct(ivec2 address) {
-    return vec4[2](
-        TEXEL_FETCH(sResourceCache, address, 0, ivec2(0, 0)),
-        TEXEL_FETCH(sResourceCache, address, 0, ivec2(1, 0))
-    );
-}
-
-vec4[2] fetch_from_resource_cache_2(int address) {
-    ivec2 uv = get_resource_cache_uv(address);
-    return vec4[2](
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0))
-    );
-}
 
 #ifdef WR_VERTEX_SHADER
 
-#define VECS_PER_CLIP_SCROLL_NODE   9
 #define VECS_PER_LOCAL_CLIP_RECT    1
-#define VECS_PER_RENDER_TASK        2
 #define VECS_PER_PRIM_HEADER        2
 #define VECS_PER_TEXT_RUN           3
 #define VECS_PER_GRADIENT_STOP      2
 
-uniform HIGHP_SAMPLER_FLOAT sampler2D sClipScrollNodes;
 uniform HIGHP_SAMPLER_FLOAT sampler2D sLocalClipRects;
-uniform HIGHP_SAMPLER_FLOAT sampler2D sRenderTasks;
 
 // Instanced attributes
 in ivec4 aData0;
 in ivec4 aData1;
 
-// get_fetch_uv is a macro to work around a macOS Intel driver parsing bug.
-// TODO: convert back to a function once the driver issues are resolved, if ever.
-// https://github.com/servo/webrender/pull/623
-// https://github.com/servo/servo/issues/13953
-// Do the division with unsigned ints because that's more efficient with D3D
-#define get_fetch_uv(i, vpi)  ivec2(int(uint(vpi) * (uint(i) % uint(WR_MAX_VERTEX_TEXTURE_WIDTH/vpi))), int(uint(i) / uint(WR_MAX_VERTEX_TEXTURE_WIDTH/vpi)))
-
-
-vec4[8] fetch_from_resource_cache_8(int address) {
-    ivec2 uv = get_resource_cache_uv(address);
-    return vec4[8](
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0)),
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(2, 0)),
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(3, 0)),
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(4, 0)),
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(5, 0)),
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(6, 0)),
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(7, 0))
-    );
-}
-
-vec4[3] fetch_from_resource_cache_3(int address) {
-    ivec2 uv = get_resource_cache_uv(address);
-    return vec4[3](
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0)),
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(2, 0))
-    );
-}
-
-vec4[3] fetch_from_resource_cache_3_direct(ivec2 address) {
-    return vec4[3](
-        TEXEL_FETCH(sResourceCache, address, 0, ivec2(0, 0)),
-        TEXEL_FETCH(sResourceCache, address, 0, ivec2(1, 0)),
-        TEXEL_FETCH(sResourceCache, address, 0, ivec2(2, 0))
-    );
-}
-
-vec4[4] fetch_from_resource_cache_4_direct(ivec2 address) {
-    return vec4[4](
-        TEXEL_FETCH(sResourceCache, address, 0, ivec2(0, 0)),
-        TEXEL_FETCH(sResourceCache, address, 0, ivec2(1, 0)),
-        TEXEL_FETCH(sResourceCache, address, 0, ivec2(2, 0)),
-        TEXEL_FETCH(sResourceCache, address, 0, ivec2(3, 0))
-    );
-}
-
-vec4[4] fetch_from_resource_cache_4(int address) {
-    ivec2 uv = get_resource_cache_uv(address);
-    return vec4[4](
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0)),
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(2, 0)),
-        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(3, 0))
-    );
-}
-
-vec4 fetch_from_resource_cache_1_direct(ivec2 address) {
-    return texelFetch(sResourceCache, address, 0);
-}
-
-vec4 fetch_from_resource_cache_1(int address) {
-    ivec2 uv = get_resource_cache_uv(address);
-    return texelFetch(sResourceCache, uv, 0);
-}
-
-struct ClipScrollNode {
-    mat4 transform;
-    mat4 inv_transform;
-    bool is_axis_aligned;
-};
-
-ClipScrollNode fetch_clip_scroll_node(int index) {
-    ClipScrollNode node;
-
-    // Create a UV base coord for each 8 texels.
-    // This is required because trying to use an offset
-    // of more than 8 texels doesn't work on some versions
-    // of OSX.
-    ivec2 uv = get_fetch_uv(index, VECS_PER_CLIP_SCROLL_NODE);
-    ivec2 uv0 = ivec2(uv.x + 0, uv.y);
-    ivec2 uv1 = ivec2(uv.x + 8, uv.y);
-
-    node.transform[0] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(0, 0));
-    node.transform[1] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(1, 0));
-    node.transform[2] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(2, 0));
-    node.transform[3] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(3, 0));
-
-    node.inv_transform[0] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(4, 0));
-    node.inv_transform[1] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(5, 0));
-    node.inv_transform[2] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(6, 0));
-    node.inv_transform[3] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(7, 0));
-
-    vec4 misc = TEXEL_FETCH(sClipScrollNodes, uv1, 0, ivec2(0, 0));
-    node.is_axis_aligned = misc.x == 0.0;
-
-    return node;
-}
-
 RectWithSize fetch_clip_chain_rect(int index) {
     ivec2 uv = get_fetch_uv(index, VECS_PER_LOCAL_CLIP_RECT);
     vec4 rect = TEXEL_FETCH(sLocalClipRects, uv, 0, ivec2(0, 0));
     return RectWithSize(rect.xy, rect.zw);
 }
 
-struct RenderTaskCommonData {
-    RectWithSize task_rect;
-    float texture_layer_index;
-};
-
-struct RenderTaskData {
-    RenderTaskCommonData common_data;
-    vec3 data1;
-};
-
-RenderTaskData fetch_render_task_data(int index) {
-    ivec2 uv = get_fetch_uv(index, VECS_PER_RENDER_TASK);
-
-    vec4 texel0 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(0, 0));
-    vec4 texel1 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(1, 0));
-
-    RectWithSize task_rect = RectWithSize(
-        texel0.xy,
-        texel0.zw
-    );
-
-    RenderTaskCommonData common_data = RenderTaskCommonData(
-        task_rect,
-        texel1.x
-    );
-
-    RenderTaskData data = RenderTaskData(
-        common_data,
-        texel1.yzw
-    );
-
-    return data;
-}
-
-RenderTaskCommonData fetch_render_task_common_data(int index) {
-    ivec2 uv = get_fetch_uv(index, VECS_PER_RENDER_TASK);
-
-    vec4 texel0 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(0, 0));
-    vec4 texel1 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(1, 0));
-
-    RectWithSize task_rect = RectWithSize(
-        texel0.xy,
-        texel0.zw
-    );
-
-    RenderTaskCommonData data = RenderTaskCommonData(
-        task_rect,
-        texel1.x
-    );
-
-    return data;
-}
-
-#define PIC_TYPE_IMAGE          1
-#define PIC_TYPE_TEXT_SHADOW    2
-
-/*
- The dynamic picture that this brush exists on. Right now, it
- contains minimal information. In the future, it will describe
- the transform mode of primitives on this picture, among other things.
- */
-struct PictureTask {
-    RenderTaskCommonData common_data;
-    vec2 content_origin;
-};
-
-PictureTask fetch_picture_task(int address) {
-    RenderTaskData task_data = fetch_render_task_data(address);
-
-    PictureTask task = PictureTask(
-        task_data.common_data,
-        task_data.data1.xy
-    );
-
-    return task;
-}
-
-struct ClipArea {
-    RenderTaskCommonData common_data;
-    vec2 screen_origin;
-    bool local_space;
-};
-
-ClipArea fetch_clip_area(int index) {
-    ClipArea area;
-
-    if (index == 0x7FFF) { //special sentinel task index
-        area.common_data = RenderTaskCommonData(
-            RectWithSize(vec2(0.0), vec2(0.0)),
-            0.0
-        );
-        area.screen_origin = vec2(0.0);
-        area.local_space = false;
-    } else {
-        RenderTaskData task_data = fetch_render_task_data(index);
-
-        area.common_data = task_data.common_data;
-        area.screen_origin = task_data.data1.xy;
-        area.local_space = task_data.data1.z == 0.0;
-    }
-
-    return area;
-}
-
 struct Glyph {
     vec2 offset;
 };
 
 Glyph fetch_glyph(int specific_prim_address,
                   int glyph_index,
                   int subpx_dir) {
     // Two glyphs are packed in each texel in the GPU cache.
@@ -437,87 +193,16 @@ Primitive load_primitive() {
     prim.user_data0 = pi.user_data0;
     prim.user_data1 = pi.user_data1;
     prim.user_data2 = pi.user_data2;
     prim.z = float(pi.z);
 
     return prim;
 }
 
-// Return the intersection of the plane (set up by "normal" and "point")
-// with the ray (set up by "ray_origin" and "ray_dir"),
-// writing the resulting scaler into "t".
-bool ray_plane(vec3 normal, vec3 pt, vec3 ray_origin, vec3 ray_dir, out float t)
-{
-    float denom = dot(normal, ray_dir);
-    if (abs(denom) > 1e-6) {
-        vec3 d = pt - ray_origin;
-        t = dot(d, normal) / denom;
-        return t >= 0.0;
-    }
-
-    return false;
-}
-
-// Apply the inverse transform "inv_transform"
-// to the reference point "ref" in CSS space,
-// producing a local point on a ClipScrollNode plane,
-// set by a base point "a" and a normal "n".
-vec4 untransform(vec2 ref, vec3 n, vec3 a, mat4 inv_transform) {
-    vec3 p = vec3(ref, -10000.0);
-    vec3 d = vec3(0, 0, 1.0);
-
-    float t = 0.0;
-    // get an intersection of the ClipScrollNode plane with Z axis vector,
-    // originated from the "ref" point
-    ray_plane(n, a, p, d, t);
-    float z = p.z + d.z * t; // Z of the visible point on the ClipScrollNode
-
-    vec4 r = inv_transform * vec4(ref, z, 1.0);
-    return r;
-}
-
-// Given a CSS space position, transform it back into the ClipScrollNode space.
-vec4 get_node_pos(vec2 pos, ClipScrollNode node) {
-    // get a point on the scroll node plane
-    vec4 ah = node.transform * vec4(0.0, 0.0, 0.0, 1.0);
-    vec3 a = ah.xyz / ah.w;
-
-    // get the normal to the scroll node plane
-    vec3 n = transpose(mat3(node.inv_transform)) * vec3(0.0, 0.0, 1.0);
-    return untransform(pos, n, a, node.inv_transform);
-}
-
-// Compute a snapping offset in world space (adjusted to pixel ratio),
-// given local position on the scroll_node and a snap rectangle.
-vec2 compute_snap_offset(vec2 local_pos,
-                         mat4 transform,
-                         RectWithSize snap_rect) {
-    // Ensure that the snap rect is at *least* one device pixel in size.
-    // TODO(gw): It's not clear to me that this is "correct". Specifically,
-    //           how should it interact with sub-pixel snap rects when there
-    //           is a scroll_node transform with scale present? But it does fix
-    //           the test cases we have in Servo that are failing without it
-    //           and seem better than not having this at all.
-    snap_rect.size = max(snap_rect.size, vec2(1.0 / uDevicePixelRatio));
-
-    // Transform the snap corners to the world space.
-    vec4 world_snap_p0 = transform * vec4(snap_rect.p0, 0.0, 1.0);
-    vec4 world_snap_p1 = transform * vec4(snap_rect.p0 + snap_rect.size, 0.0, 1.0);
-    // Snap bounds in world coordinates, adjusted for pixel ratio. XY = top left, ZW = bottom right
-    vec4 world_snap = uDevicePixelRatio * vec4(world_snap_p0.xy, world_snap_p1.xy) /
-                                          vec4(world_snap_p0.ww, world_snap_p1.ww);
-    /// World offsets applied to the corners of the snap rectangle.
-    vec4 snap_offsets = floor(world_snap + 0.5) - world_snap;
-
-    /// Compute the position of this vertex inside the snap rectangle.
-    vec2 normalized_snap_pos = (local_pos - snap_rect.p0) / snap_rect.size;
-    /// Compute the actual world offset for this vertex needed to make it snap.
-    return mix(snap_offsets.xy, snap_offsets.zw, normalized_snap_pos);
-}
 
 struct VertexInfo {
     vec2 local_pos;
     vec2 screen_pos;
     float w;
     vec2 snapped_device_pos;
 };
 
@@ -635,21 +320,21 @@ VertexInfo write_transform_vertex(RectWi
 
     // We want the world space coords to be perspective divided by W.
     // We also want that to apply to any interpolators. However, we
     // want a constant Z across the primitive, since we're using it
     // for draw ordering - so scale by the W coord to ensure this.
     vec4 final_pos = vec4(device_pos + task_offset, z, 1.0) * world_pos.w;
     gl_Position = uTransform * final_pos;
 
-    vLocalBounds = mix(
+    init_transform_vs(mix(
         vec4(prim_rect.p0, prim_rect.p1),
         vec4(segment_rect.p0, segment_rect.p1),
         clip_edge_mask
-    );
+    ));
 
     VertexInfo vi = VertexInfo(
         local_pos,
         device_pos,
         world_pos.w,
         device_pos
     );
 
@@ -676,35 +361,16 @@ struct GlyphResource {
     float scale;
 };
 
 GlyphResource fetch_glyph_resource(int address) {
     vec4 data[2] = fetch_from_resource_cache_2(address);
     return GlyphResource(data[0], data[1].x, data[1].yz, data[1].w);
 }
 
-struct ImageResource {
-    RectWithEndpoint uv_rect;
-    float layer;
-    vec3 user_data;
-};
-
-ImageResource fetch_image_resource(int address) {
-    //Note: number of blocks has to match `renderer::BLOCKS_PER_UV_RECT`
-    vec4 data[2] = fetch_from_resource_cache_2(address);
-    RectWithEndpoint uv_rect = RectWithEndpoint(data[0].xy, data[0].zw);
-    return ImageResource(uv_rect, data[1].x, data[1].yzw);
-}
-
-ImageResource fetch_image_resource_direct(ivec2 address) {
-    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
-    RectWithEndpoint uv_rect = RectWithEndpoint(data[0].xy, data[0].zw);
-    return ImageResource(uv_rect, data[1].x, data[1].yzw);
-}
-
 struct TextRun {
     vec4 color;
     vec4 bg_color;
     vec2 offset;
 };
 
 TextRun fetch_text_run(int address) {
     vec4 data[3] = fetch_from_resource_cache_3(address);
@@ -730,84 +396,16 @@ void write_clip(vec2 global_pos, ClipAre
         area.common_data.task_rect.p0 + area.common_data.task_rect.size
     );
     vClipMaskUv = vec3(uv, area.common_data.texture_layer_index);
 }
 #endif //WR_VERTEX_SHADER
 
 #ifdef WR_FRAGMENT_SHADER
 
-/// Find the appropriate half range to apply the AA approximation over.
-/// This range represents a coefficient to go from one CSS pixel to half a device pixel.
-float compute_aa_range(vec2 position) {
-    // The constant factor is chosen to compensate for the fact that length(fw) is equal
-    // to sqrt(2) times the device pixel ratio in the typical case. 0.5/sqrt(2) = 0.35355.
-    //
-    // This coefficient is chosen to ensure that any sample 0.5 pixels or more inside of
-    // the shape has no anti-aliasing applied to it (since pixels are sampled at their center,
-    // such a pixel (axis aligned) is fully inside the border). We need this so that antialiased
-    // curves properly connect with non-antialiased vertical or horizontal lines, among other things.
-    //
-    // Lines over a half-pixel away from the pixel center *can* intersect with the pixel square;
-    // indeed, unless they are horizontal or vertical, they are guaranteed to. However, choosing
-    // a nonzero area for such pixels causes noticeable artifacts at the junction between an anti-
-    // aliased corner and a straight edge.
-    //
-    // We may want to adjust this constant in specific scenarios (for example keep the principled
-    // value for straight edges where we want pixel-perfect equivalence with non antialiased lines
-    // when axis aligned, while selecting a larger and smoother aa range on curves).
-    return 0.35355 * length(fwidth(position));
-}
-
-/// Return the blending coefficient for distance antialiasing.
-///
-/// 0.0 means inside the shape, 1.0 means outside.
-///
-/// This cubic polynomial approximates the area of a 1x1 pixel square under a
-/// line, given the signed Euclidean distance from the center of the square to
-/// that line. Calculating the *exact* area would require taking into account
-/// not only this distance but also the angle of the line. However, in
-/// practice, this complexity is not required, as the area is roughly the same
-/// regardless of the angle.
-///
-/// The coefficients of this polynomial were determined through least-squares
-/// regression and are accurate to within 2.16% of the total area of the pixel
-/// square 95% of the time, with a maximum error of 3.53%.
-///
-/// See the comments in `compute_aa_range()` for more information on the
-/// cutoff values of -0.5 and 0.5.
-float distance_aa(float aa_range, float signed_distance) {
-    float dist = 0.5 * signed_distance / aa_range;
-    if (dist <= -0.5 + EPSILON)
-        return 1.0;
-    if (dist >= 0.5 - EPSILON)
-        return 0.0;
-    return 0.5 + dist * (0.8431027 * dist * dist - 1.14453603);
-}
-
-float signed_distance_rect(vec2 pos, vec2 p0, vec2 p1) {
-    vec2 d = max(p0 - pos, pos - p1);
-    return length(max(vec2(0.0), d)) + min(0.0, max(d.x, d.y));
-}
-
-float init_transform_fs(vec2 local_pos) {
-    // Get signed distance from local rect bounds.
-    float d = signed_distance_rect(
-        local_pos,
-        vLocalBounds.xy,
-        vLocalBounds.zw
-    );
-
-    // Find the appropriate distance to apply the AA smoothstep over.
-    float aa_range = compute_aa_range(local_pos);
-
-    // Only apply AA to fragments outside the signed distance field.
-    return distance_aa(aa_range, d);
-}
-
 float do_clip() {
     // anything outside of the mask is considered transparent
     bvec4 inside = lessThanEqual(
         vec4(vClipMaskUvBounds.xy, vClipMaskUv.xy),
         vec4(vClipMaskUv.xy, vClipMaskUvBounds.zw));
     // check for the dummy bounds, which are given to the opaque objects
     return vClipMaskUvBounds.xy == vClipMaskUvBounds.zw ? 1.0:
         all(inside) ? texelFetch(sCacheA8, ivec3(vClipMaskUv), 0).r : 0.0;
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/render_task.glsl
@@ -0,0 +1,115 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifdef WR_VERTEX_SHADER
+#define VECS_PER_RENDER_TASK        2
+
+uniform HIGHP_SAMPLER_FLOAT sampler2D sRenderTasks;
+
+struct RenderTaskCommonData {
+    RectWithSize task_rect;
+    float texture_layer_index;
+};
+
+struct RenderTaskData {
+    RenderTaskCommonData common_data;
+    vec3 data1;
+};
+
+RenderTaskData fetch_render_task_data(int index) {
+    ivec2 uv = get_fetch_uv(index, VECS_PER_RENDER_TASK);
+
+    vec4 texel0 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(0, 0));
+    vec4 texel1 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(1, 0));
+
+    RectWithSize task_rect = RectWithSize(
+        texel0.xy,
+        texel0.zw
+    );
+
+    RenderTaskCommonData common_data = RenderTaskCommonData(
+        task_rect,
+        texel1.x
+    );
+
+    RenderTaskData data = RenderTaskData(
+        common_data,
+        texel1.yzw
+    );
+
+    return data;
+}
+
+RenderTaskCommonData fetch_render_task_common_data(int index) {
+    ivec2 uv = get_fetch_uv(index, VECS_PER_RENDER_TASK);
+
+    vec4 texel0 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(0, 0));
+    vec4 texel1 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(1, 0));
+
+    RectWithSize task_rect = RectWithSize(
+        texel0.xy,
+        texel0.zw
+    );
+
+    RenderTaskCommonData data = RenderTaskCommonData(
+        task_rect,
+        texel1.x
+    );
+
+    return data;
+}
+
+#define PIC_TYPE_IMAGE          1
+#define PIC_TYPE_TEXT_SHADOW    2
+
+/*
+ The dynamic picture that this brush exists on. Right now, it
+ contains minimal information. In the future, it will describe
+ the transform mode of primitives on this picture, among other things.
+ */
+struct PictureTask {
+    RenderTaskCommonData common_data;
+    vec2 content_origin;
+};
+
+PictureTask fetch_picture_task(int address) {
+    RenderTaskData task_data = fetch_render_task_data(address);
+
+    PictureTask task = PictureTask(
+        task_data.common_data,
+        task_data.data1.xy
+    );
+
+    return task;
+}
+
+struct ClipArea {
+    RenderTaskCommonData common_data;
+    vec2 screen_origin;
+    bool local_space;
+};
+
+ClipArea fetch_clip_area(int index) {
+    ClipArea area;
+
+    if (index == 0x7FFF) { //special sentinel task index
+        area.common_data = RenderTaskCommonData(
+            RectWithSize(vec2(0.0), vec2(0.0)),
+            0.0
+        );
+        area.screen_origin = vec2(0.0);
+        area.local_space = false;
+    } else {
+        RenderTaskData task_data = fetch_render_task_data(index);
+
+        area.common_data = task_data.common_data;
+        area.screen_origin = task_data.data1.xy;
+        area.local_space = task_data.data1.z == 0.0;
+    }
+
+    return area;
+}
+
+#endif //WR_VERTEX_SHADER
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/resource_cache.glsl
@@ -0,0 +1,116 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+uniform HIGHP_SAMPLER_FLOAT sampler2D sResourceCache;
+
+// TODO(gw): This is here temporarily while we have
+//           both GPU store and cache. When the GPU
+//           store code is removed, we can change the
+//           PrimitiveInstance instance structure to
+//           use 2x unsigned shorts as vertex attributes
+//           instead of an int, and encode the UV directly
+//           in the vertices.
+ivec2 get_resource_cache_uv(int address) {
+    return ivec2(address % WR_MAX_VERTEX_TEXTURE_WIDTH,
+                 address / WR_MAX_VERTEX_TEXTURE_WIDTH);
+}
+
+vec4[2] fetch_from_resource_cache_2_direct(ivec2 address) {
+    return vec4[2](
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(0, 0)),
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(1, 0))
+    );
+}
+
+vec4[2] fetch_from_resource_cache_2(int address) {
+    ivec2 uv = get_resource_cache_uv(address);
+    return vec4[2](
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0))
+    );
+}
+
+#ifdef WR_VERTEX_SHADER
+
+vec4[8] fetch_from_resource_cache_8(int address) {
+    ivec2 uv = get_resource_cache_uv(address);
+    return vec4[8](
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(2, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(3, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(4, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(5, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(6, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(7, 0))
+    );
+}
+
+vec4[3] fetch_from_resource_cache_3(int address) {
+    ivec2 uv = get_resource_cache_uv(address);
+    return vec4[3](
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(2, 0))
+    );
+}
+
+vec4[3] fetch_from_resource_cache_3_direct(ivec2 address) {
+    return vec4[3](
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(0, 0)),
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(1, 0)),
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(2, 0))
+    );
+}
+
+vec4[4] fetch_from_resource_cache_4_direct(ivec2 address) {
+    return vec4[4](
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(0, 0)),
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(1, 0)),
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(2, 0)),
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(3, 0))
+    );
+}
+
+vec4[4] fetch_from_resource_cache_4(int address) {
+    ivec2 uv = get_resource_cache_uv(address);
+    return vec4[4](
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(2, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(3, 0))
+    );
+}
+
+vec4 fetch_from_resource_cache_1_direct(ivec2 address) {
+    return texelFetch(sResourceCache, address, 0);
+}
+
+vec4 fetch_from_resource_cache_1(int address) {
+    ivec2 uv = get_resource_cache_uv(address);
+    return texelFetch(sResourceCache, uv, 0);
+}
+
+//TODO: image resource is too specific for this module
+
+struct ImageResource {
+    RectWithEndpoint uv_rect;
+    float layer;
+    vec3 user_data;
+};
+
+ImageResource fetch_image_resource(int address) {
+    //Note: number of blocks has to match `renderer::BLOCKS_PER_UV_RECT`
+    vec4 data[2] = fetch_from_resource_cache_2(address);
+    RectWithEndpoint uv_rect = RectWithEndpoint(data[0].xy, data[0].zw);
+    return ImageResource(uv_rect, data[1].x, data[1].yzw);
+}
+
+ImageResource fetch_image_resource_direct(ivec2 address) {
+    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
+    RectWithEndpoint uv_rect = RectWithEndpoint(data[0].xy, data[0].zw);
+    return ImageResource(uv_rect, data[1].x, data[1].yzw);
+}
+
+#endif //WR_VERTEX_SHADER
--- a/gfx/webrender/res/shared.glsl
+++ b/gfx/webrender/res/shared.glsl
@@ -29,16 +29,23 @@
     uniform int uMode;
 
     // Uniform inputs
     uniform mat4 uTransform;       // Orthographic projection
     uniform float uDevicePixelRatio;
 
     // Attribute inputs
     in vec3 aPosition;
+
+    // get_fetch_uv is a macro to work around a macOS Intel driver parsing bug.
+    // TODO: convert back to a function once the driver issues are resolved, if ever.
+    // https://github.com/servo/webrender/pull/623
+    // https://github.com/servo/servo/issues/13953
+    // Do the division with unsigned ints because that's more efficient with D3D
+    #define get_fetch_uv(i, vpi)  ivec2(int(uint(vpi) * (uint(i) % uint(WR_MAX_VERTEX_TEXTURE_WIDTH/vpi))), int(uint(i) / uint(WR_MAX_VERTEX_TEXTURE_WIDTH/vpi)))
 #endif
 
 //======================================================================================
 // Fragment shader attributes and uniforms
 //======================================================================================
 #ifdef WR_FRAGMENT_SHADER
     // Uniform inputs
 
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/snap.glsl
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifdef WR_VERTEX_SHADER
+
+vec4 compute_snap_positions(mat4 transform, RectWithSize snap_rect) {
+    // Ensure that the snap rect is at *least* one device pixel in size.
+    // TODO(gw): It's not clear to me that this is "correct". Specifically,
+    //           how should it interact with sub-pixel snap rects when there
+    //           is a scroll_node transform with scale present? But it does fix
+    //           the test cases we have in Servo that are failing without it
+    //           and seem better than not having this at all.
+    snap_rect.size = max(snap_rect.size, vec2(1.0 / uDevicePixelRatio));
+
+    // Transform the snap corners to the world space.
+    vec4 world_snap_p0 = transform * vec4(snap_rect.p0, 0.0, 1.0);
+    vec4 world_snap_p1 = transform * vec4(snap_rect.p0 + snap_rect.size, 0.0, 1.0);
+    // Snap bounds in world coordinates, adjusted for pixel ratio. XY = top left, ZW = bottom right
+    vec4 world_snap = uDevicePixelRatio * vec4(world_snap_p0.xy, world_snap_p1.xy) /
+                                          vec4(world_snap_p0.ww, world_snap_p1.ww);
+    return world_snap;
+}
+
+vec2 compute_snap_offset_impl(
+    vec2 reference_pos,
+    mat4 transform,
+    RectWithSize snap_rect,
+    RectWithSize reference_rect,
+    vec4 snap_positions) {
+
+    /// World offsets applied to the corners of the snap rectangle.
+    vec4 snap_offsets = floor(snap_positions + 0.5) - snap_positions;
+
+    /// Compute the position of this vertex inside the snap rectangle.
+    vec2 normalized_snap_pos = (reference_pos - reference_rect.p0) / reference_rect.size;
+
+    /// Compute the actual world offset for this vertex needed to make it snap.
+    return mix(snap_offsets.xy, snap_offsets.zw, normalized_snap_pos);
+}
+
+// Compute a snapping offset in world space (adjusted to pixel ratio),
+// given local position on the scroll_node and a snap rectangle.
+vec2 compute_snap_offset(vec2 local_pos,
+                         mat4 transform,
+                         RectWithSize snap_rect) {
+    vec4 snap_positions = compute_snap_positions(
+        transform,
+        snap_rect
+    );
+
+    vec2 snap_offsets = compute_snap_offset_impl(
+        local_pos,
+        transform,
+        snap_rect,
+        snap_rect,
+        snap_positions
+    );
+
+    return snap_offsets;
+}
+
+#endif //WR_VERTEX_SHADER
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/transform.glsl
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define EPSILON     0.0001
+
+flat varying vec4 vTransformBounds;
+
+#ifdef WR_VERTEX_SHADER
+
+void init_transform_vs(vec4 local_bounds) {
+    vTransformBounds = local_bounds;
+}
+
+#endif //WR_VERTEX_SHADER
+
+#ifdef WR_FRAGMENT_SHADER
+
+/// Find the appropriate half range to apply the AA approximation over.
+/// This range represents a coefficient to go from one CSS pixel to half a device pixel.
+float compute_aa_range(vec2 position) {
+    // The constant factor is chosen to compensate for the fact that length(fw) is equal
+    // to sqrt(2) times the device pixel ratio in the typical case. 0.5/sqrt(2) = 0.35355.
+    //
+    // This coefficient is chosen to ensure that any sample 0.5 pixels or more inside of
+    // the shape has no anti-aliasing applied to it (since pixels are sampled at their center,
+    // such a pixel (axis aligned) is fully inside the border). We need this so that antialiased
+    // curves properly connect with non-antialiased vertical or horizontal lines, among other things.
+    //
+    // Lines over a half-pixel away from the pixel center *can* intersect with the pixel square;
+    // indeed, unless they are horizontal or vertical, they are guaranteed to. However, choosing
+    // a nonzero area for such pixels causes noticeable artifacts at the junction between an anti-
+    // aliased corner and a straight edge.
+    //
+    // We may want to adjust this constant in specific scenarios (for example keep the principled
+    // value for straight edges where we want pixel-perfect equivalence with non antialiased lines
+    // when axis aligned, while selecting a larger and smoother aa range on curves).
+    return 0.35355 * length(fwidth(position));
+}
+
+/// Return the blending coefficient for distance antialiasing.
+///
+/// 0.0 means inside the shape, 1.0 means outside.
+///
+/// This cubic polynomial approximates the area of a 1x1 pixel square under a
+/// line, given the signed Euclidean distance from the center of the square to
+/// that line. Calculating the *exact* area would require taking into account
+/// not only this distance but also the angle of the line. However, in
+/// practice, this complexity is not required, as the area is roughly the same
+/// regardless of the angle.
+///
+/// The coefficients of this polynomial were determined through least-squares
+/// regression and are accurate to within 2.16% of the total area of the pixel
+/// square 95% of the time, with a maximum error of 3.53%.
+///
+/// See the comments in `compute_aa_range()` for more information on the
+/// cutoff values of -0.5 and 0.5.
+float distance_aa(float aa_range, float signed_distance) {
+    float dist = 0.5 * signed_distance / aa_range;
+    if (dist <= -0.5 + EPSILON)
+        return 1.0;
+    if (dist >= 0.5 - EPSILON)
+        return 0.0;
+    return 0.5 + dist * (0.8431027 * dist * dist - 1.14453603);
+}
+
+float signed_distance_rect(vec2 pos, vec2 p0, vec2 p1) {
+    vec2 d = max(p0 - pos, pos - p1);
+    return length(max(vec2(0.0), d)) + min(0.0, max(d.x, d.y));
+}
+
+float init_transform_fs(vec2 local_pos) {
+    // Get signed distance from local rect bounds.
+    float d = signed_distance_rect(
+        local_pos,
+        vTransformBounds.xy,
+        vTransformBounds.zw
+    );
+
+    // Find the appropriate distance to apply the AA smoothstep over.
+    float aa_range = compute_aa_range(local_pos);
+
+    // Only apply AA to fragments outside the signed distance field.
+    return distance_aa(aa_range, d);
+}
+
+float init_transform_rough_fs(vec2 local_pos) {
+    return point_inside_rect(
+        local_pos,
+        vTransformBounds.xy,
+        vTransformBounds.zw
+    );
+}
+
+#endif //WR_FRAGMENT_SHADER
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{AlphaType, DeviceIntRect, DeviceIntSize};
+use api::{AlphaType, ClipMode, DeviceIntRect, DeviceIntSize};
 use api::{DeviceUintRect, DeviceUintPoint, DeviceUintSize, ExternalImageType, FilterOp, ImageRendering, LayerRect};
 use api::{DeviceIntPoint, SubpixelDirection, YuvColorSpace, YuvFormat};
 use api::{LayerToWorldTransform, WorldPixel};
 use border::{BorderCornerInstance, BorderCornerSide, BorderEdgeKind};
 use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::{CoordinateSystemId};
 use euclid::{TypedTransform3D, vec3};
 use glyph_rasterizer::GlyphFormat;
@@ -235,17 +235,17 @@ impl OpaqueBatchList {
         let mut selected_batch_index = None;
         let item_area = task_relative_bounding_rect.size.to_f32().area();
 
         // If the area of this primitive is larger than the given threshold,
         // then it is large enough to warrant breaking a batch for. In this
         // case we just see if it can be added to the existing batch or
         // create a new one.
         if item_area > self.pixel_area_threshold_for_new_batch {
-            if let Some(ref batch) = self.batches.last() {
+            if let Some(batch) = self.batches.last() {
                 if batch.key.is_compatible_with(&key) {
                     selected_batch_index = Some(self.batches.len() - 1);
                 }
             }
         } else {
             // Otherwise, look back through a reasonable number of batches.
             for (batch_index, batch) in self.batches.iter().enumerate().rev().take(10) {
                 if batch.key.is_compatible_with(&key) {
@@ -650,17 +650,17 @@ impl AlphaBatchBuilder {
                             debug_assert!(picture.surface.is_some());
 
                             let real_xf = &ctx.clip_scroll_tree
                                 .nodes[picture.reference_frame_index.0]
                                 .world_content_transform
                                 .into();
                             let polygon = make_polygon(
                                 picture.real_local_rect,
-                                &real_xf,
+                                real_xf,
                                 prim_index.0,
                             );
 
                             splitter.add(polygon);
 
                             return;
                         }
 
@@ -879,26 +879,30 @@ impl AlphaBatchBuilder {
                                         source_task_address.0 as i32,
                                     ],
                                 };
 
                                 batch.push(PrimitiveInstance::from(instance));
                                 false
                             }
                             Some(PictureCompositeMode::Blit) => {
-                                let cache_task_id = picture.surface.expect("bug: no surface allocated");
+                                let cache_task_id =
+                                    picture.surface.expect("bug: no surface allocated");
                                 let kind = BatchKind::Brush(
                                     BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
                                 );
                                 let key = BatchKey::new(
                                     kind,
                                     non_segmented_blend_mode,
                                     BatchTextures::render_target_cache(),
                                 );
-                                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
+                                let batch = self.batch_list.get_suitable_batch(
+                                    key,
+                                    &task_relative_bounding_rect
+                                );
 
                                 let uv_rect_address = render_tasks[cache_task_id]
                                     .get_texture_handle()
                                     .as_int(gpu_cache);
 
                                 let instance = BrushInstance {
                                     picture_address: task_address,
                                     prim_address: prim_cache_address,
@@ -938,17 +942,17 @@ impl AlphaBatchBuilder {
                             );
                         }
                     }
                     _ => {
                         if let Some((batch_kind, textures, user_data)) = brush.get_batch_params(
                                 ctx.resource_cache,
                                 gpu_cache,
                                 deferred_resolves,
-                                &ctx.cached_gradients,
+                                ctx.cached_gradients,
                         ) {
                             self.add_brush_to_batch(
                                 brush,
                                 prim_metadata,
                                 batch_kind,
                                 specified_blend_mode,
                                 non_segmented_blend_mode,
                                 textures,
@@ -1249,23 +1253,31 @@ impl BrushPrimitive {
     fn get_batch_params(
         &self,
         resource_cache: &ResourceCache,
         gpu_cache: &mut GpuCache,
         deferred_resolves: &mut Vec<DeferredResolve>,
         cached_gradients: &[CachedGradient],
     ) -> Option<(BrushBatchKind, BatchTextures, [i32; 3])> {
         match self.kind {
-            BrushKind::Image { request, .. } => {
-                let cache_item = resolve_image(
-                    request,
-                    resource_cache,
-                    gpu_cache,
-                    deferred_resolves,
-                );
+            BrushKind::Image { request, ref source, .. } => {
+
+                let cache_item = match *source {
+                    ImageSource::Default => {
+                        resolve_image(
+                            request,
+                            resource_cache,
+                            gpu_cache,
+                            deferred_resolves,
+                        )
+                    }
+                    ImageSource::Cache { ref item, .. } => {
+                        item.clone()
+                    }
+                };
 
                 if cache_item.texture_id == SourceTexture::Invalid {
                     None
                 } else {
                     let textures = BatchTextures::color(cache_item.texture_id);
 
                     Some((
                         BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
@@ -1610,18 +1622,19 @@ impl ClipBatcher {
                             .entry(info.cache_item.texture_id)
                             .or_insert(Vec::new())
                             .push(ClipMaskInstance {
                                 clip_data_address: gpu_address,
                                 resource_address: gpu_cache.get_address(&info.cache_item.uv_rect_handle),
                                 ..instance
                             });
                     }
-                    ClipSource::Rectangle(..) => {
-                        if work_item.coordinate_system_id != coordinate_system_id {
+                    ClipSource::Rectangle(_, mode) => {
+                        if work_item.coordinate_system_id != coordinate_system_id ||
+                           mode == ClipMode::ClipOut {
                             self.rectangles.push(ClipMaskInstance {
                                 clip_data_address: gpu_address,
                                 ..instance
                             });
                             coordinate_system_id = work_item.coordinate_system_id;
                         }
                     }
                     ClipSource::RoundedRectangle(..) => {
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ColorF, LayerPoint};
+use api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ClipMode, ColorF, LayerPoint};
 use api::{LayerPrimitiveInfo, LayerRect, LayerSize, NormalBorder, RepeatMode, TexelRect};
 use clip::ClipSource;
 use ellipse::Ellipse;
 use display_list_flattener::DisplayListFlattener;
 use gpu_cache::GpuDataRequest;
 use prim_store::{BorderPrimitiveCpu, BrushClipMaskKind, BrushSegment, BrushSegmentDescriptor};
 use prim_store::{EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
 use util::{lerp, pack_as_float};
@@ -368,16 +368,73 @@ impl<'a> DisplayListFlattener<'a> {
         ensure_no_corner_overlap(&mut border.radius, &info.rect);
 
         let radius = &border.radius;
         let left = &border.left;
         let right = &border.right;
         let top = &border.top;
         let bottom = &border.bottom;
 
+        let constant_color = left.color;
+        let is_simple_border = [left, top, right, bottom].iter().all(|edge| {
+            edge.style == BorderStyle::Solid &&
+            edge.color == constant_color
+        });
+
+        if is_simple_border {
+            let extra_clips = vec![
+                ClipSource::new_rounded_rect(
+                    info.rect,
+                    border.radius,
+                    ClipMode::Clip,
+                ),
+                ClipSource::new_rounded_rect(
+                    LayerRect::new(
+                        LayerPoint::new(
+                            info.rect.origin.x + widths.left,
+                            info.rect.origin.y + widths.top,
+                        ),
+                        LayerSize::new(
+                            info.rect.size.width - widths.left - widths.right,
+                            info.rect.size.height - widths.top - widths.bottom,
+                        ),
+                    ),
+                    BorderRadius {
+                        top_left: LayerSize::new(
+                            (border.radius.top_left.width - widths.left).max(0.0),
+                            (border.radius.top_left.height - widths.top).max(0.0),
+                        ),
+                        top_right: LayerSize::new(
+                            (border.radius.top_right.width - widths.right).max(0.0),
+                            (border.radius.top_right.height - widths.top).max(0.0),
+                        ),
+                        bottom_left: LayerSize::new(
+                            (border.radius.bottom_left.width - widths.left).max(0.0),
+                            (border.radius.bottom_left.height - widths.bottom).max(0.0),
+                        ),
+                        bottom_right: LayerSize::new(
+                            (border.radius.bottom_right.width - widths.right).max(0.0),
+                            (border.radius.bottom_right.height - widths.bottom).max(0.0),
+                        ),
+                    },
+                    ClipMode::ClipOut,
+                ),
+            ];
+
+            self.add_solid_rectangle(
+                clip_and_scroll,
+                info,
+                border.top.color,
+                None,
+                extra_clips,
+            );
+
+            return;
+        }
+
         let corners = [
             border.get_corner(
                 left,
                 widths.left,
                 top,
                 widths.top,
                 &radius.top_left,
                 BorderCorner::TopLeft,
@@ -457,69 +514,73 @@ impl<'a> DisplayListFlattener<'a> {
                         segment(p2.x, p0.y, p3.x, p1.y),
                         segment(p1.x, p0.y, p2.x, p1.y),
                     ],
                     clip_mask_kind: BrushClipMaskKind::Unknown,
                 };
 
                 self.add_solid_rectangle(
                     clip_and_scroll,
-                    &info,
+                    info,
                     border.top.color,
                     Some(descriptor),
+                    Vec::new(),
                 );
             }
 
             if left_edge == BorderEdgeKind::Solid {
                 let descriptor = BrushSegmentDescriptor {
                     segments: vec![
                         segment(p0.x, p1.y, p1.x, p2.y),
                     ],
                     clip_mask_kind: BrushClipMaskKind::Unknown,
                 };
 
                 self.add_solid_rectangle(
                     clip_and_scroll,
-                    &info,
+                    info,
                     border.left.color,
                     Some(descriptor),
+                    Vec::new(),
                 );
             }
 
             if right_edge == BorderEdgeKind::Solid {
                 let descriptor = BrushSegmentDescriptor {
                     segments: vec![
                         segment(p2.x, p1.y, p3.x, p2.y),
                     ],
                     clip_mask_kind: BrushClipMaskKind::Unknown,
                 };
 
                 self.add_solid_rectangle(
                     clip_and_scroll,
-                    &info,
+                    info,
                     border.right.color,
                     Some(descriptor),
+                    Vec::new(),
                 );
             }
 
             if bottom_edge == BorderEdgeKind::Solid {
                 let descriptor = BrushSegmentDescriptor {
                     segments: vec![
                         segment(p1.x, p2.y, p2.x, p3.y),
                         segment(p2.x, p2.y, p3.x, p3.y),
                         segment(p0.x, p2.y, p1.x, p3.y),
                     ],
                     clip_mask_kind: BrushClipMaskKind::Unknown,
                 };
 
                 self.add_solid_rectangle(
                     clip_and_scroll,
-                    &info,
+                    info,
                     border.bottom.color,
                     Some(descriptor),
+                    Vec::new(),
                 );
             }
         } else {
             // Create clip masks for border corners, if required.
             let mut extra_clips = Vec::new();
             let mut corner_instances = [BorderCornerInstance::Single; 4];
 
             for (i, corner) in corners.iter().enumerate() {
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -61,32 +61,32 @@ impl ClipRegion {
             complex_clips,
         }
     }
 
     pub fn create_for_clip_node_with_local_clip(
         local_clip: &LocalClip,
         reference_frame_relative_offset: &LayoutVector2D
     ) -> ClipRegion {
-        let complex_clips = match local_clip {
-            &LocalClip::Rect(_) => Vec::new(),
-            &LocalClip::RoundedRect(_, ref region) => vec![region.clone()],
+        let complex_clips = match *local_clip {
+            LocalClip::Rect(_) => Vec::new(),
+            LocalClip::RoundedRect(_, ref region) => vec![region.clone()],
         };
         ClipRegion::create_for_clip_node(
             *local_clip.clip_rect(),
             complex_clips,
             None,
             reference_frame_relative_offset
         )
     }
 }
 
 #[derive(Debug)]
 pub enum ClipSource {
-    Rectangle(LayerRect),
+    Rectangle(LayerRect, ClipMode),
     RoundedRectangle(LayerRect, BorderRadius, ClipMode),
     Image(ImageMask),
     /// TODO(gw): This currently only handles dashed style
     /// clips, where the border style is dashed for both
     /// adjacent border edges. Expand to handle dotted style
     /// and different styles per edge.
     BorderCorner(BorderCornerClipSource),
     BoxShadow(BoxShadowClipSource),
@@ -96,17 +96,17 @@ pub enum ClipSource {
 impl From<ClipRegion> for ClipSources {
     fn from(region: ClipRegion) -> ClipSources {
         let mut clips = Vec::new();
 
         if let Some(info) = region.image_mask {
             clips.push(ClipSource::Image(info));
         }
 
-        clips.push(ClipSource::Rectangle(region.main));
+        clips.push(ClipSource::Rectangle(region.main, ClipMode::Clip));
 
         for complex in region.complex_clips {
             clips.push(ClipSource::new_rounded_rect(
                 complex.rect,
                 complex.radii,
                 complex.mode,
             ));
         }
@@ -116,22 +116,26 @@ impl From<ClipRegion> for ClipSources {
 }
 
 impl ClipSource {
     pub fn new_rounded_rect(
         rect: LayerRect,
         mut radii: BorderRadius,
         clip_mode: ClipMode
     ) -> ClipSource {
-        ensure_no_corner_overlap(&mut radii, &rect);
-        ClipSource::RoundedRectangle(
-            rect,
-            radii,
-            clip_mode,
-        )
+        if radii.is_zero() {
+            ClipSource::Rectangle(rect, clip_mode)
+        } else {
+            ensure_no_corner_overlap(&mut radii, &rect);
+            ClipSource::RoundedRectangle(
+                rect,
+                radii,
+                clip_mode,
+            )
+        }
     }
 
     pub fn new_line_decoration(
         rect: LayerRect,
         style: LineStyle,
         orientation: LineOrientation,
         wavy_line_thickness: f32,
     ) -> ClipSource {
@@ -301,17 +305,24 @@ impl ClipSources {
             match *source {
                 ClipSource::Image(ref mask) => {
                     if !mask.repeat {
                         can_calculate_outer_rect = true;
                         local_outer = local_outer.and_then(|r| r.intersection(&mask.rect));
                     }
                     local_inner = None;
                 }
-                ClipSource::Rectangle(rect) => {
+                ClipSource::Rectangle(rect, mode) => {
+                    // Once we encounter a clip-out, we just assume the worst
+                    // case clip mask size, for now.
+                    if mode == ClipMode::ClipOut {
+                        can_calculate_inner_rect = false;
+                        break;
+                    }
+
                     can_calculate_outer_rect = true;
                     local_outer = local_outer.and_then(|r| r.intersection(&rect));
                     local_inner = local_inner.and_then(|r| r.intersection(&rect));
                 }
                 ClipSource::RoundedRectangle(ref rect, ref radius, mode) => {
                     // Once we encounter a clip-out, we just assume the worst
                     // case clip mask size, for now.
                     if mode == ClipMode::ClipOut {
@@ -330,24 +341,26 @@ impl ClipSources {
                 ClipSource::BorderCorner { .. } |
                 ClipSource::LineDecoration(..) => {
                     can_calculate_inner_rect = false;
                     break;
                 }
             }
         }
 
-        let outer = match can_calculate_outer_rect {
-            true => Some(local_outer.unwrap_or_else(LayerRect::zero)),
-            false => None,
+        let outer = if can_calculate_outer_rect {
+            Some(local_outer.unwrap_or_else(LayerRect::zero))
+        } else {
+            None
         };
 
-        let inner = match can_calculate_inner_rect {
-            true => local_inner.unwrap_or_else(LayerRect::zero),
-            false => LayerRect::zero(),
+        let inner = if can_calculate_inner_rect {
+            local_inner.unwrap_or_else(LayerRect::zero)
+        } else {
+            LayerRect::zero()
         };
 
         (inner, outer)
     }
 
     pub fn update(
         &mut self,
         gpu_cache: &mut GpuCache,
@@ -371,18 +384,18 @@ impl ClipSources {
                         request.push([
                             info.stretch_mode_x as i32 as f32,
                             info.stretch_mode_y as i32 as f32,
                             0.0,
                             0.0,
                         ]);
                         request.push(info.prim_shadow_rect);
                     }
-                    ClipSource::Rectangle(rect) => {
-                        let data = ClipData::uniform(rect, 0.0, ClipMode::Clip);
+                    ClipSource::Rectangle(rect, mode) => {
+                        let data = ClipData::uniform(rect, 0.0, mode);
                         data.write(&mut request);
                     }
                     ClipSource::RoundedRectangle(ref rect, ref radius, mode) => {
                         let data = ClipData::rounded_rect(rect, radius, mode);
                         data.write(&mut request);
                     }
                     ClipSource::BorderCorner(ref mut source) => {
                         source.write(request);
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,35 +1,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{DevicePixelScale, ExternalScrollId, LayerPixel, LayerPoint, LayerRect, LayerSize};
 use api::{LayerVector2D, LayoutTransform, LayoutVector2D, PipelineId, PropertyBinding};
-use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollSensitivity, StickyOffsetBounds};
+use api::{ScrollClamping, ScrollLocation, ScrollSensitivity, StickyOffsetBounds};
 use api::WorldPoint;
 use clip::{ClipChain, ClipChainNode, ClipSourcesHandle, ClipStore, ClipWorkItem};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
 use clip_scroll_tree::TransformUpdateState;
 use euclid::SideOffsets2D;
 use geometry::ray_intersects_rect;
 use gpu_cache::GpuCache;
 use gpu_types::{ClipScrollNodeIndex as GPUClipScrollNodeIndex, ClipScrollNodeData};
 use resource_cache::ResourceCache;
 use scene::SceneProperties;
-use spring::{DAMPING, STIFFNESS, Spring};
 use util::{LayerToWorldFastTransform, LayerFastTransform, LayoutFastTransform};
 use util::{TransformedRectKind};
 
-#[cfg(target_os = "macos")]
-const CAN_OVERSCROLL: bool = true;
-
-#[cfg(not(target_os = "macos"))]
-const CAN_OVERSCROLL: bool = false;
-
 #[derive(Debug)]
 pub struct StickyFrameInfo {
     pub margins: SideOffsets2D<Option<f32>>,
     pub vertical_offset_bounds: StickyOffsetBounds,
     pub horizontal_offset_bounds: StickyOffsetBounds,
     pub previously_applied_offset: LayoutVector2D,
     pub current_offset: LayerVector2D,
 }
@@ -264,18 +257,16 @@ impl ClipScrollNode {
             ScrollClamping::NoClamping => LayerPoint::zero() - *origin,
         };
 
         if new_offset == scrolling.offset {
             return false;
         }
 
         scrolling.offset = new_offset;
-        scrolling.bouncing_back = false;
-        scrolling.started_bouncing_back = false;
         true
     }
 
     pub fn mark_uninvertible(&mut self) {
         self.invertible = false;
         self.world_content_transform = LayerToWorldFastTransform::identity();
         self.world_viewport_transform = LayerToWorldFastTransform::identity();
     }
@@ -661,27 +652,23 @@ impl ClipScrollNode {
     pub fn scrollable_size(&self) -> LayerSize {
         match self.node_type {
            NodeType:: ScrollFrame(state) => state.scrollable_size,
             _ => LayerSize::zero(),
         }
     }
 
 
-    pub fn scroll(&mut self, scroll_location: ScrollLocation, phase: ScrollEventPhase) -> bool {
+    pub fn scroll(&mut self, scroll_location: ScrollLocation) -> bool {
         let scrolling = match self.node_type {
             NodeType::ScrollFrame(ref mut scrolling) => scrolling,
             _ => return false,
         };
 
-        if scrolling.started_bouncing_back && phase == ScrollEventPhase::Move(false) {
-            return false;
-        }
-
-        let mut delta = match scroll_location {
+        let delta = match scroll_location {
             ScrollLocation::Delta(delta) => delta,
             ScrollLocation::Start => {
                 if scrolling.offset.y.round() >= 0.0 {
                     // Nothing to do on this layer.
                     return false;
                 }
 
                 scrolling.offset.y = 0.0;
@@ -694,66 +681,35 @@ impl ClipScrollNode {
                     return false;
                 }
 
                 scrolling.offset.y = end_pos;
                 return true;
             }
         };
 
-        let overscroll_amount = scrolling.overscroll_amount();
-        let overscrolling = CAN_OVERSCROLL && (overscroll_amount != LayerVector2D::zero());
-        if overscrolling {
-            if overscroll_amount.x != 0.0 {
-                delta.x /= overscroll_amount.x.abs()
-            }
-            if overscroll_amount.y != 0.0 {
-                delta.y /= overscroll_amount.y.abs()
-            }
-        }
-
         let scrollable_width = scrolling.scrollable_size.width;
         let scrollable_height = scrolling.scrollable_size.height;
-        let is_unscrollable = scrollable_width <= 0. && scrollable_height <= 0.;
         let original_layer_scroll_offset = scrolling.offset;
 
         if scrollable_width > 0. {
-            scrolling.offset.x = scrolling.offset.x + delta.x;
-            if is_unscrollable || !CAN_OVERSCROLL {
-                scrolling.offset.x = scrolling.offset.x.min(0.0).max(-scrollable_width).round();
-            }
+            scrolling.offset.x = (scrolling.offset.x + delta.x)
+                .min(0.0)
+                .max(-scrollable_width)
+                .round();
         }
 
         if scrollable_height > 0. {
-            scrolling.offset.y = scrolling.offset.y + delta.y;
-            if is_unscrollable || !CAN_OVERSCROLL {
-                scrolling.offset.y = scrolling.offset.y.min(0.0).max(-scrollable_height).round();
-            }
+            scrolling.offset.y = (scrolling.offset.y + delta.y)
+                .min(0.0)
+                .max(-scrollable_height)
+                .round();
         }
 
-        if phase == ScrollEventPhase::Start || phase == ScrollEventPhase::Move(true) {
-            scrolling.started_bouncing_back = false
-        } else if overscrolling &&
-            ((delta.x < 1.0 && delta.y < 1.0) || phase == ScrollEventPhase::End)
-        {
-            scrolling.started_bouncing_back = true;
-            scrolling.bouncing_back = true
-        }
-
-        if CAN_OVERSCROLL {
-            scrolling.stretch_overscroll_spring(overscroll_amount);
-        }
-
-        scrolling.offset != original_layer_scroll_offset || scrolling.started_bouncing_back
-    }
-
-    pub fn tick_scrolling_bounce_animation(&mut self) {
-        if let NodeType::ScrollFrame(ref mut scrolling) = self.node_type {
-            scrolling.tick_scrolling_bounce_animation();
-        }
+        scrolling.offset != original_layer_scroll_offset
     }
 
     pub fn ray_intersects_node(&self, cursor: &WorldPoint) -> bool {
         let inv = match self.world_viewport_transform.inverse() {
             Some(inv) => inv,
             None => return false,
         };
 
@@ -776,109 +732,60 @@ impl ClipScrollNode {
 
     pub fn scroll_offset(&self) -> LayerVector2D {
         match self.node_type {
             NodeType::ScrollFrame(ref scrolling) => scrolling.offset,
             _ => LayerVector2D::zero(),
         }
     }
 
-    pub fn is_overscrolling(&self) -> bool {
-        match self.node_type {
-            NodeType::ScrollFrame(ref state) => state.overscroll_amount() != LayerVector2D::zero(),
-            _ => false,
-        }
-    }
-
     pub fn matches_external_id(&self, external_id: ExternalScrollId) -> bool {
         match self.node_type {
             NodeType::ScrollFrame(info) if info.external_id == Some(external_id) => true,
             _ => false,
         }
     }
 }
 
 #[derive(Copy, Clone, Debug)]
 pub struct ScrollFrameInfo {
     pub offset: LayerVector2D,
-    pub spring: Spring,
-    pub started_bouncing_back: bool,
-    pub bouncing_back: bool,
-    pub should_handoff_scroll: bool,
     pub scroll_sensitivity: ScrollSensitivity,
 
     /// Amount that this ScrollFrame can scroll in both directions.
     pub scrollable_size: LayerSize,
 
     /// An external id to identify this scroll frame to API clients. This
     /// allows setting scroll positions via the API without relying on ClipsIds
     /// which may change between frames.
     pub external_id: Option<ExternalScrollId>,
 
 }
 
-/// Manages scrolling offset, overscroll state, etc.
+/// Manages scrolling offset.
 impl ScrollFrameInfo {
     pub fn new(
         scroll_sensitivity: ScrollSensitivity,
         scrollable_size: LayerSize,
         external_id: Option<ExternalScrollId>,
     ) -> ScrollFrameInfo {
         ScrollFrameInfo {
             offset: LayerVector2D::zero(),
-            spring: Spring::at(LayerPoint::zero(), STIFFNESS, DAMPING),
-            started_bouncing_back: false,
-            bouncing_back: false,
-            should_handoff_scroll: false,
             scroll_sensitivity,
             scrollable_size,
             external_id,
         }
     }
 
     pub fn sensitive_to_input_events(&self) -> bool {
         match self.scroll_sensitivity {
             ScrollSensitivity::ScriptAndInputEvents => true,
             ScrollSensitivity::Script => false,
         }
     }
-
-    pub fn stretch_overscroll_spring(&mut self, overscroll_amount: LayerVector2D) {
-        let offset = self.offset.to_point();
-        self.spring
-            .coords(offset, offset, offset + overscroll_amount);
-    }
-
-    pub fn tick_scrolling_bounce_animation(&mut self) {
-        let finished = self.spring.animate();
-        self.offset = self.spring.current().to_vector();
-        if finished {
-            self.bouncing_back = false
-        }
-    }
-
-    pub fn overscroll_amount(&self) -> LayerVector2D {
-        let overscroll_x = if self.offset.x > 0.0 {
-            -self.offset.x
-        } else if self.offset.x < -self.scrollable_size.width {
-            -self.scrollable_size.width - self.offset.x
-        } else {
-            0.0
-        };
-
-        let overscroll_y = if self.offset.y > 0.0 {
-            -self.offset.y
-        } else if self.offset.y < -self.scrollable_size.height {
-            -self.scrollable_size.height - self.offset.y
-        } else {
-            0.0
-        };
-
-        LayerVector2D::new(overscroll_x, overscroll_y)
-    }
 }
 
 /// Contains information about reference frames.
 #[derive(Copy, Clone, Debug)]
 pub struct ReferenceFrameInfo {
     /// The transformation that establishes this reference frame, relative to the parent
     /// reference frame. The origin of the reference frame is included in the transformation.
     pub resolved_transform: LayerFastTransform,
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{DeviceIntRect, DevicePixelScale, ExternalScrollId, LayerPoint, LayerRect, LayerVector2D};
-use api::{PipelineId, ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollNodeState};
+use api::{PipelineId, ScrollClamping, ScrollLocation, ScrollNodeState};
 use api::WorldPoint;
 use clip::{ClipChain, ClipSourcesHandle, ClipStore};
 use clip_scroll_node::{ClipScrollNode, NodeType, ScrollFrameInfo, StickyFrameInfo};
 use gpu_cache::GpuCache;
 use gpu_types::{ClipScrollNodeIndex as GPUClipScrollNodeIndex, ClipScrollNodeData};
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
 use resource_cache::ResourceCache;
@@ -65,20 +65,16 @@ pub struct ClipScrollTree {
     pub clip_chains_descriptors: Vec<ClipChainDescriptor>,
 
     /// A vector of all ClipChains in this ClipScrollTree including those from
     /// ClipChainDescriptors and also those defined by the clipping node hierarchy.
     pub clip_chains: Vec<ClipChain>,
 
     pub pending_scroll_offsets: FastHashMap<ExternalScrollId, (LayerPoint, ScrollClamping)>,
 
-    /// The ClipId of the currently scrolling node. Used to allow the same
-    /// node to scroll even if a touch operation leaves the boundaries of that node.
-    pub currently_scrolling_node_index: Option<ClipScrollNodeIndex>,
-
     /// The current frame id, used for giving a unique id to all new dynamically
     /// added frames and clips. The ClipScrollTree increments this by one every
     /// time a new dynamic frame is created.
     current_new_node_item: u64,
 
     /// A set of pipelines which should be discarded the next time this
     /// tree is drained.
     pub pipelines_to_discard: FastHashSet<PipelineId>,
@@ -111,17 +107,16 @@ pub struct TransformUpdateState {
 
 impl ClipScrollTree {
     pub fn new() -> Self {
         ClipScrollTree {
             nodes: Vec::new(),
             clip_chains_descriptors: Vec::new(),
             clip_chains: vec![ClipChain::empty(&DeviceIntRect::zero())],
             pending_scroll_offsets: FastHashMap::default(),
-            currently_scrolling_node_index: None,
             current_new_node_item: 1,
             pipelines_to_discard: FastHashSet::default(),
         }
     }
 
     /// The root reference frame, which is the true root of the ClipScrollTree. Initially
     /// this ID is not valid, which is indicated by ```nodes``` being empty.
     pub fn root_reference_frame_index(&self) -> ClipScrollNodeIndex {
@@ -133,28 +128,16 @@ impl ClipScrollTree {
     /// The root scroll node which is the first child of the root reference frame.
     /// Initially this ID is not valid, which is indicated by ```nodes``` being empty.
     pub fn topmost_scroll_node_index(&self) -> ClipScrollNodeIndex {
         // TODO(mrobinson): We should eventually make this impossible to misuse.
         debug_assert!(self.nodes.len() >= 1);
         TOPMOST_SCROLL_NODE_INDEX
     }
 
-    pub fn collect_nodes_bouncing_back(&self) -> FastHashSet<ClipScrollNodeIndex> {
-        let mut nodes_bouncing_back = FastHashSet::default();
-        for (index, node) in self.nodes.iter().enumerate() {
-            if let NodeType::ScrollFrame(ref scrolling) = node.node_type {
-                if scrolling.bouncing_back {
-                    nodes_bouncing_back.insert(ClipScrollNodeIndex(index));
-                }
-            }
-        }
-        nodes_bouncing_back
-    }
-
     fn find_scrolling_node_at_point_in_node(
         &self,
         cursor: &WorldPoint,
         index: ClipScrollNodeIndex,
     ) -> Option<ClipScrollNodeIndex> {
         let node = &self.nodes[index.0];
         for child_index in node.children.iter().rev() {
             let found_index = self.find_scrolling_node_at_point_in_node(cursor, *child_index);
@@ -230,82 +213,23 @@ impl ClipScrollTree {
         self.pending_scroll_offsets.insert(id, (origin, clamp));
         false
     }
 
     pub fn scroll(
         &mut self,
         scroll_location: ScrollLocation,
         cursor: WorldPoint,
-        phase: ScrollEventPhase,
     ) -> bool {
         if self.nodes.is_empty() {
             return false;
         }
 
-        let node_index = match (
-            phase,
-            self.find_scrolling_node_at_point(&cursor),
-            self.currently_scrolling_node_index,
-        ) {
-            (ScrollEventPhase::Start, scroll_node_at_point_index, _) => {
-                self.currently_scrolling_node_index = Some(scroll_node_at_point_index);
-                scroll_node_at_point_index
-            }
-            (_, scroll_node_at_point_index, Some(cached_node_index)) => {
-                let node_index = match self.nodes.get(cached_node_index.0) {
-                    Some(_) => cached_node_index,
-                    None => {
-                        self.currently_scrolling_node_index = Some(scroll_node_at_point_index);
-                        scroll_node_at_point_index
-                    }
-                };
-                node_index
-            }
-            (_, _, None) => return false,
-        };
-
-        let topmost_scroll_node_index = self.topmost_scroll_node_index();
-        let non_root_overscroll = if node_index != topmost_scroll_node_index {
-            self.nodes[node_index.0].is_overscrolling()
-        } else {
-            false
-        };
-
-        let mut switch_node = false;
-        {
-            let node = &mut self.nodes[node_index.0];
-            if let NodeType::ScrollFrame(ref mut scrolling) = node.node_type {
-                match phase {
-                    ScrollEventPhase::Start => {
-                        // if this is a new gesture, we do not switch node,
-                        // however we do save the state of non_root_overscroll,
-                        // for use in the subsequent Move phase.
-                        scrolling.should_handoff_scroll = non_root_overscroll;
-                    }
-                    ScrollEventPhase::Move(_) => {
-                        // Switch node if movement originated in a new gesture,
-                        // from a non root node in overscroll.
-                        switch_node = scrolling.should_handoff_scroll && non_root_overscroll
-                    }
-                    ScrollEventPhase::End => {
-                        // clean-up when gesture ends.
-                        scrolling.should_handoff_scroll = false;
-                    }
-                }
-            }
-        }
-
-        let node_index = if switch_node {
-            topmost_scroll_node_index
-        } else {
-            node_index
-        };
-
-        self.nodes[node_index.0].scroll(scroll_location, phase)
+        let node_index = self.find_scrolling_node_at_point(&cursor);
+        self.nodes[node_index.0].scroll(scroll_location)
     }
 
     pub fn update_tree(
         &mut self,
         screen_rect: &DeviceIntRect,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
@@ -429,22 +353,16 @@ impl ClipScrollTree {
                 };
             }
 
             chain.parent_index = descriptor.parent;
             self.clip_chains[descriptor.index.0] = chain;
         }
     }
 
-    pub fn tick_scrolling_bounce_animations(&mut self) {
-        for node in &mut self.nodes {
-            node.tick_scrolling_bounce_animation()
-        }
-    }
-
     pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
         for node in &mut self.nodes {
             let external_id = match node.node_type {
                 NodeType::ScrollFrame(ScrollFrameInfo { external_id: Some(id), ..} ) => id,
                 _ => continue,
             };
 
             if let Some(scrolling_state) = old_states.get(&external_id) {
@@ -525,37 +443,31 @@ impl ClipScrollTree {
         let length_to_extend = self.nodes.len() .. index.0;
         self.nodes.extend(length_to_extend.map(|_| ClipScrollNode::empty()));
 
         self.nodes.push(node);
     }
 
     pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
         self.pipelines_to_discard.insert(pipeline_id);
-
-        if let Some(index) = self.currently_scrolling_node_index {
-            if self.nodes[index.0].pipeline_id == pipeline_id {
-                self.currently_scrolling_node_index = None;
-            }
-        }
     }
 
     fn print_node<T: PrintTreePrinter>(
         &self,
         index: ClipScrollNodeIndex,
         pt: &mut T,
         clip_store: &ClipStore
     ) {
         let node = &self.nodes[index.0];
         match node.node_type {
             NodeType::Clip { ref handle, .. } => {
                 pt.new_level("Clip".to_owned());
 
                 pt.add_item(format!("index: {:?}", index));
-                let clips = clip_store.get(&handle).clips();
+                let clips = clip_store.get(handle).clips();
                 pt.new_level(format!("Clip Sources [{}]", clips.len()));
                 for source in clips {
                     pt.add_item(format!("{:?}", source));
                 }
                 pt.end_level();
             }
             NodeType::ReferenceFrame(ref info) => {
                 pt.new_level(format!("ReferenceFrame {:?}", info.resolved_transform));
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -48,17 +48,16 @@ const GL_FORMAT_BGRA_GL: gl::GLuint = gl
 const GL_FORMAT_BGRA_GLES: gl::GLuint = gl::BGRA_EXT;
 
 const SHADER_VERSION_GL: &str = "#version 150\n";
 const SHADER_VERSION_GLES: &str = "#version 300 es\n";
 
 const SHADER_KIND_VERTEX: &str = "#define WR_VERTEX_SHADER\n";
 const SHADER_KIND_FRAGMENT: &str = "#define WR_FRAGMENT_SHADER\n";
 const SHADER_IMPORT: &str = "#include ";
-const SHADER_LINE_MARKER: &str = "#line 1\n";
 
 pub struct TextureSlot(pub usize);
 
 // In some places we need to temporarily bind a texture to any slot.
 const DEFAULT_TEXTURE: TextureSlot = TextureSlot(0);
 
 #[repr(u32)]
 pub enum DepthFunction {
@@ -165,30 +164,26 @@ fn get_shader_source(shader_name: &str, 
     shader_source::SHADERS
         .get(shader_name)
         .map(|s| s.to_string())
 }
 
 // Parse a shader string for imports. Imports are recursively processed, and
 // prepended to the list of outputs.
 fn parse_shader_source(source: String, base_path: &Option<PathBuf>, output: &mut String) {
-    output.push_str(SHADER_LINE_MARKER);
-
-    for (line_num, line) in source.lines().enumerate() {
+    for line in source.lines() {
         if line.starts_with(SHADER_IMPORT) {
-            let imports = line[SHADER_IMPORT.len() ..].split(",");
+            let imports = line[SHADER_IMPORT.len() ..].split(',');
 
             // For each import, get the source, and recurse.
             for import in imports {
                 if let Some(include) = get_shader_source(import, base_path) {
                     parse_shader_source(include, base_path, output);
                 }
             }
-
-            output.push_str(&format!("#line {}\n", line_num+1));
         } else {
             output.push_str(line);
             output.push_str("\n");
         }
     }
 }
 
 pub fn build_shader_strings(
@@ -344,21 +339,21 @@ impl VertexDescriptor {
         for (i, attr) in attributes.iter().enumerate() {
             let attr_index = (start_index + i) as gl::GLuint;
             attr.bind_to_vao(attr_index, divisor, stride as _, offset, gl);
             offset += attr.size_in_bytes();
         }
     }
 
     fn bind(&self, gl: &gl::Gl, main: VBOId, instance: VBOId) {
-        Self::bind_attributes(&self.vertex_attributes, 0, 0, gl, main);
+        Self::bind_attributes(self.vertex_attributes, 0, 0, gl, main);
 
         if !self.instance_attributes.is_empty() {
             Self::bind_attributes(
-                &self.instance_attributes,
+                self.instance_attributes,
                 self.vertex_attributes.len(),
                 1, gl, instance,
             );
         }
     }
 }
 
 impl VBOId {
@@ -772,29 +767,50 @@ impl Device {
 
     pub fn reset_state(&mut self) {
         self.bound_textures = [0; 16];
         self.bound_vao = 0;
         self.bound_read_fbo = FBOId(0);
         self.bound_draw_fbo = FBOId(0);
     }
 
+    #[cfg(debug_assertions)]
+    fn print_shader_errors(source: &str, log: &str) {
+        // hacky way to extract the offending lines
+        if !log.starts_with("0:") {
+            return;
+        }
+        let end_pos = match log[2..].chars().position(|c| !c.is_digit(10)) {
+            Some(pos) => 2 + pos,
+            None => return,
+        };
+        let base_line_number = match log[2 .. end_pos].parse::<usize>() {
+            Ok(number) if number >= 2 => number - 2,
+            _ => return,
+        };
+        for (line, prefix) in source.lines().skip(base_line_number).zip(&["|",">","|"]) {
+            println!("{}\t{}", prefix, line);
+        }
+    }
+
     pub fn compile_shader(
         gl: &gl::Gl,
         name: &str,
         shader_type: gl::GLenum,
         source: &String,
     ) -> Result<gl::GLuint, ShaderError> {
         debug!("compile {}", name);
         let id = gl.create_shader(shader_type);
         gl.shader_source(id, &[source.as_bytes()]);
         gl.compile_shader(id);
         let log = gl.get_shader_info_log(id);
         if gl.get_shader_iv(id, gl::COMPILE_STATUS) == (0 as gl::GLint) {
             println!("Failed to compile shader: {}\n{}", name, log);
+            #[cfg(debug_assertions)]
+            Self::print_shader_errors(source, &log);
             Err(ShaderError::Compilation(name.to_string(), log))
         } else {
             if !log.is_empty() {
                 println!("Warnings detected on shader: {}\n{}", name, log);
             }
             Ok(id)
         }
     }
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -316,41 +316,45 @@ impl<'a> DisplayListFlattener<'a> {
             .collect()
     }
 
     fn flatten_root(&mut self, pipeline: &'a ScenePipeline, frame_size: &LayoutSize) {
         let pipeline_id = pipeline.pipeline_id;
         let reference_frame_info = self.id_to_index_mapper.simple_scroll_and_clip_chain(
             &ClipId::root_reference_frame(pipeline_id)
         );
+
+        let root_scroll_node = ClipId::root_scroll_node(pipeline_id);
         let scroll_frame_info = self.id_to_index_mapper.simple_scroll_and_clip_chain(
-            &ClipId::root_scroll_node(pipeline_id)
+            &root_scroll_node,
         );
 
         self.push_stacking_context(
             pipeline_id,
             CompositeOps::default(),
             TransformStyle::Flat,
             true,
             true,
-            scroll_frame_info,
+            root_scroll_node,
+            None,
         );
 
         // For the root pipeline, there's no need to add a full screen rectangle
         // here, as it's handled by the framebuffer clear.
         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 root_bounds = LayerRect::new(LayerPoint::zero(), *frame_size);
                     let info = LayerPrimitiveInfo::new(root_bounds);
                     self.add_solid_rectangle(
                         reference_frame_info,
                         &info,
                         bg_color,
                         None,
+                        Vec::new(),
                     );
                 }
             }
         }
 
         self.flatten_items(&mut pipeline.display_list.iter(), pipeline_id, LayerVector2D::zero());
 
         if self.config.enable_scrollbars {
@@ -402,56 +406,56 @@ impl<'a> DisplayListFlattener<'a> {
     fn flatten_sticky_frame(
         &mut self,
         item: &DisplayItemRef,
         info: &StickyFrameDisplayItem,
         clip_and_scroll: &ScrollNodeAndClipChain,
         parent_id: &ClipId,
         reference_frame_relative_offset: &LayerVector2D,
     ) {
-        let frame_rect = item.rect().translate(&reference_frame_relative_offset);
+        let frame_rect = item.rect().translate(reference_frame_relative_offset);
         let sticky_frame_info = StickyFrameInfo::new(
             info.margins,
             info.vertical_offset_bounds,
             info.horizontal_offset_bounds,
             info.previously_applied_offset,
         );
 
         let index = self.id_to_index_mapper.get_node_index(info.id);
         self.clip_scroll_tree.add_sticky_frame(
             index,
             clip_and_scroll.scroll_node_id, /* parent id */
             frame_rect,
             sticky_frame_info,
             info.id.pipeline_id(),
         );
-        self.id_to_index_mapper.map_to_parent_clip_chain(info.id, &parent_id);
+        self.id_to_index_mapper.map_to_parent_clip_chain(info.id, parent_id);
     }
 
     fn flatten_scroll_frame(
         &mut self,
         item: &DisplayItemRef,
         info: &ScrollFrameDisplayItem,
         pipeline_id: PipelineId,
         clip_and_scroll_ids: &ClipAndScrollInfo,
         reference_frame_relative_offset: &LayerVector2D,
     ) {
         let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
         let clip_region = ClipRegion::create_for_clip_node(
             *item.clip_rect(),
             complex_clips,
             info.image_mask,
-            &reference_frame_relative_offset,
+            reference_frame_relative_offset,
         );
         // Just use clip rectangle as the frame rect for this scroll frame.
         // This is useful when calculating scroll extents for the
         // ClipScrollNode::scroll(..) API as well as for properly setting sticky
         // 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 = item.clip_rect().translate(reference_frame_relative_offset);
+        let content_rect = item.rect().translate(reference_frame_relative_offset);
 
         debug_assert!(info.clip_id != info.scroll_frame_id);
 
         self.add_clip_node(info.clip_id, clip_and_scroll_ids.scroll_node_id, clip_region);
 
         self.add_scroll_frame(
             info.scroll_frame_id,
             info.clip_id,
@@ -462,22 +466,21 @@ impl<'a> DisplayListFlattener<'a> {
             info.scroll_sensitivity,
         );
     }
 
     fn flatten_stacking_context(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
+        item: &DisplayItemRef,
+        stacking_context: &StackingContext,
         unreplaced_scroll_id: ClipId,
         mut scroll_node_id: ClipId,
         mut reference_frame_relative_offset: LayerVector2D,
-        bounds: &LayerRect,
-        stacking_context: &StackingContext,
-        filters: ItemRange<FilterOp>,
         is_backface_visible: bool,
     ) {
         // Avoid doing unnecessary work for empty stacking contexts.
         if traversal.current_stacking_context_empty() {
             traversal.skip_current_stacking_context();
             return;
         }
 
@@ -485,26 +488,27 @@ impl<'a> DisplayListFlattener<'a> {
             // TODO(optimization?): self.traversal.display_list()
             let display_list = &self
                 .scene
                 .pipelines
                 .get(&pipeline_id)
                 .expect("No display list?!")
                 .display_list;
             CompositeOps::new(
-                stacking_context.filter_ops_for_compositing(display_list, filters),
+                stacking_context.filter_ops_for_compositing(display_list, item.filters()),
                 stacking_context.mix_blend_mode_for_compositing(),
             )
         };
 
         if stacking_context.scroll_policy == ScrollPolicy::Fixed {
             scroll_node_id = self.current_reference_frame_id();
             self.replacements.push((unreplaced_scroll_id, scroll_node_id));
         }
 
+        let bounds = item.rect();
         reference_frame_relative_offset += bounds.origin.to_vector();
 
         // If we have a transformation or a perspective, we should have been assigned a new
         // reference frame id. This means this stacking context establishes a new reference frame.
         // Descendant fixed position content will be positioned relative to us.
         if let Some(reference_frame_id) = stacking_context.reference_frame_id {
             debug_assert!(
                 stacking_context.transform.is_some() ||
@@ -522,26 +526,25 @@ impl<'a> DisplayListFlattener<'a> {
                 reference_frame_relative_offset,
             );
             self.replacements.push((unreplaced_scroll_id, reference_frame_id));
             reference_frame_relative_offset = LayerVector2D::zero();
         }
 
         // We apply the replacements one more time in case we need to set it to a replacement
         // that we just pushed above.
-        let sc_scroll_node = self.apply_scroll_frame_id_replacement(unreplaced_scroll_id);
-        let stacking_context_clip_and_scroll =
-            self.id_to_index_mapper.simple_scroll_and_clip_chain(&sc_scroll_node);
+        let final_scroll_node = self.apply_scroll_frame_id_replacement(unreplaced_scroll_id);
         self.push_stacking_context(
             pipeline_id,
             composition_operations,
             stacking_context.transform_style,
             is_backface_visible,
             false,
-            stacking_context_clip_and_scroll,
+            final_scroll_node,
+            stacking_context.clip_node_id,
         );
 
         self.flatten_items(
             traversal,
             pipeline_id,
             reference_frame_relative_offset,
         );
 
@@ -572,17 +575,17 @@ impl<'a> DisplayListFlattener<'a> {
 
         self.id_to_index_mapper.initialize_for_pipeline(pipeline);
 
         self.add_clip_node(
             info.clip_id,
             clip_and_scroll_ids.scroll_node_id,
             ClipRegion::create_for_clip_node_with_local_clip(
                 &LocalClip::from(*item.clip_rect()),
-                &reference_frame_relative_offset
+                reference_frame_relative_offset
             ),
         );
 
         let epoch = self.scene.pipeline_epochs[&iframe_pipeline_id];
         self.pipeline_epochs.push((iframe_pipeline_id, epoch));
 
         let bounds = item.rect();
         let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
@@ -683,26 +686,26 @@ impl<'a> DisplayListFlattener<'a> {
             SpecificDisplayItem::Text(ref text_info) => {
                 self.add_text(
                     clip_and_scroll,
                     reference_frame_relative_offset,
                     &prim_info,
                     &text_info.font_key,
                     &text_info.color,
                     item.glyphs(),
-                    item.display_list().get(item.glyphs()).count(),
                     text_info.glyph_options,
                 );
             }
             SpecificDisplayItem::Rectangle(ref info) => {
                 self.add_solid_rectangle(
                     clip_and_scroll,
                     &prim_info,
                     info.color,
                     None,
+                    Vec::new(),
                 );
             }
             SpecificDisplayItem::ClearRectangle => {
                 self.add_clear_rectangle(
                     clip_and_scroll,
                     &prim_info,
                 );
             }
@@ -769,22 +772,21 @@ impl<'a> DisplayListFlattener<'a> {
                     item.display_list().get(item.gradient_stops()).count(),
                 );
             }
             SpecificDisplayItem::PushStackingContext(ref info) => {
                 let mut subtraversal = item.sub_iter();
                 self.flatten_stacking_context(
                     &mut subtraversal,
                     pipeline_id,
+                    &item,
+                    &info.stacking_context,
                     unreplaced_scroll_id,
                     clip_and_scroll_ids.scroll_node_id,
                     reference_frame_relative_offset,
-                    &item.rect(),
-                    &info.stacking_context,
-                    item.filters(),
                     prim_info.is_backface_visible,
                 );
                 return Some(subtraversal);
             }
             SpecificDisplayItem::Iframe(ref info) => {
                 self.flatten_iframe(
                     &item,
                     info,
@@ -864,26 +866,24 @@ impl<'a> DisplayListFlattener<'a> {
         let stacking_context = self.sc_stack.last().expect("bug: no stacking context!");
 
         let clip_sources = if clip_sources.is_empty() {
             None
         } else {
             Some(self.clip_store.insert(ClipSources::new(clip_sources)))
         };
 
-        let prim_index = self.prim_store.add_primitive(
+        self.prim_store.add_primitive(
             &info.rect,
             &info.clip_rect,
             info.is_backface_visible && stacking_context.is_backface_visible,
             clip_sources,
             info.tag,
             container,
-        );
-
-        prim_index
+        )
     }
 
     pub fn add_primitive_to_hit_testing_list(
         &mut self,
         info: &LayerPrimitiveInfo,
         clip_and_scroll: ScrollNodeAndClipChain
     ) {
         let tag = match info.tag {
@@ -964,18 +964,28 @@ impl<'a> DisplayListFlattener<'a> {
 
     pub fn push_stacking_context(
         &mut self,
         pipeline_id: PipelineId,
         composite_ops: CompositeOps,
         transform_style: TransformStyle,
         is_backface_visible: bool,
         is_pipeline_root: bool,
-        clip_and_scroll: ScrollNodeAndClipChain,
+        positioning_node: ClipId,
+        clipping_node: Option<ClipId>,
     ) {
+        let clip_chain_id = match clipping_node {
+            Some(ref clipping_node) => self.id_to_index_mapper.get_clip_chain_index(clipping_node),
+            None => ClipChainIndex(0), // This means no clipping.
+        };
+        let clip_and_scroll = ScrollNodeAndClipChain::new(
+            self.id_to_index_mapper.get_node_index(positioning_node),
+            clip_chain_id
+        );
+
         // Construct the necessary set of Picture primitives
         // to draw this stacking context.
         let current_reference_frame_index = self.current_reference_frame_index();
 
         // An arbitrary large clip rect. For now, we don't
         // specify a clip specific to the stacking context.
         // However, now that they are represented as Picture
         // primitives, we can apply any kind of clip mask
@@ -1069,20 +1079,17 @@ impl<'a> DisplayListFlattener<'a> {
                 None,
                 None,
                 PrimitiveContainer::Brush(prim),
             );
 
             let parent_pic_index = *self.picture_stack.last().unwrap();
 
             let pic = &mut self.prim_store.pictures[parent_pic_index.0];
-            pic.add_primitive(
-                prim_index,
-                clip_and_scroll,
-            );
+            pic.add_primitive(prim_index, clip_and_scroll);
 
             self.picture_stack.push(container_index);
 
             Some(container_index)
         } else {
             None
         };
 
@@ -1155,20 +1162,17 @@ impl<'a> DisplayListFlattener<'a> {
 
             if let Some(shadow_prim_index) = shadow_prim_index {
                 parent_pic.add_primitive(
                     shadow_prim_index,
                     clip_and_scroll,
                 );
             }
 
-            parent_pic.add_primitive(
-                src_prim_index,
-                clip_and_scroll,
-            );
+            parent_pic.add_primitive(src_prim_index, clip_and_scroll);
 
             self.picture_stack.push(src_pic_index);
         }
 
         // Same for mix-blend-mode.
         if let Some(mix_blend_mode) = composite_ops.mix_blend_mode {
             let src_pic_index = self.prim_store.add_image_picture(
                 Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
@@ -1191,37 +1195,38 @@ impl<'a> DisplayListFlattener<'a> {
                 is_backface_visible,
                 None,
                 None,
                 PrimitiveContainer::Brush(src_prim),
             );
 
             let parent_pic = &mut self.prim_store.pictures[parent_pic_index.0];
             parent_pic_index = src_pic_index;
-            parent_pic.add_primitive(
-                src_prim_index,
-                clip_and_scroll,
-            );
+            parent_pic.add_primitive(src_prim_index, clip_and_scroll);
 
             self.picture_stack.push(src_pic_index);
         }
 
         // By default, this picture will be collapsed into
         // the owning target.
         let mut composite_mode = None;
         let mut frame_output_pipeline_id = None;
 
         // If this stacking context if the root of a pipeline, and the caller
         // has requested it as an output frame, create a render task to isolate it.
         if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) {
             composite_mode = Some(PictureCompositeMode::Blit);
             frame_output_pipeline_id = Some(pipeline_id);
         }
 
-        if participating_in_3d_context {
+        // Force an intermediate surface if the stacking context
+        // 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.
+        if participating_in_3d_context || clipping_node.is_some() {
             // TODO(gw): For now, as soon as this picture is in
             //           a 3D context, we draw it to an intermediate
             //           surface and apply plane splitting. However,
             //           there is a large optimization opportunity here.
             //           During culling, we can check if there is actually
             //           perspective present, and skip the plane splitting
             //           completely when that is not the case.
             composite_mode = Some(PictureCompositeMode::Blit);
@@ -1229,17 +1234,17 @@ impl<'a> DisplayListFlattener<'a> {
 
         // Add picture for this actual stacking context contents to render into.
         let pic_index = self.prim_store.add_image_picture(
             composite_mode,
             participating_in_3d_context,
             pipeline_id,
             current_reference_frame_index,
             frame_output_pipeline_id,
-                true,
+            true,
         );
 
         // Create a brush primitive that draws this picture.
         let sc_prim = BrushPrimitive::new_picture(
             pic_index,
             BrushImageSourceKind::Color,
             LayoutVector2D::zero(),
         );
@@ -1503,16 +1508,17 @@ impl<'a> DisplayListFlattener<'a> {
     }
 
     pub fn add_solid_rectangle(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
         color: ColorF,
         segments: Option<BrushSegmentDescriptor>,
+        extra_clips: Vec<ClipSource>,
     ) {
         if color.a == 0.0 {
             // Don't add transparent rectangles to the draw list, but do consider them for hit
             // testing. This allows specifying invisible hit testing areas.
             self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
             return;
         }
 
@@ -1521,17 +1527,17 @@ impl<'a> DisplayListFlattener<'a> {
                 color,
             },
             segments,
         );
 
         self.add_primitive(
             clip_and_scroll,
             info,
-            Vec::new(),
+            extra_clips,
             PrimitiveContainer::Brush(prim),
         );
     }
 
     pub fn add_clear_rectangle(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
@@ -2060,22 +2066,21 @@ impl<'a> DisplayListFlattener<'a> {
     pub fn add_text(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         run_offset: LayoutVector2D,
         prim_info: &LayerPrimitiveInfo,
         font_instance_key: &FontInstanceKey,
         text_color: &ColorF,
         glyph_range: ItemRange<GlyphInstance>,
-        glyph_count: usize,
         glyph_options: Option<GlyphOptions>,
     ) {
         let prim = {
             let instance_map = self.font_instances.read().unwrap();
-            let font_instance = match instance_map.get(&font_instance_key) {
+            let font_instance = match instance_map.get(font_instance_key) {
                 Some(instance) => instance,
                 None => {
                     warn!("Unknown font instance key");
                     debug!("key={:?}", font_instance_key);
                     return;
                 }
             };
 
@@ -2109,17 +2114,17 @@ impl<'a> DisplayListFlattener<'a> {
             // There are some conditions under which we can't use
             // subpixel text rendering, even if enabled.
             if render_mode == FontRenderMode::Subpixel {
                 // text on a picture that has filters
                 // (e.g. opacity) can't use sub-pixel.
                 // TODO(gw): It's possible we can relax this in
                 //           the future, if we modify the way
                 //           we handle subpixel blending.
-                if let Some(ref stacking_context) = self.sc_stack.last() {
+                if let Some(stacking_context) = self.sc_stack.last() {
                     if !stacking_context.allow_subpixel_aa {
                         render_mode = FontRenderMode::Alpha;
                     }
                 }
             }
 
             let prim_font = FontInstance::new(
                 font_instance.font_key,
@@ -2130,17 +2135,16 @@ impl<'a> DisplayListFlattener<'a> {
                 font_instance.subpx_dir,
                 flags,
                 font_instance.platform_options,
                 font_instance.variations.clone(),
             );
             TextRunPrimitiveCpu {
                 font: prim_font,
                 glyph_range,
-                glyph_count,
                 glyph_gpu_blocks: Vec::new(),
                 glyph_keys: Vec::new(),
                 offset: run_offset,
                 shadow: false,
             }
         };
 
         self.add_primitive(
@@ -2172,27 +2176,43 @@ impl<'a> DisplayListFlattener<'a> {
         }
 
         let request = ImageRequest {
             key: image_key,
             rendering: image_rendering,
             tile: tile_offset,
         };
 
+        let sub_rect = sub_rect.map(|texel_rect| {
+            DeviceIntRect::new(
+                DeviceIntPoint::new(
+                    texel_rect.uv0.x as i32,
+                    texel_rect.uv0.y as i32,
+                ),
+                DeviceIntSize::new(
+                    (texel_rect.uv1.x - texel_rect.uv0.x) as i32,
+                    (texel_rect.uv1.y - texel_rect.uv0.y) as i32,
+                ),
+            )
+        });
+
         // See if conditions are met to run through the new
         // image brush shader, which supports segments.
         if tile_spacing == LayerSize::zero() &&
            stretch_size == info.rect.size &&
-           sub_rect.is_none() &&
            tile_offset.is_none() {
             let prim = BrushPrimitive::new(
                 BrushKind::Image {
                     request,
                     current_epoch: Epoch::invalid(),
                     alpha_type,
+                    stretch_size,
+                    tile_spacing,
+                    source: ImageSource::Default,
+                    sub_rect,
                 },
                 None,
             );
 
             self.add_primitive(
                 clip_and_scroll,
                 info,
                 Vec::new(),
@@ -2202,28 +2222,17 @@ impl<'a> DisplayListFlattener<'a> {
             let prim_cpu = ImagePrimitiveCpu {
                 tile_spacing,
                 alpha_type,
                 stretch_size,
                 current_epoch: Epoch::invalid(),
                 source: ImageSource::Default,
                 key: ImageCacheKey {
                     request,
-                    texel_rect: sub_rect.map(|texel_rect| {
-                        DeviceIntRect::new(
-                            DeviceIntPoint::new(
-                                texel_rect.uv0.x as i32,
-                                texel_rect.uv0.y as i32,
-                            ),
-                            DeviceIntSize::new(
-                                (texel_rect.uv1.x - texel_rect.uv0.x) as i32,
-                                (texel_rect.uv1.y - texel_rect.uv0.y) as i32,
-                            ),
-                        )
-                    }),
+                    texel_rect: sub_rect,
                 },
             };
 
             self.add_primitive(
                 clip_and_scroll,
                 info,
                 Vec::new(),
                 PrimitiveContainer::Image(prim_cpu),
--- a/gfx/webrender/src/ellipse.rs
+++ b/gfx/webrender/src/ellipse.rs
@@ -117,17 +117,17 @@ impl Ellipse {
             let ry = (s - u) * (3.0_f32).sqrt();
             let rm = (rx * rx + ry * ry).sqrt();
             let p = ry / (rm - rx).sqrt();
             (p + 2.0 * g / rm - m) / 2.0
         };
 
         let si = (1.0 - co * co).sqrt();
         let r = LayerVector2D::new(ab.x * co, ab.y * si);
-        return (r - p).length() * (p.y - r.y).signum();
+        (r - p).length() * (p.y - r.y).signum()
     }
 }
 
 /// Use Simpsons rule to approximate the arc length of
 /// part of an ellipse. Note that this only works over
 /// the range of [0, pi/2].
 // TODO(gw): This is a simplistic way to estimate the
 // arc length of an ellipse segment. We can probably use
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -289,17 +289,17 @@ impl FrameBuilder {
             .total_primitives
             .set(self.prim_store.prim_count());
 
         resource_cache.begin_frame(frame_id);
         gpu_cache.begin_frame();
 
         let mut node_data = Vec::with_capacity(clip_scroll_tree.nodes.len());
         let total_prim_runs =
-            self.prim_store.pictures.iter().fold(1, |count, ref pic| count + pic.runs.len());
+            self.prim_store.pictures.iter().fold(1, |count, pic| count + pic.runs.len());
         let mut clip_chain_local_clip_rects = Vec::with_capacity(total_prim_runs);
         clip_chain_local_clip_rects.push(LayerRect::max_rect());
 
         clip_scroll_tree.update_tree(
             &self.screen_rect.to_i32(),
             device_pixel_scale,
             &mut self.clip_store,
             resource_cache,
@@ -409,14 +409,14 @@ impl FrameBuilder {
             has_been_rendered: false,
             has_texture_cache_tasks,
         }
     }
 
     pub fn create_hit_tester(&mut self, clip_scroll_tree: &ClipScrollTree) -> HitTester {
         HitTester::new(
             &self.hit_testing_runs,
-            &clip_scroll_tree,
+            clip_scroll_tree,
             &self.clip_store
         )
     }
 }
 
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -772,17 +772,17 @@ impl GlyphRasterizer {
                     GlyphRasterResult::LoadFailed => GlyphCacheEntry::Blank,
                     GlyphRasterResult::Bitmap(ref glyph) if glyph.width == 0 ||
                                                             glyph.height == 0 => {
                         GlyphCacheEntry::Blank
                     }
                     GlyphRasterResult::Bitmap(glyph) => {
                         assert_eq!((glyph.left.fract(), glyph.top.fract()), (0.0, 0.0));
                         let mut texture_cache_handle = TextureCacheHandle::new();
-                        texture_cache.request(&mut texture_cache_handle, gpu_cache);
+                        texture_cache.request(&texture_cache_handle, gpu_cache);
                         texture_cache.update(
                             &mut texture_cache_handle,
                             ImageDescriptor {
                                 width: glyph.width,
                                 height: glyph.height,
                                 stride: None,
                                 format: ImageFormat::BGRA8,
                                 is_opaque: false,
@@ -840,36 +840,36 @@ impl GlyphRasterizer {
 }
 
 trait AddFont {
     fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate);
 }
 
 impl AddFont for FontContext {
     fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate) {
-        match template {
-            &FontTemplate::Raw(ref bytes, index) => {
-                self.add_raw_font(&font_key, bytes.clone(), index);
+        match *template {
+            FontTemplate::Raw(ref bytes, index) => {
+                self.add_raw_font(font_key, bytes.clone(), index);
             }
-            &FontTemplate::Native(ref native_font_handle) => {
-                self.add_native_font(&font_key, (*native_font_handle).clone());
+            FontTemplate::Native(ref native_font_handle) => {
+                self.add_native_font(font_key, (*native_font_handle).clone());
             }
         }
     }
 }
 
 #[cfg(feature = "pathfinder")]
 impl AddFont for PathfinderFontContext {
     fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate) {
-        match template {
-            &FontTemplate::Raw(ref bytes, index) => {
-                drop(self.add_font_from_memory(&font_key, bytes.clone(), index));
+        match *template {
+            FontTemplate::Raw(ref bytes, index) => {
+                drop(self.add_font_from_memory(font_key, bytes.clone(), index));
             }
-            &FontTemplate::Native(ref native_font_handle) => {
-                drop(self.add_native_font(&font_key, (*native_font_handle).clone().0));
+            FontTemplate::Native(ref native_font_handle) => {
+                drop(self.add_native_font(font_key, (*native_font_handle).clone().0));
             }
         }
     }
 }
 
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -70,27 +70,30 @@ impl HitTestingItem {
         }
     }
 }
 
 #[derive(Clone)]
 pub struct HitTestingRun(pub Vec<HitTestingItem>, pub ScrollNodeAndClipChain);
 
 enum HitTestRegion {
-    Rectangle(LayerRect),
+    Rectangle(LayerRect, ClipMode),
     RoundedRectangle(LayerRect, BorderRadius, ClipMode),
 }
 
 impl HitTestRegion {
     pub fn contains(&self, point: &LayerPoint) -> bool {
-        match self {
-            &HitTestRegion::Rectangle(ref rectangle) => rectangle.contains(point),
-            &HitTestRegion::RoundedRectangle(rect, radii, ClipMode::Clip) =>
+        match *self {
+            HitTestRegion::Rectangle(ref rectangle, ClipMode::Clip) =>
+                rectangle.contains(point),
+            HitTestRegion::Rectangle(ref rectangle, ClipMode::ClipOut) =>
+                !rectangle.contains(point),
+            HitTestRegion::RoundedRectangle(rect, radii, ClipMode::Clip) =>
                 rounded_rectangle_contains_point(point, &rect, &radii),
-            &HitTestRegion::RoundedRectangle(rect, radii, ClipMode::ClipOut) =>
+            HitTestRegion::RoundedRectangle(rect, radii, ClipMode::ClipOut) =>
                 !rounded_rectangle_contains_point(point, &rect, &radii),
         }
     }
 }
 
 pub struct HitTester {
     runs: Vec<HitTestingRun>,
     nodes: Vec<HitTestClipScrollNode>,
@@ -300,22 +303,22 @@ fn get_regions_for_clip_scroll_node(
     node: &ClipScrollNode,
     clip_store: &ClipStore
 ) -> Vec<HitTestRegion> {
     let clips = match node.node_type {
         NodeType::Clip{ ref handle, .. } => clip_store.get(handle).clips(),
         _ => return Vec::new(),
     };
 
-    clips.iter().map(|ref source| {
+    clips.iter().map(|source| {
         match source.0 {
-            ClipSource::Rectangle(ref rect) => HitTestRegion::Rectangle(*rect),
+            ClipSource::Rectangle(ref rect, mode) => HitTestRegion::Rectangle(*rect, mode),
             ClipSource::RoundedRectangle(ref rect, ref radii, ref mode) =>
                 HitTestRegion::RoundedRectangle(*rect, *radii, *mode),
-            ClipSource::Image(ref mask) => HitTestRegion::Rectangle(mask.rect),
+            ClipSource::Image(ref mask) => HitTestRegion::Rectangle(mask.rect, ClipMode::Clip),
             ClipSource::BorderCorner(_) |
             ClipSource::LineDecoration(_) |
             ClipSource::BoxShadow(_) => {
                 unreachable!("Didn't expect to hit test against BorderCorner / BoxShadow / LineDecoration");
             }
         }
     }).collect()
 }
@@ -360,12 +363,12 @@ impl HitTest {
 
     pub fn get_absolute_point(&self, hit_tester: &HitTester) -> WorldPoint {
         if !self.flags.contains(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT) {
             return self.point;
         }
 
         let point =  &LayerPoint::new(self.point.x, self.point.y);
         self.pipeline_id.map(|id|
-            hit_tester.get_pipeline_root(id).world_viewport_transform.transform_point2d(&point)
+            hit_tester.get_pipeline_root(id).world_viewport_transform.transform_point2d(point)
         ).unwrap_or_else(|| WorldPoint::new(self.point.x, self.point.y))
     }
 }
--- a/gfx/webrender/src/image.rs
+++ b/gfx/webrender/src/image.rs
@@ -84,17 +84,17 @@ fn decompose_row(item_rect: &LayerRect, 
     let num_repetitions = (item_rect.size.width / layout_stride).ceil() as u32;
 
     for i in 0 .. num_repetitions {
         let decomposed_rect = rect(
             item_rect.origin.x + (i as f32) * layout_stride,
             item_rect.origin.y,
             info.stretch_size.width,
             item_rect.size.height,
-        ).intersection(&item_rect);
+        ).intersection(item_rect);
 
         if let Some(decomposed_rect) = decomposed_rect {
             decompose_cache_tiles(&decomposed_rect, info, callback);
         }
     }
 }
 
 fn decompose_cache_tiles(
@@ -265,16 +265,16 @@ fn add_device_tile(
     }
 
     if shader_repeat_y {
         assert_eq!(tile_offset.y, 0);
         prim_rect.size.height = item_rect.size.height;
     }
 
     // Fix up the primitive's rect if it overflows the original item rect.
-    if let Some(rect) = prim_rect.intersection(&item_rect) {
+    if let Some(rect) = prim_rect.intersection(item_rect) {
         callback(&DecomposedTile {
             tile_offset,
             rect,
             stretch_size,
         });
     }
 }
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -1,15 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{DebugCommand, DeviceUintRect, DocumentId, ExternalImageData, ExternalImageId};
 use api::ImageFormat;
-use clip_scroll_tree::ClipScrollNodeIndex;
 use device::TextureFilter;
 use renderer::PipelineInfo;
 use gpu_cache::GpuCacheUpdateList;
 use fxhash::FxHasher;
 use profiler::BackendProfileCounters;
 use std::{usize, i32};
 use std::collections::{HashMap, HashSet};
 use std::f32;
@@ -133,31 +132,27 @@ impl TextureUpdateList {
 /// Mostly wraps a tiling::Frame, adding a bit of extra information.
 pub struct RenderedDocument {
     /// The pipeline info contains:
     /// - The last rendered epoch for each pipeline present in the frame.
     /// This information is used to know if a certain transformation on the layout has
     /// been rendered, which is necessary for reftests.
     /// - Pipelines that were removed from the scene.
     pub pipeline_info: PipelineInfo,
-    /// The layers that are currently affected by the over-scrolling animation.
-    pub layers_bouncing_back: FastHashSet<ClipScrollNodeIndex>,
 
     pub frame: tiling::Frame,
 }
 
 impl RenderedDocument {
     pub fn new(
         pipeline_info: PipelineInfo,
-        layers_bouncing_back: FastHashSet<ClipScrollNodeIndex>,
         frame: tiling::Frame,
     ) -> Self {
         RenderedDocument {
             pipeline_info,
-            layers_bouncing_back,
             frame,
         }
     }
 }
 
 pub enum DebugOutput {
     FetchDocuments(String),
     FetchClipScrollTree(String),
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -95,17 +95,16 @@ mod record;
 mod render_backend;
 mod render_task;
 mod renderer;
 mod resource_cache;
 mod scene;
 mod scene_builder;
 mod segment;
 mod shade;
-mod spring;
 mod texture_allocator;
 mod texture_cache;
 mod tiling;
 mod util;
 
 mod shader_source {
     include!(concat!(env!("OUT_DIR"), "/shaders.rs"));
 }
@@ -180,12 +179,12 @@ extern crate base64;
 extern crate png;
 
 pub extern crate webrender_api;
 
 #[doc(hidden)]
 pub use device::{build_shader_strings, ProgramCache, ReadPixelsFormat, UploadMethod, VertexUsageHint};
 pub use renderer::{CpuProfile, DebugFlags, GpuProfile, OutputImageHandler, RendererKind};
 pub use renderer::{ExternalImage, ExternalImageHandler, ExternalImageSource};
-pub use renderer::{GraphicsApi, GraphicsApiInfo, Renderer, RendererOptions};
-pub use renderer::{RendererStats, ThreadListener};
+pub use renderer::{GraphicsApi, GraphicsApiInfo, PipelineInfo, Renderer, RendererOptions};
+pub use renderer::{RendererStats, SceneBuilderHooks, ThreadListener};
 pub use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 pub use webrender_api as api;
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -84,18 +84,18 @@ pub struct PicturePrimitive {
     // picture.
     pub extra_gpu_data_handle: GpuCacheHandle,
 }
 
 impl PicturePrimitive {
     pub fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool {
         match self.composite_mode {
             Some(PictureCompositeMode::Filter(ref mut filter)) => {
-                match filter {
-                    &mut FilterOp::Opacity(ref binding, ref mut value) => {
+                match *filter {
+                    FilterOp::Opacity(ref binding, ref mut value) => {
                         *value = properties.resolve_float(binding, *value);
                     }
                     _ => {}
                 }
 
                 filter.is_visible()
             }
             _ => true,
@@ -164,86 +164,98 @@ impl PicturePrimitive {
                 local_content_rect.inflate(inflate_size, inflate_size)
             }
             _ => {
                 local_content_rect
             }
         }
     }
 
-    pub fn prepare_for_render(
+    pub fn can_draw_directly_to_parent_surface(&self) -> bool {
+        match self.composite_mode {
+            Some(PictureCompositeMode::Filter(filter)) => {
+                filter.is_noop()
+            }
+            Some(PictureCompositeMode::Blit) |
+            Some(PictureCompositeMode::MixBlend(..)) => {
+                false
+            }
+            None => {
+                true
+            }
+        }
+    }
+
+    pub fn prepare_for_render_inner(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_metadata: &mut PrimitiveMetadata,
         pic_state_for_children: PictureState,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
-    ) {
+    ) -> Option<DeviceIntRect> {
         let prim_screen_rect = prim_metadata
                                 .screen_rect
                                 .as_ref()
                                 .expect("bug: trying to draw an off-screen picture!?");
+        if self.can_draw_directly_to_parent_surface() {
+            pic_state.tasks.extend(pic_state_for_children.tasks);
+            self.surface = None;
+            return None;
+        }
+
 
         // TODO(gw): Almost all of the Picture types below use extra_gpu_cache_data
         //           to store the same type of data. The exception is the filter
         //           with a ColorMatrix, which stores the color matrix here. It's
         //           probably worth tidying this code up to be a bit more consistent.
         //           Perhaps store the color matrix after the common data, even though
         //           it's not used by that shader.
-        let device_rect = match self.composite_mode {
+        match self.composite_mode {
             Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
-                // If blur radius is 0, we can skip drawing this on an
-                // intermediate surface.
-                if blur_radius == 0.0 {
-                    pic_state.tasks.extend(pic_state_for_children.tasks);
-                    self.surface = None;
+                let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
+                let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
 
-                    None
-                } else {
-                    let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
-                    let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
+                // The clipped field is the part of the picture that is visible
+                // on screen. The unclipped field is the screen-space rect of
+                // the complete picture, if no screen / clip-chain was applied
+                // (this includes the extra space for blur region). To ensure
+                // that we draw a large enough part of the picture to get correct
+                // blur results, inflate that clipped area by the blur range, and
+                // then intersect with the total screen rect, to minimize the
+                // allocation size.
+                let device_rect = prim_screen_rect
+                    .clipped
+                    .inflate(blur_range, blur_range)
+                    .intersection(&prim_screen_rect.unclipped)
+                    .unwrap();
 
-                    // The clipped field is the part of the picture that is visible
-                    // on screen. The unclipped field is the screen-space rect of
-                    // the complete picture, if no screen / clip-chain was applied
-                    // (this includes the extra space for blur region). To ensure
-                    // that we draw a large enough part of the picture to get correct
-                    // blur results, inflate that clipped area by the blur range, and
-                    // then intersect with the total screen rect, to minimize the
-                    // allocation size.
-                    let device_rect = prim_screen_rect
-                        .clipped
-                        .inflate(blur_range, blur_range)
-                        .intersection(&prim_screen_rect.unclipped)
-                        .unwrap();
+                let picture_task = RenderTask::new_picture(
+                    RenderTaskLocation::Dynamic(None, device_rect.size),
+                    prim_index,
+                    device_rect.origin,
+                    pic_state_for_children.tasks,
+                );
+
+                let picture_task_id = frame_state.render_tasks.add(picture_task);
 
-                    let picture_task = RenderTask::new_picture(
-                        RenderTaskLocation::Dynamic(None, device_rect.size),
-                        prim_index,
-                        device_rect.origin,
-                        pic_state_for_children.tasks,
-                    );
-
-                    let picture_task_id = frame_state.render_tasks.add(picture_task);
+                let blur_render_task = RenderTask::new_blur(
+                    blur_std_deviation,
+                    picture_task_id,
+                    frame_state.render_tasks,
+                    RenderTargetKind::Color,
+                    ClearMode::Transparent,
+                );
 
-                    let blur_render_task = RenderTask::new_blur(
-                        blur_std_deviation,
-                        picture_task_id,
-                        frame_state.render_tasks,
-                        RenderTargetKind::Color,
-                        ClearMode::Transparent,
-                    );
+                let render_task_id = frame_state.render_tasks.add(blur_render_task);
+                pic_state.tasks.push(render_task_id);
+                self.surface = Some(render_task_id);
 
-                    let render_task_id = frame_state.render_tasks.add(blur_render_task);
-                    pic_state.tasks.push(render_task_id);
-                    self.surface = Some(render_task_id);
-
-                    Some(device_rect)
-                }
+                Some(device_rect)
             }
             Some(PictureCompositeMode::Filter(FilterOp::DropShadow(_, blur_radius, _))) => {
                 let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
                 let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
 
                 // The clipped field is the part of the picture that is visible
                 // on screen. The unclipped field is the screen-space rect of
                 // the complete picture, if no screen / clip-chain was applied
@@ -301,77 +313,76 @@ impl PicturePrimitive {
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
                 pic_state.tasks.push(render_task_id);
                 self.surface = Some(render_task_id);
 
                 Some(prim_screen_rect.clipped)
             }
             Some(PictureCompositeMode::Filter(filter)) => {
-                // If this filter is not currently going to affect
-                // the picture, just collapse this picture into the
-                // current render task. This most commonly occurs
-                // when opacity == 1.0, but can also occur on other
-                // filters and be a significant performance win.
-                if filter.is_noop() {
-                    pic_state.tasks.extend(pic_state_for_children.tasks);
-                    self.surface = None;
-
-                    None
-                } else {
-                    let device_rect = match filter {
-                        FilterOp::ColorMatrix(m) => {
-                            if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
-                                for i in 0..5 {
-                                    request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
-                                }
+                let device_rect = match filter {
+                    FilterOp::ColorMatrix(m) => {
+                        if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
+                            for i in 0..5 {
+                                request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
                             }
-
-                            None
                         }
-                        _ => {
-                            Some(prim_screen_rect.clipped)
-                        }
-                    };
+
+                        None
+                    }
+                    _ => Some(prim_screen_rect.clipped),
+                };
 
-                    let picture_task = RenderTask::new_picture(
-                        RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
-                        prim_index,
-                        prim_screen_rect.clipped.origin,
-                        pic_state_for_children.tasks,
-                    );
+                let picture_task = RenderTask::new_picture(
+                    RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
+                    prim_index,
+                    prim_screen_rect.clipped.origin,
+                    pic_state_for_children.tasks,
+                );
 
-                    let render_task_id = frame_state.render_tasks.add(picture_task);
-                    pic_state.tasks.push(render_task_id);
-                    self.surface = Some(render_task_id);
+                let render_task_id = frame_state.render_tasks.add(picture_task);
+                pic_state.tasks.push(render_task_id);
+                self.surface = Some(render_task_id);
 
-                    device_rect
-                }
+                device_rect
             }
-            Some(PictureCompositeMode::Blit) => {
+            Some(PictureCompositeMode::Blit) | None => {
                 let picture_task = RenderTask::new_picture(
                     RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
                     prim_index,
                     prim_screen_rect.clipped.origin,
                     pic_state_for_children.tasks,
                 );
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
                 pic_state.tasks.push(render_task_id);
                 self.surface = Some(render_task_id);
 
                 Some(prim_screen_rect.clipped)
             }
-            None => {
-                pic_state.tasks.extend(pic_state_for_children.tasks);
-                self.surface = None;
+        }
+    }
 
-                None
-            }
-        };
+    pub fn prepare_for_render(
+        &mut self,
+        prim_index: PrimitiveIndex,
+        prim_metadata: &mut PrimitiveMetadata,
+        pic_state_for_children: PictureState,
+        pic_state: &mut PictureState,
+        frame_context: &FrameBuildingContext,
+        frame_state: &mut FrameBuildingState,
+    ) {
+        let device_rect = self.prepare_for_render_inner(
+            prim_index,
+            prim_metadata,
+            pic_state_for_children,
+            pic_state,
+            frame_context,
+            frame_state,
+        );
 
         // If this picture type uses the common / general GPU data
         // format, then write it now.
         if let Some(device_rect) = device_rect {
             // If scrolling or property animation has resulted in the task
             // rect being different than last time, invalidate the GPU
             // cache entry for this picture to ensure that the correct
             // task rect is provided to the image shader.
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -142,26 +142,24 @@ fn get_glyph_metrics(
     left -= 1;
     bottom -= 1;
     right += 1;
     top += 1;
 
     let width = right - left;
     let height = top - bottom;
 
-    let metrics = GlyphMetrics {
+    GlyphMetrics {
         rasterized_left: left,
         rasterized_width: width as u32,
         rasterized_height: height as u32,
         rasterized_ascent: top,
         rasterized_descent: -bottom,
         advance: advance.width as f32,
-    };
-
-    metrics
+    }
 }
 
 #[link(name = "ApplicationServices", kind = "framework")]
 extern {
     static kCTFontVariationAxisIdentifierKey: CFStringRef;
     static kCTFontVariationAxisNameKey: CFStringRef;
     static kCTFontVariationAxisMinimumValueKey: CFStringRef;
     static kCTFontVariationAxisMaximumValueKey: CFStringRef;
@@ -448,17 +446,17 @@ impl FontContext {
 
             for pixel in data[current_height .. current_height + (width * 4)].chunks(4) {
                 let b = pixel[0];
                 let g = pixel[1];
                 let r = pixel[2];
                 let a = pixel[3];
                 print!("({}, {}, {}, {}) ", r, g, b, a);
             }
-            println!("");
+            println!();
         }
     }
 
     pub fn prepare_font(font: &mut FontInstance) {
         match font.render_mode {
             FontRenderMode::Mono => {
                 // In mono mode the color of the font is irrelevant.
                 font.color = ColorU::new(255, 255, 255, 255);
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -208,16 +208,20 @@ pub enum BrushKind {
         // A local space offset to apply when drawing
         // this picture.
         local_offset: LayerVector2D,
     },
     Image {
         request: ImageRequest,
         current_epoch: Epoch,
         alpha_type: AlphaType,
+        stretch_size: LayerSize,
+        tile_spacing: LayerSize,
+        source: ImageSource,
+        sub_rect: Option<DeviceIntRect>,
     },
     YuvImage {
         yuv_key: [ImageKey; 3],
         format: YuvFormat,
         color_space: YuvColorSpace,
         image_rendering: ImageRendering,
     },
     RadialGradient {
@@ -628,17 +632,16 @@ impl<'a> GradientGpuBlockBuilder<'a> {
     }
 }
 
 #[derive(Debug, Clone)]
 pub struct TextRunPrimitiveCpu {
     pub font: FontInstance,
     pub offset: LayerVector2D,
     pub glyph_range: ItemRange<GlyphInstance>,
-    pub glyph_count: usize,
     pub glyph_keys: Vec<GlyphKey>,
     pub glyph_gpu_blocks: Vec<GpuBlockData>,
     pub shadow: bool,
 }
 
 impl TextRunPrimitiveCpu {
     pub fn get_font(
         &self,
@@ -967,17 +970,16 @@ impl PrimitiveContainer {
                 PrimitiveContainer::TextRun(TextRunPrimitiveCpu {
                     font: FontInstance {
                         color: shadow.color.into(),
                         render_mode,
                         ..info.font.clone()
                     },
                     offset: info.offset + shadow.offset,
                     glyph_range: info.glyph_range,
-                    glyph_count: info.glyph_count,
                     glyph_keys: info.glyph_keys.clone(),
                     glyph_gpu_blocks: Vec::new(),
                     shadow: true,
                 })
             }
             PrimitiveContainer::Brush(ref brush) => {
                 match brush.kind {
                     BrushKind::Solid { .. } => {
@@ -1046,29 +1048,28 @@ impl PrimitiveStore {
         &mut self,
         composite_mode: Option<PictureCompositeMode>,
         is_in_3d_context: bool,
         pipeline_id: PipelineId,
         reference_frame_index: ClipScrollNodeIndex,
         frame_output_pipeline_id: Option<PipelineId>,
         apply_local_clip_rect: bool,
     ) -> PictureIndex {
-        let pic = PicturePrimitive::new_image(
+        let picture = PicturePrimitive::new_image(
             composite_mode,
             is_in_3d_context,
             pipeline_id,
             reference_frame_index,
             frame_output_pipeline_id,
             apply_local_clip_rect,
         );
 
-        let pic_index = PictureIndex(self.pictures.len());
-        self.pictures.push(pic);
-
-        pic_index
+        let picture_index = PictureIndex(self.pictures.len());
+        self.pictures.push(picture);
+        picture_index
     }
 
     pub fn add_primitive(
         &mut self,
         local_rect: &LayerRect,
         local_clip_rect: &LayerRect,
         is_backface_visible: bool,
         clip_sources: Option<ClipSourcesHandle>,
@@ -1296,34 +1297,111 @@ impl PrimitiveStore {
                         );
                     }
                 }
             }
             PrimitiveKind::Brush => {
                 let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0];
 
                 match brush.kind {
-                    BrushKind::Image { request, ref mut current_epoch, .. } => {
+                    BrushKind::Image { request, sub_rect, ref mut current_epoch, ref mut source, .. } => {
                         let image_properties = frame_state
                             .resource_cache
                             .get_image_properties(request.key);
 
+                        // Set if we need to request the source image from the cache this frame.
                         if let Some(image_properties) = image_properties {
                             // See if this image has been updated since we last hit this code path.
                             // If so, we need to update the opacity.
                             if image_properties.epoch != *current_epoch {
                                 *current_epoch = image_properties.epoch;
                                 metadata.opacity.is_opaque = image_properties.descriptor.is_opaque;
                             }
+
+                            // Work out whether this image is a normal / simple type, or if
+                            // we need to pre-render it to the render task cache.
+                            if let Some(rect) = sub_rect {
+                                *source = ImageSource::Cache {
+                                    // Size in device-pixels we need to allocate in render task cache.
+                                    size: rect.size,
+                                    item: CacheItem::invalid(),
+                                };
+                            }
+
+                            let mut request_source_image = false;
+
+                            // Every frame, for cached items, we need to request the render
+                            // task cache item. The closure will be invoked on the first
+                            // time through, and any time the render task output has been
+                            // evicted from the texture cache.
+                            match *source {
+                                ImageSource::Cache { size, ref mut item } => {
+                                    let image_cache_key = ImageCacheKey {
+                                        request,
+                                        texel_rect: sub_rect,
+                                    };
+
+                                    // Request a pre-rendered image task.
+                                    *item = frame_state.resource_cache.request_render_task(
+                                        RenderTaskCacheKey {
+                                            size,
+                                            kind: RenderTaskCacheKeyKind::Image(image_cache_key),
+                                        },
+                                        frame_state.gpu_cache,
+                                        frame_state.render_tasks,
+                                        None,
+                                        |render_tasks| {
+                                            // We need to render the image cache this frame,
+                                            // so will need access to the source texture.
+                                            request_source_image = true;
+
+                                            // Create a task to blit from the texture cache to
+                                            // a normal transient render task surface. This will
+                                            // copy only the sub-rect, if specified.
+                                            let cache_to_target_task = RenderTask::new_blit(
+                                                size,
+                                                BlitSource::Image { key: image_cache_key },
+                                            );
+                                            let cache_to_target_task_id = render_tasks.add(cache_to_target_task);
+
+                                            // Create a task to blit the rect from the child render
+                                            // task above back into the right spot in the persistent
+                                            // render target cache.
+                                            let target_to_cache_task = RenderTask::new_blit(
+                                                size,
+                                                BlitSource::RenderTask {
+                                                    task_id: cache_to_target_task_id,
+                                                },
+                                            );
+                                            let target_to_cache_task_id = render_tasks.add(target_to_cache_task);
+
+                                            // Hook this into the render task tree at the right spot.
+                                            pic_state.tasks.push(target_to_cache_task_id);
+
+                                            // Pass the image opacity, so that the cached render task
+                                            // item inherits the same opacity properties.
+                                            (target_to_cache_task_id, image_properties.descriptor.is_opaque)
+                                        }
+                                    );
+
+                                }
+                                ImageSource::Default => {
+                                    // Normal images just reference the source texture each frame.
+                                    request_source_image = true;
+                                }
+                            }
+
+                            if request_source_image {
+                                frame_state.resource_cache.request_image(
+                                    request,
+                                    frame_state.gpu_cache,
+                                );
+                            }
                         }
 
-                        frame_state.resource_cache.request_image(
-                            request,
-                            frame_state.gpu_cache,
-                        );
                     }
                     BrushKind::YuvImage { format, yuv_key, image_rendering, .. } => {
                         let channel_num = format.get_plane_num();
                         debug_assert!(channel_num <= 3);
                         for channel in 0 .. channel_num {
                             frame_state.resource_cache.request_image(
                                 ImageRequest {
                                     key: yuv_key[channel],
@@ -1459,51 +1537,65 @@ impl PrimitiveStore {
                 }
             }
             None => {
                 // If no segment descriptor built yet, see if it is a brush
                 // type that wants to be segmented.
                 if !brush.kind.supports_segments() {
                     return;
                 }
-                if metadata.local_rect.size.area() <= MIN_BRUSH_SPLIT_AREA {
-                    return;
-                }
             }
         }
 
+        // If the brush is small, we generally want to skip building segments
+        // and just draw it as a single primitive with clip mask. However,
+        // if the clips are purely rectangles that have no per-fragment
+        // clip masks, we will segment anyway. This allows us to completely
+        // skip allocating a clip mask in these cases.
+        let is_large = metadata.local_rect.size.area() > MIN_BRUSH_SPLIT_AREA;
+
+        // TODO(gw): We should probably detect and store this on each
+        //           ClipSources instance, to avoid having to iterate
+        //           the clip sources here.
+        let mut rect_clips_only = true;
+
         let mut segment_builder = SegmentBuilder::new(
             metadata.local_rect,
             None,
             metadata.local_clip_rect
         );
 
         // If this primitive is clipped by clips from a different coordinate system, then we
         // need to apply a clip mask for the entire primitive.
-        let mut clip_mask_kind = match has_clips_from_other_coordinate_systems {
-            true => BrushClipMaskKind::Global,
-            false => BrushClipMaskKind::Individual,
+        let mut clip_mask_kind = if has_clips_from_other_coordinate_systems {
+            BrushClipMaskKind::Global
+        } else {
+            BrushClipMaskKind::Individual
         };
 
         // Segment the primitive on all the local-space clip sources that we can.
         for clip_item in clips {
             if clip_item.coordinate_system_id != prim_run_context.scroll_node.coordinate_system_id {
                 continue;
             }
 
             let local_clips = frame_state.clip_store.get_opt(&clip_item.clip_sources).expect("bug");
             for &(ref clip, _) in &local_clips.clips {
                 let (local_clip_rect, radius, mode) = match *clip {
                     ClipSource::RoundedRectangle(rect, radii, clip_mode) => {
+                        rect_clips_only = false;
+
                         (rect, Some(radii), clip_mode)
                     }
-                    ClipSource::Rectangle(rect) => {
-                        (rect, None, ClipMode::Clip)
+                    ClipSource::Rectangle(rect, mode) => {
+                        (rect, None, mode)
                     }
                     ClipSource::BoxShadow(ref info) => {
+                        rect_clips_only = false;
+
                         // For inset box shadows, we can clip out any
                         // pixels that are inside the shadow region
                         // and are beyond the inner rect, as they can't
                         // be affected by the blur radius.
                         let inner_clip_mode = match info.clip_mode {
                             BoxShadowClipMode::Outset => None,
                             BoxShadowClipMode::Inset => Some(ClipMode::ClipOut),
                         };
@@ -1521,16 +1613,18 @@ impl PrimitiveStore {
                             inner_clip_mode,
                         );
 
                         continue;
                     }
                     ClipSource::BorderCorner(..) |
                     ClipSource::LineDecoration(..) |
                     ClipSource::Image(..) => {
+                        rect_clips_only = false;
+
                         // TODO(gw): We can easily extend the segment builder
                         //           to support these clip sources in the
                         //           future, but they are rarely used.
                         clip_mask_kind = BrushClipMaskKind::Global;
                         continue;
                     }
                 };
 
@@ -1552,42 +1646,44 @@ impl PrimitiveStore {
 
                     relative_transform.transform_rect(&local_clip_rect)
                 };
 
                 segment_builder.push_clip_rect(local_clip_rect, radius, mode);
             }
         }
 
-        match brush.segment_desc {
-            Some(ref mut segment_desc) => {
-                segment_desc.clip_mask_kind = clip_mask_kind;
-            }
-            None => {
-                // TODO(gw): We can probably make the allocation
-                //           patterns of this and the segment
-                //           builder significantly better, by
-                //           retaining it across primitives.
-                let mut segments = Vec::new();
+        if is_large || rect_clips_only {
+            match brush.segment_desc {
+                Some(ref mut segment_desc) => {
+                    segment_desc.clip_mask_kind = clip_mask_kind;
+                }
+                None => {
+                    // TODO(gw): We can probably make the allocation
+                    //           patterns of this and the segment
+                    //           builder significantly better, by
+                    //           retaining it across primitives.
+                    let mut segments = Vec::new();
 
-                segment_builder.build(|segment| {
-                    segments.push(
-                        BrushSegment::new(
-                            segment.rect.origin,
-                            segment.rect.size,
-                            segment.has_mask,
-                            segment.edge_flags,
-                        ),
-                    );
-                });
+                    segment_builder.build(|segment| {
+                        segments.push(
+                            BrushSegment::new(
+                                segment.rect.origin,
+                                segment.rect.size,
+                                segment.has_mask,
+                                segment.edge_flags,
+                            ),
+                        );
+                    });
 
-                brush.segment_desc = Some(BrushSegmentDescriptor {
-                    segments,
-                    clip_mask_kind,
-                });
+                    brush.segment_desc = Some(BrushSegmentDescriptor {
+                        segments,
+                        clip_mask_kind,
+                    });
+                }
             }
         }
     }
 
     fn update_clip_task_for_brush(
         &mut self,
         prim_run_context: &PrimitiveRunContext,
         prim_index: PrimitiveIndex,
@@ -1693,17 +1789,17 @@ impl PrimitiveStore {
         let mut combined_outer_rect =
             prim_screen_rect.intersection(&prim_run_context.clip_chain.combined_outer_screen_rect);
         let clip_chain = prim_run_context.clip_chain.nodes.clone();
 
         let prim_coordinate_system_id = prim_run_context.scroll_node.coordinate_system_id;
         let transform = &prim_run_context.scroll_node.world_content_transform;
         let extra_clip =  {
             let metadata = &self.cpu_metadata[prim_index.0];
-            metadata.clip_sources.as_ref().map(|ref clip_sources| {
+            metadata.clip_sources.as_ref().map(|clip_sources| {
                 let prim_clips = frame_state.clip_store.get_mut(clip_sources);
                 prim_clips.update(
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_context.device_pixel_scale,
                 );
                 let (screen_inner_rect, screen_outer_rect) =
                     prim_clips.get_screen_bounds(transform, frame_context.device_pixel_scale);
@@ -2118,17 +2214,17 @@ fn convert_clip_chain_to_clip_vector(
         .chain(ClipChainNodeIter { current: clip_chain_nodes })
         .filter_map(|node| {
             *has_clips_from_other_coordinate_systems |=
                 prim_coordinate_system != node.work_item.coordinate_system_id;
 
             *combined_inner_rect = if !node.screen_inner_rect.is_empty() {
                 // If this clip's inner area contains the area of the primitive clipped
                 // by previous clips, then it's not going to affect rendering in any way.
-                if node.screen_inner_rect.contains_rect(&combined_outer_rect) {
+                if node.screen_inner_rect.contains_rect(combined_outer_rect) {
                     return None;
                 }
                 combined_inner_rect.intersection(&node.screen_inner_rect)
                     .unwrap_or_else(DeviceIntRect::zero)
             } else {
                 DeviceIntRect::zero()
             };
 
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ApiMsg, BuiltDisplayList, ClearCache, DebugCommand};
 #[cfg(feature = "debugger")]
 use api::{BuiltDisplayListIter, SpecificDisplayItem};
 use api::{DeviceIntPoint, DevicePixelScale, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
 use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestResult};
 use api::{IdNamespace, LayerPoint, PipelineId, RenderNotifier, SceneMsg, ScrollClamping};
-use api::{ScrollEventPhase, ScrollLocation, ScrollNodeState, TransactionMsg, WorldPoint};
+use api::{ScrollLocation, ScrollNodeState, TransactionMsg, WorldPoint};
 use api::channel::{MsgReceiver, Payload};
 #[cfg(feature = "capture")]
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
 use clip_scroll_tree::ClipScrollTree;
 #[cfg(feature = "debugger")]
 use debug_server;
@@ -259,16 +259,17 @@ impl Document {
         };
 
         scene_tx.send(SceneBuilderRequest::Transaction {
             scene: scene_request,
             resource_updates: transaction_msg.resource_updates,
             frame_ops: transaction_msg.frame_ops,
             render: transaction_msg.generate_frame,
             document_id,
+            current_epochs: self.current.scene.pipeline_epochs.clone(),
         }).unwrap();
     }
 
     fn render(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         resource_profile: &mut ResourceProfileCounters,
@@ -295,56 +296,49 @@ impl Document {
             self.hit_tester = Some(frame_builder.create_hit_tester(&self.clip_scroll_tree));
             frame
         };
 
         self.make_rendered_document(frame, removed_pipelines)
     }
 
     pub fn make_rendered_document(&mut self, frame: Frame, removed_pipelines: Vec<PipelineId>) -> RenderedDocument {
-        let nodes_bouncing_back = self.clip_scroll_tree.collect_nodes_bouncing_back();
         RenderedDocument::new(
             PipelineInfo {
                 epochs: self.current.scene.pipeline_epochs.clone(),
                 removed_pipelines,
             },
-            nodes_bouncing_back,
             frame
         )
     }
 
     pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
         self.clip_scroll_tree
             .discard_frame_state_for_pipeline(pipeline_id);
     }
 
     /// Returns true if any nodes actually changed position or false otherwise.
     pub fn scroll(
         &mut self,
         scroll_location: ScrollLocation,
         cursor: WorldPoint,
-        phase: ScrollEventPhase,
     ) -> bool {
-        self.clip_scroll_tree.scroll(scroll_location, cursor, phase)
+        self.clip_scroll_tree.scroll(scroll_location, cursor)
     }
 
     /// Returns true if the node actually changed position or false otherwise.
     pub fn scroll_node(
         &mut self,
         origin: LayerPoint,
         id: ExternalScrollId,
         clamp: ScrollClamping
     ) -> bool {
         self.clip_scroll_tree.scroll_node(origin, id, clamp)
     }
 
-    pub fn tick_scrolling_bounce_animations(&mut self) {
-        self.clip_scroll_tree.tick_scrolling_bounce_animations();
-    }
-
     pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
         self.clip_scroll_tree.get_scroll_node_state()
     }
 
     pub fn new_async_scene_ready(&mut self, mut built_scene: BuiltScene) {
         self.current.scene = built_scene.scene;
 
         self.frame_builder = Some(built_scene.frame_builder);
@@ -616,20 +610,20 @@ impl RenderBackend {
             FrameMsg::EnableFrameOutput(pipeline_id, enable) => {
                 if enable {
                     doc.output_pipelines.insert(pipeline_id);
                 } else {
                     doc.output_pipelines.remove(&pipeline_id);
                 }
                 DocumentOps::nop()
             }
-            FrameMsg::Scroll(delta, cursor, move_phase) => {
+            FrameMsg::Scroll(delta, cursor) => {
                 profile_scope!("Scroll");
 
-                let should_render = doc.scroll(delta, cursor, move_phase)
+                let should_render = doc.scroll(delta, cursor)
                     && doc.render_on_scroll == Some(true);
 
                 DocumentOps {
                     scroll: true,
                     render: should_render,
                     composite: should_render,
                     ..DocumentOps::nop()
                 }
@@ -658,30 +652,16 @@ impl RenderBackend {
 
                 DocumentOps {
                     scroll: true,
                     render: should_render,
                     composite: should_render,
                     ..DocumentOps::nop()
                 }
             }
-            FrameMsg::TickScrollingBounce => {
-                profile_scope!("TickScrollingBounce");
-
-                doc.tick_scrolling_bounce_animations();
-
-                let should_render = doc.render_on_scroll == Some(true);
-
-                DocumentOps {
-                    scroll: true,
-                    render: should_render,
-                    composite: should_render,
-                    ..DocumentOps::nop()
-                }
-            }
             FrameMsg::GetScrollNodeState(tx) => {
                 profile_scope!("GetScrollNodeState");
                 tx.send(doc.get_scroll_node_state()).unwrap();
                 DocumentOps::nop()
             }
             FrameMsg::UpdateDynamicProperties(property_bindings) => {
                 doc.dynamic_properties.set_properties(property_bindings);
                 DocumentOps::render()
@@ -703,26 +683,29 @@ impl RenderBackend {
             while let Ok(msg) = self.scene_rx.try_recv() {
                 match msg {
                     SceneBuilderResult::Transaction {
                         document_id,
                         mut built_scene,
                         resource_updates,
                         frame_ops,
                         render,
+                        result_tx,
                     } => {
                         if let Some(doc) = self.documents.get_mut(&document_id) {
                             if let Some(mut built_scene) = built_scene.take() {
                                 doc.new_async_scene_ready(built_scene);
                                 doc.render_on_hittest = true;
                             }
+                            result_tx.send(SceneSwapResult::Complete).unwrap();
                         } else {
                             // The document was removed while we were building it, skip it.
                             // TODO: we might want to just ensure that removed documents are
                             // always forwarded to the scene builder thread to avoid this case.
+                            result_tx.send(SceneSwapResult::Aborted).unwrap();
                             continue;
                         }
 
                         let transaction_msg = TransactionMsg {
                             scene_ops: Vec::new(),
                             frame_ops,
                             resource_updates,
                             generate_frame: render,
@@ -759,16 +742,19 @@ impl RenderBackend {
     fn process_api_msg(
         &mut self,
         msg: ApiMsg,
         profile_counters: &mut BackendProfileCounters,
         frame_counter: &mut u32,
     ) -> bool {
         match msg {
             ApiMsg::WakeUp => {}
+            ApiMsg::WakeSceneBuilder => {
+                self.scene_tx.send(SceneBuilderRequest::WakeUp).unwrap();
+            }
             ApiMsg::UpdateResources(updates) => {
                 self.resource_cache
                     .update_resources(updates, &mut profile_counters.resources);
             }
             ApiMsg::GetGlyphDimensions(instance_key, glyph_keys, tx) => {
                 let mut glyph_dimensions = Vec::with_capacity(glyph_keys.len());
                 if let Some(font) = self.resource_cache.get_font_instance(instance_key) {
                     for glyph_key in &glyph_keys {
@@ -924,17 +910,17 @@ impl RenderBackend {
                     document_id,
                     scene_msg,
                     *frame_counter,
                     &mut profile_counters.ipc,
                 )
             );
         }
 
-        if transaction_msg.use_scene_builder_thread && !transaction_msg.is_empty() {
+        if transaction_msg.use_scene_builder_thread {
             let doc = self.documents.get_mut(&document_id).unwrap();
             doc.forward_transaction_to_scene_builder(
                 transaction_msg,
                 &op,
                 document_id,
                 &self.resource_cache,
                 &self.scene_tx,
             );
@@ -1176,17 +1162,17 @@ impl RenderBackend {
             }
             if config.bits.contains(CaptureBits::FRAME) {
                 let rendered_document = doc.render(
                     &mut self.resource_cache,
                     &mut self.gpu_cache,
                     &mut profile_counters.resources,
                 );
                 //TODO: write down full `RenderedDocument`?
-                // it has `pipeline_epoch_map` and `layers_bouncing_back`,
+                // it has `pipeline_epoch_map`,
                 // which may capture necessary details for some cases.
                 let file_name = format!("frame-{}-{}", (id.0).0, id.1);
                 config.serialize(&rendered_document.frame, file_name);
             }
         }
 
         debug!("\tresource cache");
         let (resources, deferred) = self.resource_cache.save_capture(&config.root);
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -503,29 +503,27 @@ impl RenderTask {
                 uv_rect_handle: GpuCacheHandle::new(),
             }),
             clear_mode,
             saved_index: None,
         };
 
         let blur_task_v_id = render_tasks.add(blur_task_v);
 
-        let blur_task_h = RenderTask {
+        RenderTask {
             children: vec![blur_task_v_id],
             location: RenderTaskLocation::Dynamic(None, adjusted_blur_target_size),
             kind: RenderTaskKind::HorizontalBlur(BlurTask {
                 blur_std_deviation: adjusted_blur_std_deviation,
                 target_kind,
                 uv_rect_handle: GpuCacheHandle::new(),
             }),
             clear_mode,
             saved_index: None,
-        };
-
-        blur_task_h
+        }
     }
 
     pub fn new_scaling(
         target_kind: RenderTargetKind,
         src_task_id: RenderTaskId,
         target_size: DeviceIntSize,
     ) -> Self {
         RenderTask {
@@ -927,17 +925,17 @@ impl RenderTaskCache {
         // or create one.
         let cache_entry = self.entries
                               .entry(key)
                               .or_insert(RenderTaskCacheEntry {
                                   handle: TextureCacheHandle::new(),
                               });
 
         // Check if this texture cache handle is valid.
-        if texture_cache.request(&mut cache_entry.handle, gpu_cache) {
+        if texture_cache.request(&cache_entry.handle, gpu_cache) {
             // Invoke user closure to get render task chain
             // to draw this into the texture cache.
             let (render_task_id, is_opaque) = try!(f(render_tasks));
             let render_task = &mut render_tasks[render_task_id];
 
             // Select the right texture page to allocate from.
             let image_format = match render_task.target_kind() {
                 RenderTargetKind::Color => ImageFormat::BGRA8,
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -1017,18 +1017,18 @@ impl CacheTexture {
             }
         }
     }
 
     fn update(&mut self, device: &mut Device, updates: &GpuCacheUpdateList) {
         match self.bus {
             CacheBus::PixelBuffer { ref mut rows, ref mut cpu_blocks, .. } => {
                 for update in &updates.updates {
-                    match update {
-                        &GpuCacheUpdate::Copy {
+                    match *update {
+                        GpuCacheUpdate::Copy {
                             block_index,
                             block_count,
                             address,
                         } => {
                             let row = address.v as usize;
 
                             // Ensure that the CPU-side shadow copy of the GPU cache data has enough
                             // rows to apply this patch.
@@ -1061,18 +1061,18 @@ impl CacheTexture {
             } => {
                 //TODO: re-use this heap allocation
                 // Unused positions will be left as 0xFFFF, which translates to
                 // (1.0, 1.0) in the vertex output position and gets culled out
                 let mut position_data = vec![[!0u16; 2]; updates.blocks.len()];
                 let size = self.texture.get_dimensions().to_usize();
 
                 for update in &updates.updates {
-                    match update {
-                        &GpuCacheUpdate::Copy {
+                    match *update {
+                        GpuCacheUpdate::Copy {
                             block_index,
                             block_count,
                             address,
                         } => {
                             // Convert the absolute texel position into normalized
                             let y = ((2*address.v as usize + 1) << 15) / size.height;
                             for i in 0 .. block_count {
                                 let x = ((2*address.u as usize + 2*i + 1) << 15) / size.width;
@@ -1602,22 +1602,25 @@ impl Renderer {
                     .build();
                 Arc::new(worker.unwrap())
             });
         let enable_render_on_scroll = options.enable_render_on_scroll;
 
         let blob_image_renderer = options.blob_image_renderer.take();
         let thread_listener_for_render_backend = thread_listener.clone();
         let thread_listener_for_scene_builder = thread_listener.clone();
-        let renderer_id_for_render_backend = options.renderer_id.clone();
+        let scene_builder_hooks = options.scene_builder_hooks;
         let rb_thread_name = format!("WRRenderBackend#{}", options.renderer_id.unwrap_or(0));
         let scene_thread_name = format!("WRSceneBuilder#{}", options.renderer_id.unwrap_or(0));
         let glyph_rasterizer = GlyphRasterizer::new(workers)?;
 
-        let (scene_builder, scene_tx, scene_rx) = SceneBuilder::new(config, api_tx.clone());
+        let (scene_builder, scene_tx, scene_rx) = SceneBuilder::new(
+            config,
+            api_tx.clone(),
+            scene_builder_hooks);
         thread::Builder::new().name(scene_thread_name.clone()).spawn(move || {
             register_thread_with_profiler(scene_thread_name.clone());
             if let Some(ref thread_listener) = *thread_listener_for_scene_builder {
                 thread_listener.thread_started(&scene_thread_name);
             }
 
             let mut scene_builder = scene_builder;
             scene_builder.run();
@@ -1625,17 +1628,16 @@ impl Renderer {
             if let Some(ref thread_listener) = *thread_listener_for_scene_builder {
                 thread_listener.thread_stopped(&scene_thread_name);
             }
         })?;
 
         thread::Builder::new().name(rb_thread_name.clone()).spawn(move || {
             register_thread_with_profiler(rb_thread_name.clone());
             if let Some(ref thread_listener) = *thread_listener_for_render_backend {
-                thread_listener.new_render_backend_thread(renderer_id_for_render_backend);
                 thread_listener.thread_started(&rb_thread_name);
             }
 
             let texture_cache = TextureCache::new(max_device_size);
             let resource_cache = ResourceCache::new(
                 texture_cache,
                 glyph_rasterizer,
                 blob_image_renderer,
@@ -2314,22 +2316,16 @@ impl Renderer {
 
         if self.renderer_errors.is_empty() {
             Ok(stats)
         } else {
             Err(mem::replace(&mut self.renderer_errors, Vec::new()))
         }
     }
 
-    pub fn layers_are_bouncing_back(&self) -> bool {
-        self.active_documents
-            .iter()
-            .any(|&(_, ref render_doc)| !render_doc.layers_bouncing_back.is_empty())
-    }
-
     fn update_gpu_cache(&mut self) {
         let _gm = self.gpu_profile.start_marker("gpu cache update");
 
         // For an artificial stress test of GPU cache resizing,
         // always pass an extra update list with at least one block in it.
         let gpu_cache_height = self.gpu_cache_texture.get_height();
         if gpu_cache_height != 0 && GPU_CACHE_RESIZE_TEST {
             self.pending_gpu_cache_updates.push(GpuCacheUpdateList {
@@ -2844,17 +2840,17 @@ impl Renderer {
                 for batch in alpha_batch_container
                     .opaque_batches
                     .iter()
                     .rev()
                 {
                     self.submit_batch(
                         &batch.key,
                         &batch.instances,
-                        &projection,
+                        projection,
                         render_tasks,
                         render_target,
                         target_size,
                         stats,
                         alpha_batch_container.target_rect,
                     );
                 }
 
@@ -3029,17 +3025,17 @@ impl Renderer {
                                 }
                             }
                             prev_blend_mode = batch.key.blend_mode;
                         }
 
                         self.submit_batch(
                             &batch.key,
                             &batch.instances,
-                            &projection,
+                            projection,
                             render_tasks,
                             render_target,
                             target_size,
                             stats,
                             alpha_batch_container.target_rect,
                         );
                     }
                 }
@@ -3991,17 +3987,38 @@ pub trait ExternalImageHandler {
 pub trait OutputImageHandler {
     fn lock(&mut self, pipeline_id: PipelineId) -> Option<(u32, DeviceIntSize)>;
     fn unlock(&mut self, pipeline_id: PipelineId);
 }
 
 pub trait ThreadListener {
     fn thread_started(&self, thread_name: &str);
     fn thread_stopped(&self, thread_name: &str);
-    fn new_render_backend_thread(&self, renderer_id: Option<u64>);
+}
+
+/// Allows callers to hook in at certain points of the async scene build. These
+/// functions are all called from the scene builder thread.
+pub trait SceneBuilderHooks {
+    /// This is called exactly once, when the scene builder thread is started
+    /// and before it processes anything.
+    fn register(&self);
+    /// This is called before each scene swap occurs.
+    fn pre_scene_swap(&self);
+    /// This is called after each scene swap occurs. The PipelineInfo contains
+    /// the updated epochs and pipelines removed in the new scene compared to
+    /// the old scene.
+    fn post_scene_swap(&self, info: PipelineInfo);
+    /// This is a generic callback which provides an opportunity to run code
+    /// on the scene builder thread. This is called as part of the main message
+    /// loop of the scene builder thread, but outside of any specific message
+    /// handler.
+    fn poke(&self);
+    /// This is called exactly once, when the scene builder thread is about to
+    /// terminate.
+    fn deregister(&self);
 }
 
 pub struct RendererOptions {
     pub device_pixel_ratio: f32,
     pub resource_override_path: Option<PathBuf>,
     pub enable_aa: bool,
     pub enable_dithering: bool,
     pub max_recorded_profiles: usize,
@@ -4018,16 +4035,17 @@ pub struct RendererOptions {
     pub blob_image_renderer: Option<Box<BlobImageRenderer>>,
     pub recorder: Option<Box<ApiRecordingReceiver>>,
     pub thread_listener: Option<Box<ThreadListener + Send + Sync>>,
     pub enable_render_on_scroll: bool,
     pub cached_programs: Option<Rc<ProgramCache>>,
     pub debug_flags: DebugFlags,
     pub renderer_id: Option<u64>,
     pub disable_dual_source_blending: bool,
+    pub scene_builder_hooks: Option<Box<SceneBuilderHooks + Send>>,
 }
 
 impl Default for RendererOptions {
     fn default() -> Self {
         RendererOptions {
             device_pixel_ratio: 1.0,
             resource_override_path: None,
             enable_aa: true,
@@ -4049,16 +4067,17 @@ impl Default for RendererOptions {
             workers: None,
             blob_image_renderer: None,
             recorder: None,
             thread_listener: None,
             enable_render_on_scroll: true,
             renderer_id: None,
             cached_programs: None,
             disable_dual_source_blending: false,
+            scene_builder_hooks: None,
         }
     }
 }
 
 #[cfg(not(feature = "debugger"))]
 pub struct DebugServer;
 
 #[cfg(not(feature = "debugger"))]
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -592,17 +592,17 @@ impl ResourceCache {
                         texture_cache_handle: TextureCacheHandle::new(),
                     }
                 )),
                 true,
             ),
         };
 
         let needs_upload = self.texture_cache
-            .request(&mut entry.as_mut().unwrap().texture_cache_handle, gpu_cache);
+            .request(&entry.as_ref().unwrap().texture_cache_handle, gpu_cache);
 
         if !needs_upload && !needs_update {
             return;
         }
 
         // We can start a worker thread rasterizing right now, if:
         //  - The image is a blob.
         //  - The blob hasn't already been requested this frame.
@@ -853,17 +853,17 @@ impl ResourceCache {
             })
             .collect()
     }
 
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         debug_assert_eq!(self.state, State::Idle);
         self.state = State::AddResources;
         self.texture_cache.begin_frame(frame_id);
-        self.cached_glyphs.begin_frame(&mut self.texture_cache, &self.cached_render_tasks);
+        self.cached_glyphs.begin_frame(&self.texture_cache, &self.cached_render_tasks);
         self.cached_render_tasks.begin_frame(&mut self.texture_cache);
         self.current_frame_id = frame_id;
     }
 
     pub fn block_until_all_resources_added(
         &mut self,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -1,46 +1,58 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdates};
+use api::{DocumentId, Epoch, PipelineId, ApiMsg, FrameMsg, ResourceUpdates};
 use api::channel::MsgSender;
 use display_list_flattener::build_scene;
 use frame_builder::{FrameBuilderConfig, FrameBuilder};
 use clip_scroll_tree::ClipScrollTree;
-use internal_types::FastHashSet;
+use internal_types::{FastHashMap, FastHashSet};
 use resource_cache::{FontInstanceMap, TiledImageMap};
 use render_backend::DocumentView;
+use renderer::{PipelineInfo, SceneBuilderHooks};
 use scene::Scene;
 use std::sync::mpsc::{channel, Receiver, Sender};
 
 // Message from render backend to scene builder.
 pub enum SceneBuilderRequest {
     Transaction {
         document_id: DocumentId,
         scene: Option<SceneRequest>,
         resource_updates: ResourceUpdates,
         frame_ops: Vec<FrameMsg>,
         render: bool,
+        current_epochs: FastHashMap<PipelineId, Epoch>,
     },
+    WakeUp,
     Stop
 }
 
 // Message from scene builder to render backend.
 pub enum SceneBuilderResult {
     Transaction {
         document_id: DocumentId,
         built_scene: Option<BuiltScene>,
         resource_updates: ResourceUpdates,
         frame_ops: Vec<FrameMsg>,
         render: bool,
+        result_tx: Sender<SceneSwapResult>,
     },
 }
 
+// Message from render backend to scene builder to indicate the
+// scene swap was completed. We need a separate channel for this
+// so that they don't get mixed with SceneBuilderRequest messages.
+pub enum SceneSwapResult {
+    Complete,
+    Aborted,
+}
+
 /// Contains the render backend data needed to build a scene.
 pub struct SceneRequest {
     pub scene: Scene,
     pub view: DocumentView,
     pub font_instances: FontInstanceMap,
     pub tiled_image_map: TiledImageMap,
     pub output_pipelines: FastHashSet<PipelineId>,
     pub removed_pipelines: Vec<PipelineId>,
@@ -53,75 +65,114 @@ pub struct BuiltScene {
     pub removed_pipelines: Vec<PipelineId>,
 }
 
 pub struct SceneBuilder {
     rx: Receiver<SceneBuilderRequest>,
     tx: Sender<SceneBuilderResult>,
     api_tx: MsgSender<ApiMsg>,
     config: FrameBuilderConfig,
+    hooks: Option<Box<SceneBuilderHooks + Send>>,
 }
 
 impl SceneBuilder {
     pub fn new(
         config: FrameBuilderConfig,
-        api_tx: MsgSender<ApiMsg>
+        api_tx: MsgSender<ApiMsg>,
+        hooks: Option<Box<SceneBuilderHooks + Send>>,
     ) -> (Self, Sender<SceneBuilderRequest>, Receiver<SceneBuilderResult>) {
         let (in_tx, in_rx) = channel();
         let (out_tx, out_rx) = channel();
         (
             SceneBuilder {
                 rx: in_rx,
                 tx: out_tx,
                 api_tx,
                 config,
+                hooks,
             },
             in_tx,
             out_rx,
         )
     }
 
     pub fn run(&mut self) {
+        if let Some(ref hooks) = self.hooks {
+            hooks.register();
+        }
+
         loop {
             match self.rx.recv() {
                 Ok(msg) => {
                     if !self.process_message(msg) {
-                        return;
+                        break;
                     }
                 }
                 Err(_) => {
-                    return;
+                    break;
                 }
             }
+
+            if let Some(ref hooks) = self.hooks {
+                hooks.poke();
+            }
+        }
+
+        if let Some(ref hooks) = self.hooks {
+            hooks.deregister();
         }
     }
 
     fn process_message(&mut self, msg: SceneBuilderRequest) -> bool {
         match msg {
+            SceneBuilderRequest::WakeUp => {}
             SceneBuilderRequest::Transaction {
                 document_id,
                 scene,
                 resource_updates,
                 frame_ops,
                 render,
+                current_epochs,
             } => {
                 let built_scene = scene.map(|request|{
                     build_scene(&self.config, request)
                 });
+                let pipeline_info = if let Some(ref built) = built_scene {
+                    PipelineInfo {
+                        epochs: built.scene.pipeline_epochs.clone(),
+                        removed_pipelines: built.removed_pipelines.clone(),
+                    }
+                } else {
+                    PipelineInfo {
+                        epochs: current_epochs,
+                        removed_pipelines: vec![],
+                    }
+                };
 
                 // TODO: pre-rasterization.
 
+                if let Some(ref hooks) = self.hooks {
+                    hooks.pre_scene_swap();
+                }
+                let (result_tx, result_rx) = channel();
                 self.tx.send(SceneBuilderResult::Transaction {
                     document_id,
                     built_scene,
                     resource_updates,
                     frame_ops,
                     render,
+                    result_tx,
                 }).unwrap();
 
                 let _ = self.api_tx.send(ApiMsg::WakeUp);
+
+                // Block until the swap is done, then invoke the hook
+                let _ = result_rx.recv();
+                if let Some(ref hooks) = self.hooks {
+                    hooks.post_scene_swap(pipeline_info);
+                }
             }
             SceneBuilderRequest::Stop => { return false; }
         }
 
         true
     }
 }
--- a/gfx/webrender/src/shade.rs
+++ b/gfx/webrender/src/shade.rs
@@ -619,44 +619,43 @@ impl Shaders {
         // All yuv_image configuration.
         let mut yuv_features = Vec::new();
         let yuv_shader_num = IMAGE_BUFFER_KINDS.len() * YUV_FORMATS.len() * YUV_COLOR_SPACES.len();
         let mut brush_yuv_image = Vec::new();
         // PrimitiveShader is not clonable. Use push() to initialize the vec.
         for _ in 0 .. yuv_shader_num {
             brush_yuv_image.push(None);
         }
-        for buffer_kind in 0 .. IMAGE_BUFFER_KINDS.len() {
-            if IMAGE_BUFFER_KINDS[buffer_kind].has_platform_support(&gl_type) {
-                for format_kind in 0 .. YUV_FORMATS.len() {
-                    for color_space_kind in 0 .. YUV_COLOR_SPACES.len() {
-                        let feature_string = IMAGE_BUFFER_KINDS[buffer_kind].get_feature_string();
+        for image_buffer_kind in &IMAGE_BUFFER_KINDS {
+            if image_buffer_kind.has_platform_support(&gl_type) {
+                for format_kind in &YUV_FORMATS {
+                    for color_space_kind in &YUV_COLOR_SPACES {
+                        let feature_string = image_buffer_kind.get_feature_string();
                         if feature_string != "" {
                             yuv_features.push(feature_string);
                         }
-                        let feature_string = YUV_FORMATS[format_kind].get_feature_string();
+                        let feature_string = format_kind.get_feature_string();
                         if feature_string != "" {
                             yuv_features.push(feature_string);
                         }
-                        let feature_string =
-                            YUV_COLOR_SPACES[color_space_kind].get_feature_string();
+                        let feature_string = color_space_kind.get_feature_string();
                         if feature_string != "" {
                             yuv_features.push(feature_string);
                         }
 
                         let shader = BrushShader::new(
                             "brush_yuv_image",
                             device,
                             &yuv_features,
                             options.precache_shaders,
                         )?;
                         let index = Self::get_yuv_shader_index(
-                            IMAGE_BUFFER_KINDS[buffer_kind],
-                            YUV_FORMATS[format_kind],
-                            YUV_COLOR_SPACES[color_space_kind],
+                            *image_buffer_kind,
+                            *format_kind,
+                            *color_space_kind,
                         );
                         brush_yuv_image[index] = Some(shader);
                         yuv_features.clear();
                     }
                 }
             }
         }
 
deleted file mode 100644
--- a/gfx/webrender/src/spring.rs
+++ /dev/null
@@ -1,110 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-use api::LayerPoint;
-
-/// Some arbitrarily small positive number used as threshold value.
-pub const EPSILON: f32 = 0.1;
-
-/// The default stiffness factor.
-pub const STIFFNESS: f32 = 0.2;
-
-/// The default damping factor.
-pub const DAMPING: f32 = 1.0;
-
-#[derive(Copy, Clone, Debug)]
-pub struct Spring {
-    /// The current position of spring.
-    cur: LayerPoint,
-    /// The position of spring at previous tick.
-    prev: LayerPoint,
-    /// The destination of spring.
-    dest: LayerPoint,
-    /// How hard it springs back.
-    stiffness: f32,
-    /// Friction. 1.0 means no bounce.
-    damping: f32,
-}
-
-impl Spring {
-    /// Create a new spring at location.
-    pub fn at(pos: LayerPoint, stiffness: f32, damping: f32) -> Spring {
-        Spring {
-            cur: pos,
-            prev: pos,
-            dest: pos,
-            stiffness,
-            damping,
-        }
-    }
-
-    /// Set coords on a spring, mutating spring
-    pub fn coords(&mut self, cur: LayerPoint, prev: LayerPoint, dest: LayerPoint) {
-        self.cur = cur;
-        self.prev = prev;
-        self.dest = dest
-    }
-
-    pub fn current(&self) -> LayerPoint {
-        self.cur
-    }
-
-    /// Run one tick of the spring animation. Return true if the animation is complete.
-    pub fn animate(&mut self) -> bool {
-        if !is_resting(self.cur.x, self.prev.x, self.dest.x) ||
-            !is_resting(self.cur.y, self.prev.y, self.dest.y)
-        {
-            let next = LayerPoint::new(
-                next(
-                    self.cur.x,
-                    self.prev.x,
-                    self.dest.x,
-                    self.stiffness,
-                    self.damping,
-                ),
-                next(
-                    self.cur.y,
-                    self.prev.y,
-                    self.dest.y,
-                    self.stiffness,
-                    self.damping,
-                ),
-            );
-            let (cur, dest) = (self.cur, self.dest);
-            self.coords(next, cur, dest);
-            false
-        } else {
-            let dest = self.dest;
-            self.coords(dest, dest, dest);
-            true
-        }
-    }
-}
-
-/// Given numbers, calculate the next position for a spring.
-fn next(cur: f32, prev: f32, dest: f32, stiffness: f32, damping: f32) -> f32 {
-    // Calculate spring force
-    let fspring = -stiffness * (cur - dest);
-
-    // Calculate velocity
-    let vel = cur - prev;
-
-    // Calculate damping force.
-    let fdamping = -damping * vel;
-
-    // Calc acceleration by adjusting spring force to damping force
-    let acc = fspring + fdamping;
-
-    // Calculate new velocity after adding acceleration. Scale to framerate.
-    let nextv = vel + acc;
-
-    // Calculate next position by integrating velocity. Scale to framerate.
-    let next = cur + nextv;
-    next
-}
-
-/// Given numbers, calculate if a spring is at rest.
-fn is_resting(cur: f32, prev: f32, dest: f32) -> bool {
-    (cur - prev).abs() < EPSILON && (cur - dest).abs() < EPSILON
-}
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -585,17 +585,17 @@ impl RenderTarget for AlphaRenderTarget 
                 );
             }
             RenderTaskKind::CacheMask(ref task_info) => {
                 let task_address = render_tasks.get_task_address(task_id);
                 self.clip_batcher.add(
                     task_address,
                     &task_info.clips,
                     task_info.coordinate_system_id,
-                    &ctx.resource_cache,
+                    ctx.resource_cache,
                     gpu_cache,
                     clip_store,
                 );
             }
             RenderTaskKind::ClipRegion(ref task) => {
                 let task_address = render_tasks.get_task_address(task_id);
                 self.clip_batcher.add_clip_region(
                     task_address,
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -278,17 +278,17 @@ pub fn recycle_vec<T>(mut old_vec: Vec<T
         // Avoid reusing the buffer if it is a lot larger than it needs to be. This prevents
         // a frame with exceptionally large allocations to cause subsequent frames to retain
         // more memory than they need.
         return Vec::with_capacity(old_vec.len());
     }
 
     old_vec.clear();
 
-    return old_vec;
+    old_vec
 }
 
 
 #[cfg(test)]
 pub mod test {
     use super::*;
     use euclid::{Point2D, Angle, Transform3D};
     use std::f32::consts::PI;
@@ -407,20 +407,20 @@ impl<Src, Dst> FastTransform<Src, Dst> {
                 let new_transform = self.to_transform().pre_mul(&other.to_transform());
                 FastTransform::with_transform(new_transform)
             }
         }
     }
 
     #[inline(always)]
     pub fn pre_translate(&self, other_offset: &TypedVector2D<f32, Src>) -> Self {
-        match self {
-            &FastTransform::Offset(ref offset) =>
-                return FastTransform::Offset(*offset + *other_offset),
-            &FastTransform::Transform { transform, .. } =>
+        match *self {
+            FastTransform::Offset(ref offset) =>
+                FastTransform::Offset(*offset + *other_offset),
+            FastTransform::Transform { transform, .. } =>
                 FastTransform::with_transform(transform.pre_translate(other_offset.to_3d()))
         }
     }
 
     #[inline(always)]
     pub fn preserves_2d_axis_alignment(&self) -> bool {
         match *self {
             FastTransform::Offset(..) => true,
@@ -474,17 +474,17 @@ impl<Src, Dst> FastTransform<Src, Dst> {
         }
     }
 
     pub fn unapply(&self, rect: &TypedRect<f32, Dst>) -> Option<TypedRect<f32, Src>> {
         match *self {
             FastTransform::Offset(offset) =>
                 Some(TypedRect::from_untyped(&rect.to_untyped().translate(&-offset.to_untyped()))),
             FastTransform::Transform { inverse: Some(ref inverse), is_2d: true, .. }  =>
-                Some(inverse.transform_rect(&rect)),
+                Some(inverse.transform_rect(rect)),
             FastTransform::Transform { ref transform, is_2d: false, .. } =>
                 Some(transform.inverse_rect_footprint(rect)),
             FastTransform::Transform { inverse: None, .. }  => None,
         }
     }
 
     #[inline(always)]
     pub fn offset(&self, new_offset: TypedVector2D<f32, Src>) -> Self {
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -283,19 +283,18 @@ impl Transaction {
     /// Scrolls the scrolling layer under the `cursor`
     ///
     /// WebRender looks for the layer closest to the user
     /// which has `ScrollPolicy::Scrollable` set.
     pub fn scroll(
         &mut self,
         scroll_location: ScrollLocation,
         cursor: WorldPoint,
-        phase: ScrollEventPhase,
     ) {
-        self.frame_ops.push(FrameMsg::Scroll(scroll_location, cursor, phase));
+        self.frame_ops.push(FrameMsg::Scroll(scroll_location, cursor));
     }
 
     pub fn scroll_node_with_id(
         &mut self,
         origin: LayoutPoint,
         id: ExternalScrollId,
         clamp: ScrollClamping,
     ) {
@@ -309,20 +308,16 @@ impl Transaction {
     pub fn set_pinch_zoom(&mut self, pinch_zoom: ZoomFactor) {
         self.scene_ops.push(SceneMsg::SetPinchZoom(pinch_zoom));
     }
 
     pub fn set_pan(&mut self, pan: DeviceIntPoint) {
         self.frame_ops.push(FrameMsg::SetPan(pan));
     }
 
-    pub fn tick_scrolling_bounce_animations(&mut self) {
-        self.frame_ops.push(FrameMsg::TickScrollingBounce);
-    }
-
     /// Generate a new frame.
     pub fn generate_frame(&mut self) {
         self.generate_frame = true;
     }
 
     /// Supply a list of animated property bindings that should be used to resolve
     /// bindings in the current display list.
     pub fn update_dynamic_properties(&mut self, properties: DynamicProperties) {
@@ -482,19 +477,18 @@ pub enum SceneMsg {
 
 // Frame messages affect frame generation (applied after building the scene).
 #[derive(Clone, Deserialize, Serialize)]
 pub enum FrameMsg {
     UpdateEpoch(PipelineId, Epoch),
     HitTest(Option<PipelineId>, WorldPoint, HitTestFlags, MsgSender<HitTestResult>),
     SetPan(DeviceIntPoint),
     EnableFrameOutput(PipelineId, bool),
-    Scroll(ScrollLocation, WorldPoint, ScrollEventPhase),
+    Scroll(ScrollLocation, WorldPoint),
     ScrollNodeWithId(LayoutPoint, ExternalScrollId, ScrollClamping),
-    TickScrollingBounce,
     GetScrollNodeState(MsgSender<Vec<ScrollNodeState>>),
     UpdateDynamicProperties(DynamicProperties),
 }
 
 impl fmt::Debug for SceneMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             SceneMsg::UpdateEpoch(..) => "SceneMsg::UpdateEpoch",
@@ -511,17 +505,16 @@ impl fmt::Debug for SceneMsg {
 impl fmt::Debug for FrameMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             FrameMsg::UpdateEpoch(..) => "FrameMsg::UpdateEpoch",
             FrameMsg::HitTest(..) => "FrameMsg::HitTest",
             FrameMsg::SetPan(..) => "FrameMsg::SetPan",
             FrameMsg::Scroll(..) => "FrameMsg::Scroll",
             FrameMsg::ScrollNodeWithId(..) => "FrameMsg::ScrollNodeWithId",
-            FrameMsg::TickScrollingBounce => "FrameMsg::TickScrollingBounce",
             FrameMsg::GetScrollNodeState(..) => "FrameMsg::GetScrollNodeState",
             FrameMsg::EnableFrameOutput(..) => "FrameMsg::EnableFrameOutput",
             FrameMsg::UpdateDynamicProperties(..) => "FrameMsg::UpdateDynamicProperties",
         })
     }
 }
 
 bitflags!{
@@ -617,16 +610,17 @@ pub enum ApiMsg {
     ClearNamespace(IdNamespace),
     /// Flush from the caches anything that isn't necessary, to free some memory.
     MemoryPressure,
     /// Change debugging options.
     DebugCommand(DebugCommand),
     /// Wakes the render backend's event loop up. Needed when an event is communicated
     /// through another channel.
     WakeUp,
+    WakeSceneBuilder,
     ShutDown,
 }
 
 impl fmt::Debug for ApiMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             ApiMsg::UpdateResources(..) => "ApiMsg::UpdateResources",
             ApiMsg::GetGlyphDimensions(..) => "ApiMsg::GetGlyphDimensions",
@@ -636,16 +630,17 @@ impl fmt::Debug for ApiMsg {
             ApiMsg::UpdateDocument(..) => "ApiMsg::UpdateDocument",
             ApiMsg::DeleteDocument(..) => "ApiMsg::DeleteDocument",
             ApiMsg::ExternalEvent(..) => "ApiMsg::ExternalEvent",
             ApiMsg::ClearNamespace(..) => "ApiMsg::ClearNamespace",
             ApiMsg::MemoryPressure => "ApiMsg::MemoryPressure",
             ApiMsg::DebugCommand(..) => "ApiMsg::DebugCommand",
             ApiMsg::ShutDown => "ApiMsg::ShutDown",
             ApiMsg::WakeUp => "ApiMsg::WakeUp",
+            ApiMsg::WakeSceneBuilder => "ApiMsg::WakeSceneBuilder",
         })
     }
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
 pub struct Epoch(pub u32);
 
@@ -944,16 +939,20 @@ impl RenderApi {
     }
 
     pub fn get_scroll_node_state(&self, document_id: DocumentId) -> Vec<ScrollNodeState> {
         let (tx, rx) = channel::msg_channel().unwrap();
         self.send_frame_msg(document_id, FrameMsg::GetScrollNodeState(tx));
         rx.recv().unwrap()
     }
 
+    pub fn wake_scene_builder(&self) {
+        self.send_message(ApiMsg::WakeSceneBuilder);
+    }
+
     /// Save a capture of the current frame state for debugging.
     pub fn save_capture(&self, path: PathBuf, bits: CaptureBits) {
         let msg = ApiMsg::DebugCommand(DebugCommand::SaveCapture(path, bits));
         self.send_message(msg);
     }
 
     /// Load a capture of the current frame state for debugging.
     pub fn load_capture(&self, path: PathBuf) -> Vec<CapturedDocument> {
@@ -976,27 +975,16 @@ impl RenderApi {
 
 impl Drop for RenderApi {
     fn drop(&mut self) {
         let msg = ApiMsg::ClearNamespace(self.namespace_id);
         let _ = self.api_sender.send(msg);
     }
 }
 
-#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
-pub enum ScrollEventPhase {
-    /// The user started scrolling.
-    Start,
-    /// The user performed a scroll. The Boolean flag indicates whether the user's fingers are
-    /// down, if a touchpad is in use. (If false, the event is a touchpad fling.)
-    Move(bool),
-    /// The user ended scrolling.
-    End,
-}
-
 #[derive(Clone, Deserialize, Serialize)]
 pub struct ScrollNodeState {
     pub id: ExternalScrollId,
     pub scroll_offset: LayoutVector2D,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
 pub enum ScrollLocation {
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -446,16 +446,17 @@ pub struct PushStackingContextDisplayIte
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct StackingContext {
     pub scroll_policy: ScrollPolicy,
     pub transform: Option<PropertyBinding<LayoutTransform>>,
     pub transform_style: TransformStyle,
     pub perspective: Option<LayoutTransform>,
     pub mix_blend_mode: MixBlendMode,
     pub reference_frame_id: Option<ClipId>,
+    pub clip_node_id: Option<ClipId>,
 } // IMPLICIT: filters: Vec<FilterOp>
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
 pub enum ScrollPolicy {
     Scrollable = 0,
     Fixed = 1,
 }
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -1264,16 +1264,17 @@ impl DisplayListBuilder {
         });
 
         self.push_item(item, info);
     }
 
     pub fn push_stacking_context(
         &mut self,
         info: &LayoutPrimitiveInfo,
+        clip_node_id: Option<ClipId>,
         scroll_policy: ScrollPolicy,
         transform: Option<PropertyBinding<LayoutTransform>>,
         transform_style: TransformStyle,
         perspective: Option<LayoutTransform>,
         mix_blend_mode: MixBlendMode,
         filters: Vec<FilterOp>,
     ) {
         let reference_frame_id = if transform.is_some() || perspective.is_some() {
@@ -1285,16 +1286,17 @@ impl DisplayListBuilder {
         let item = SpecificDisplayItem::PushStackingContext(PushStackingContextDisplayItem {
             stacking_context: StackingContext {
                 scroll_policy,
                 transform,
                 transform_style,
                 perspective,
                 mix_blend_mode,
                 reference_frame_id,
+                clip_node_id,
             },
         });
 
         self.push_item(item, info);
         self.push_iter(&filters);
     }
 
     pub fn pop_stacking_context(&mut self) {
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-941bf5ac998061689a1bcd18d417f1f315e41ae6
+092ada1154b72fe71d2f227a5df0239586d2323a
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -1509,16 +1509,19 @@ impl YamlFrameReader {
         let perspective_origin = yaml["perspective-origin"]
             .as_point()
             .unwrap_or(default_transform_origin);
 
         let transform = yaml["transform"]
             .as_transform(&transform_origin)
             .map(|transform| transform.into());
 
+        let clip_node_id =
+            yaml["clip-node"].as_i64().map(|id| self.to_clip_id(id as u64, dl.pipeline_id));
+
         let perspective = match yaml["perspective"].as_f32() {
             Some(value) if value != 0.0 => {
                 Some(make_perspective(perspective_origin, value as f32))
             }
             Some(_) => None,
             _ => yaml["perspective"].as_matrix4d(),
         };
 
@@ -1540,16 +1543,17 @@ impl YamlFrameReader {
         }
 
         let filters = yaml["filters"].as_vec_filter_op().unwrap_or(vec![]);
         info.rect = bounds;
         info.clip_rect = bounds;
 
         dl.push_stacking_context(
             &info,
+            clip_node_id,
             scroll_policy,
             transform.into(),
             transform_style,
             perspective,
             mix_blend_mode,
             filters,
         );
 
--- a/gfx/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wrench/src/yaml_frame_writer.rs
@@ -173,23 +173,33 @@ fn maybe_radius_yaml(radius: &BorderRadi
         size_node(&mut table, "top-left", &radius.top_left);
         size_node(&mut table, "top-right", &radius.top_right);
         size_node(&mut table, "bottom-left", &radius.bottom_left);
         size_node(&mut table, "bottom-right", &radius.bottom_right);
         Some(Yaml::Hash(table))
     }
 }
 
-fn write_sc(parent: &mut Table, sc: &StackingContext, properties: &SceneProperties, filter_iter: AuxIter<FilterOp>) {
+fn write_stacking_context(
+    parent: &mut Table,
+    sc: &StackingContext,
+    properties: &SceneProperties,
+    filter_iter: AuxIter<FilterOp>,
+    clip_id_mapper: &ClipIdMapper,
+) {
     enum_node(parent, "scroll-policy", sc.scroll_policy);
 
     matrix4d_node(parent, "transform", &properties.resolve_layout_transform(&sc.transform));
 
     enum_node(parent, "transform-style", sc.transform_style);
 
+    if let Some(clip_node_id) = sc.clip_node_id {
+        yaml_node(parent, "clip-node", Yaml::Integer(clip_id_mapper.map_id(&clip_node_id) as i64));
+    }
+
     if let Some(perspective) = sc.perspective {
         matrix4d_node(parent, "perspective", &perspective);
     }
 
     // mix_blend_mode
     if sc.mix_blend_mode != MixBlendMode::Normal {
         enum_node(parent, "mix-blend-mode", sc.mix_blend_mode)
     }
@@ -994,17 +1004,23 @@ impl YamlFrameWriter {
                 }
                 Iframe(item) => {
                     str_node(&mut v, "type", "iframe");
                     u32_vec_node(&mut v, "id", &[item.pipeline_id.0, item.pipeline_id.1]);
                 }
                 PushStackingContext(item) => {
                     str_node(&mut v, "type", "stacking-context");
                     let filters = display_list.get(base.filters());
-                    write_sc(&mut v, &item.stacking_context, &scene.properties, filters);
+                    write_stacking_context(
+                        &mut v,
+                        &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);
                 }
                 Clip(item) => {
                     str_node(&mut v, "type", "clip");
                     usize_node(&mut v, "id", clip_id_mapper.add_id(item.id));