Bug 1340270 - Update webrender and webrender_traits to cset edc74274d28b1fa1229a1d1ea05027f57172b992. r=jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 22 Feb 2017 22:54:21 -0500
changeset 488439 a7eaf588d494ee40da84e5b19cd5ad582ba46294
parent 488430 9d9cc19f0627ed80c8f245cfcb22a79c09936144
child 488440 2fc1e1550cc78bd8d99773c75de6320f6a8be0a1
push id46526
push userkgupta@mozilla.com
push dateThu, 23 Feb 2017 03:58:47 +0000
reviewersjrmuizel
bugs1340270
milestone54.0a1
Bug 1340270 - Update webrender and webrender_traits to cset edc74274d28b1fa1229a1d1ea05027f57172b992. r=jrmuizel MozReview-Commit-ID: CaW1TwxCkgO
gfx/doc/README.webrender
gfx/webrender/Cargo.toml
gfx/webrender/res/cs_text_run.vs.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_border.vs.glsl
gfx/webrender/res/ps_box_shadow.vs.glsl
gfx/webrender/res/ps_cache_image.vs.glsl
gfx/webrender/res/ps_gradient.vs.glsl
gfx/webrender/res/ps_text_run.vs.glsl
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/device.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/layer.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/platform/windows/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/record.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/scroll_tree.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender_traits/Cargo.toml
gfx/webrender_traits/src/api.rs
gfx/webrender_traits/src/channel_mpsc.rs
gfx/webrender_traits/src/display_item.rs
gfx/webrender_traits/src/display_list.rs
gfx/webrender_traits/src/types.rs
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: 938b32ca93bf5e878422ac4bafcdd53f8058f880
+Latest Commit: edc74274d28b1fa1229a1d1ea05027f57172b992
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -1,46 +1,46 @@
 [package]
 name = "webrender"
-version = "0.15.0"
+version = "0.19.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 build = "build.rs"
 
 [features]
 default = ["codegen", "freetype-lib"]
 codegen = ["webrender_traits/codegen"]
 freetype-lib = ["freetype/servo-freetype-sys"]
 serde_derive = ["webrender_traits/serde_derive"]
 profiler = ["thread_profiler/thread_profiler"]
 
 [dependencies]
-app_units = "0.3"
-bincode = "0.6"
+app_units = "0.4"
+bincode = "1.0.0-alpha2"
 bit-set = "0.4"
 byteorder = "1.0"
-euclid = "0.10.3"
+euclid = "0.11"
 fnv="1.0"
 gleam = "0.2.30"
 lazy_static = "0.2"
 log = "0.3"
 num-traits = "0.1.32"
-offscreen_gl_context = {version = "0.5", features = ["serde_serialization", "osmesa"]}
+offscreen_gl_context = {version = "0.6", features = ["serde", "osmesa"]}
 time = "0.1"
 threadpool = "1.3.2"
 webrender_traits = {path = "../webrender_traits", default-features = false}
 bitflags = "0.7"
 gamma-lut = "0.1"
 thread_profiler = "0.1.1"
 
 [dev-dependencies]
 angle = {git = "https://github.com/servo/angle", branch = "servo"}
 
 [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
 freetype = { version = "0.2", default-features = false }
 
 [target.'cfg(target_os = "windows")'.dependencies]
-dwrote = "0.1.7"
+servo-dwrote = "0.2"
 
 [target.'cfg(target_os = "macos")'.dependencies]
-core-graphics = "0.6.0"
-core-text = "3.0"
+core-graphics = "0.7.0"
+core-text = "4.0"
--- a/gfx/webrender/res/cs_text_run.vs.glsl
+++ b/gfx/webrender/res/cs_text_run.vs.glsl
@@ -14,17 +14,17 @@ void main(void) {
     Glyph glyph = fetch_glyph(cpi.sub_index);
     PrimitiveGeometry pg = fetch_prim_geometry(cpi.global_prim_index);
     ResourceRect res = fetch_resource_rect(cpi.user_data.x);
 
     // Glyphs size is already in device-pixels.
     // The render task origin is in device-pixels. Offset that by
     // the glyph offset, relative to its primitive bounding rect.
     vec2 size = res.uv_rect.zw - res.uv_rect.xy;
-    vec2 origin = task.data0.xy + uDevicePixelRatio * (glyph.offset.xy - pg.local_rect.xy);
+    vec2 origin = task.data0.xy + uDevicePixelRatio * (glyph.offset.xy - pg.local_rect.p0);
     vec4 local_rect = vec4(origin, size);
 
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vec2 st0 = res.uv_rect.xy / texture_size;
     vec2 st1 = res.uv_rect.zw / texture_size;
 
     vec2 pos = mix(local_rect.xy,
                    local_rect.xy + local_rect.zw,
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -81,20 +81,63 @@ ivec2 get_fetch_uv_2(int index) {
 ivec2 get_fetch_uv_4(int index) {
     return get_fetch_uv(index, 4);
 }
 
 ivec2 get_fetch_uv_8(int index) {
     return get_fetch_uv(index, 8);
 }
 
+struct RectWithSize {
+    vec2 p0;
+    vec2 size;
+};
+
+struct RectWithEndpoint {
+    vec2 p0;
+    vec2 p1;
+};
+
+RectWithEndpoint to_rect_with_endpoint(RectWithSize rect) {
+    RectWithEndpoint result;
+    result.p0 = rect.p0;
+    result.p1 = rect.p0 + rect.size;
+
+    return result;
+}
+
+RectWithSize to_rect_with_size(RectWithEndpoint rect) {
+    RectWithSize result;
+    result.p0 = rect.p0;
+    result.size = rect.p1 - rect.p0;
+
+    return result;
+}
+
+vec2 clamp_rect(vec2 point, RectWithSize rect) {
+    return clamp(point, rect.p0, rect.p0 + rect.size);
+}
+
+vec2 clamp_rect(vec2 point, RectWithEndpoint rect) {
+    return clamp(point, rect.p0, rect.p1);
+}
+
+// Clamp 2 points at once.
+vec4 clamp_rect(vec4 points, RectWithSize rect) {
+    return clamp(points, rect.p0.xyxy, rect.p0.xyxy + rect.size.xyxy);
+}
+
+vec4 clamp_rect(vec4 points, RectWithEndpoint rect) {
+    return clamp(points, rect.p0.xyxy, rect.p1.xyxy);
+}
+
 struct Layer {
     mat4 transform;
     mat4 inv_transform;
-    vec4 local_clip_rect;
+    RectWithSize local_clip_rect;
     vec4 screen_vertices[4];
 };
 
 Layer fetch_layer(int index) {
     Layer layer;
 
     // Create a UV base coord for each 8 texels.
     // This is required because trying to use an offset
@@ -109,17 +152,18 @@ Layer fetch_layer(int index) {
     layer.transform[2] = texelFetchOffset(sLayers, uv0, 0, ivec2(2, 0));
     layer.transform[3] = texelFetchOffset(sLayers, uv0, 0, ivec2(3, 0));
 
     layer.inv_transform[0] = texelFetchOffset(sLayers, uv0, 0, ivec2(4, 0));
     layer.inv_transform[1] = texelFetchOffset(sLayers, uv0, 0, ivec2(5, 0));
     layer.inv_transform[2] = texelFetchOffset(sLayers, uv0, 0, ivec2(6, 0));
     layer.inv_transform[3] = texelFetchOffset(sLayers, uv0, 0, ivec2(7, 0));
 
-    layer.local_clip_rect = texelFetchOffset(sLayers, uv1, 0, ivec2(0, 0));
+    vec4 clip_rect = texelFetchOffset(sLayers, uv1, 0, ivec2(0, 0));
+    layer.local_clip_rect = RectWithSize(clip_rect.xy, clip_rect.zw);
 
     layer.screen_vertices[0] = texelFetchOffset(sLayers, uv1, 0, ivec2(1, 0));
     layer.screen_vertices[1] = texelFetchOffset(sLayers, uv1, 0, ivec2(2, 0));
     layer.screen_vertices[2] = texelFetchOffset(sLayers, uv1, 0, ivec2(3, 0));
     layer.screen_vertices[3] = texelFetchOffset(sLayers, uv1, 0, ivec2(4, 0));
 
     return layer;
 }
@@ -241,36 +285,38 @@ Glyph fetch_glyph(int index) {
 
     ivec2 uv = get_fetch_uv_1(index);
 
     glyph.offset = texelFetchOffset(sData16, uv, 0, ivec2(0, 0));
 
     return glyph;
 }
 
-vec4 fetch_instance_geometry(int index) {
+RectWithSize fetch_instance_geometry(int index) {
     ivec2 uv = get_fetch_uv_1(index);
 
     vec4 rect = texelFetchOffset(sData16, uv, 0, ivec2(0, 0));
 
-    return rect;
+    return RectWithSize(rect.xy, rect.zw);
 }
 
 struct PrimitiveGeometry {
-    vec4 local_rect;
-    vec4 local_clip_rect;
+    RectWithSize local_rect;
+    RectWithSize local_clip_rect;
 };
 
 PrimitiveGeometry fetch_prim_geometry(int index) {
     PrimitiveGeometry pg;
 
     ivec2 uv = get_fetch_uv(index, VECS_PER_PRIM_GEOM);
 
-    pg.local_rect = texelFetchOffset(sPrimGeometry, uv, 0, ivec2(0, 0));
-    pg.local_clip_rect = texelFetchOffset(sPrimGeometry, uv, 0, ivec2(1, 0));
+    vec4 local_rect = texelFetchOffset(sPrimGeometry, uv, 0, ivec2(0, 0));
+    pg.local_rect = RectWithSize(local_rect.xy, local_rect.zw);
+    vec4 local_clip_rect = texelFetchOffset(sPrimGeometry, uv, 0, ivec2(1, 0));
+    pg.local_clip_rect = RectWithSize(local_clip_rect.xy, local_clip_rect.zw);
 
     return pg;
 }
 
 struct PrimitiveInstance {
     int global_prim_index;
     int specific_prim_index;
     int render_task_index;
@@ -317,18 +363,18 @@ CachePrimitiveInstance fetch_cache_insta
 
     return cpi;
 }
 
 struct Primitive {
     Layer layer;
     ClipArea clip_area;
     AlphaBatchTask task;
-    vec4 local_rect;
-    vec4 local_clip_rect;
+    RectWithSize local_rect;
+    RectWithSize local_clip_rect;
     int prim_index;
     // when sending multiple primitives of the same type (e.g. border segments)
     // this index allows the vertex shader to recognize the difference
     int sub_index;
     ivec2 user_data;
     float z;
 };
 
@@ -397,57 +443,38 @@ vec4 get_layer_pos(vec2 pos, Layer layer
     vec3 a = layer.screen_vertices[0].xyz / layer.screen_vertices[0].w;
     vec3 b = layer.screen_vertices[3].xyz / layer.screen_vertices[3].w;
     vec3 c = layer.screen_vertices[2].xyz / layer.screen_vertices[2].w;
     // get the normal to the layer plane
     vec3 n = normalize(cross(b-a, c-a));
     return untransform(pos, n, a, layer.inv_transform);
 }
 
-vec2 clamp_rect(vec2 point, vec4 rect) {
-    return clamp(point, rect.xy, rect.xy + rect.zw);
-}
-
-struct Rect {
-    vec2 p0;
-    vec2 p1;
-};
-
 struct VertexInfo {
-    Rect local_rect;
+    RectWithEndpoint local_rect;
     vec2 local_pos;
     vec2 screen_pos;
 };
 
-VertexInfo write_vertex(vec4 instance_rect,
-                        vec4 local_clip_rect,
+VertexInfo write_vertex(RectWithSize instance_rect,
+                        RectWithSize local_clip_rect,
                         float z,
                         Layer layer,
                         AlphaBatchTask task) {
-    // Get the min/max local space coords of the rectangle.
-    vec2 local_p0 = instance_rect.xy;
-    vec2 local_p1 = instance_rect.xy + instance_rect.zw;
-
-    // Get the min/max coords of the local space clip rect.
-    vec2 local_clip_p0 = local_clip_rect.xy;
-    vec2 local_clip_p1 = local_clip_rect.xy + local_clip_rect.zw;
-
-    // Get the min/max coords of the layer clip rect.
-    vec2 layer_clip_p0 = layer.local_clip_rect.xy;
-    vec2 layer_clip_p1 = layer.local_clip_rect.xy + layer.local_clip_rect.zw;
+    RectWithEndpoint local_rect = to_rect_with_endpoint(instance_rect);
 
     // Select the corner of the local rect that we are processing.
-    vec2 local_pos = mix(local_p0, local_p1, aPosition.xy);
+    vec2 local_pos = mix(local_rect.p0, local_rect.p1, aPosition.xy);
 
     // xy = top left corner of the local rect, zw = position of current vertex.
-    vec4 local_p0_pos = vec4(local_p0, local_pos);
+    vec4 local_p0_pos = vec4(local_rect.p0, local_pos);
 
     // Clamp to the two local clip rects.
-    local_p0_pos = clamp(local_p0_pos, local_clip_p0.xyxy, local_clip_p1.xyxy);
-    local_p0_pos = clamp(local_p0_pos, layer_clip_p0.xyxy, layer_clip_p1.xyxy);
+    local_p0_pos = clamp_rect(local_p0_pos, local_clip_rect);
+    local_p0_pos = clamp_rect(local_p0_pos, layer.local_clip_rect);
 
     // Transform the top corner and current vertex to world space.
     vec4 world_p0 = layer.transform * vec4(local_p0_pos.xy, 0.0, 1.0);
     world_p0.xyz /= world_p0.w;
     vec4 world_pos = layer.transform * vec4(local_p0_pos.zw, 0.0, 1.0);
     world_pos.xyz /= world_pos.w;
 
     // Convert the world positions to device pixel space. xy=top left corner. zw=current vertex.
@@ -459,35 +486,35 @@ VertexInfo write_vertex(vec4 instance_re
     // Apply offsets for the render task to get correct screen location.
     vec2 final_pos = device_p0_pos.zw -
                      snap_delta -
                      task.screen_space_origin +
                      task.render_target_origin;
 
     gl_Position = uTransform * vec4(final_pos, z, 1.0);
 
-    VertexInfo vi = VertexInfo(Rect(local_p0, local_p1), local_p0_pos.zw, device_p0_pos.zw);
+    VertexInfo vi = VertexInfo(local_rect, local_p0_pos.zw, device_p0_pos.zw);
     return vi;
 }
 
 #ifdef WR_FEATURE_TRANSFORM
 
 struct TransformVertexInfo {
     vec3 local_pos;
     vec2 screen_pos;
     vec4 clipped_local_rect;
 };
 
-TransformVertexInfo write_transform_vertex(vec4 instance_rect,
-                                           vec4 local_clip_rect,
+TransformVertexInfo write_transform_vertex(RectWithSize instance_rect,
+                                           RectWithSize local_clip_rect,
                                            float z,
                                            Layer layer,
                                            AlphaBatchTask task) {
-    vec2 lp0_base = instance_rect.xy;
-    vec2 lp1_base = instance_rect.xy + instance_rect.zw;
+    vec2 lp0_base = instance_rect.p0;
+    vec2 lp1_base = instance_rect.p0 + instance_rect.size;
 
     vec2 lp0 = clamp_rect(clamp_rect(lp0_base, local_clip_rect),
                           layer.local_clip_rect);
     vec2 lp1 = clamp_rect(clamp_rect(lp1_base, local_clip_rect),
                           layer.local_clip_rect);
 
     vec4 clipped_local_rect = vec4(lp0, lp1 - lp0);
 
--- a/gfx/webrender/res/ps_border.vs.glsl
+++ b/gfx/webrender/res/ps_border.vs.glsl
@@ -27,110 +27,100 @@ Border fetch_border(int index) {
     return border;
 }
 
 void main(void) {
     Primitive prim = load_primitive();
     Border border = fetch_border(prim.prim_index);
     int sub_part = prim.sub_index;
 
-    vec2 tl_outer = prim.local_rect.xy;
+    vec2 tl_outer = prim.local_rect.p0;
     vec2 tl_inner = tl_outer + vec2(max(border.radii[0].x, border.widths.x),
                                     max(border.radii[0].y, border.widths.y));
 
-    vec2 tr_outer = vec2(prim.local_rect.x + prim.local_rect.z,
-                         prim.local_rect.y);
+    vec2 tr_outer = vec2(prim.local_rect.p0.x + prim.local_rect.size.x,
+                         prim.local_rect.p0.y);
     vec2 tr_inner = tr_outer + vec2(-max(border.radii[0].z, border.widths.z),
                                     max(border.radii[0].w, border.widths.y));
 
-    vec2 br_outer = vec2(prim.local_rect.x + prim.local_rect.z,
-                         prim.local_rect.y + prim.local_rect.w);
+    vec2 br_outer = vec2(prim.local_rect.p0.x + prim.local_rect.size.x,
+                         prim.local_rect.p0.y + prim.local_rect.size.y);
     vec2 br_inner = br_outer - vec2(max(border.radii[1].x, border.widths.z),
                                     max(border.radii[1].y, border.widths.w));
 
-    vec2 bl_outer = vec2(prim.local_rect.x,
-                         prim.local_rect.y + prim.local_rect.w);
+    vec2 bl_outer = vec2(prim.local_rect.p0.x,
+                         prim.local_rect.p0.y + prim.local_rect.size.y);
     vec2 bl_inner = bl_outer + vec2(max(border.radii[1].z, border.widths.x),
                                     -max(border.radii[1].w, border.widths.w));
 
-    vec4 segment_rect;
+    RectWithSize segment_rect;
     switch (sub_part) {
         case PST_TOP_LEFT:
-            segment_rect = vec4(tl_outer, tl_inner - tl_outer);
+            segment_rect.p0 = tl_outer;
+            segment_rect.size = tl_inner - tl_outer;
             vBorderStyle = int(border.style.x);
             vHorizontalColor = border.colors[BORDER_LEFT];
             vVerticalColor = border.colors[BORDER_TOP];
             vRadii = vec4(border.radii[0].xy,
                           border.radii[0].xy - border.widths.xy);
             break;
         case PST_TOP_RIGHT:
-            segment_rect = vec4(tr_inner.x,
-                                tr_outer.y,
-                                tr_outer.x - tr_inner.x,
-                                tr_inner.y - tr_outer.y);
+            segment_rect.p0 = vec2(tr_inner.x, tr_outer.y);
+            segment_rect.size = vec2(tr_outer.x - tr_inner.x, tr_inner.y - tr_outer.y);
             vBorderStyle = int(border.style.y);
             vHorizontalColor = border.colors[BORDER_TOP];
             vVerticalColor = border.colors[BORDER_RIGHT];
             vRadii = vec4(border.radii[0].zw,
                           border.radii[0].zw - border.widths.zy);
             break;
         case PST_BOTTOM_RIGHT:
-            segment_rect = vec4(br_inner, br_outer - br_inner);
+            segment_rect.p0 = br_inner;
+            segment_rect.size = br_outer - br_inner;
             vBorderStyle = int(border.style.z);
             vHorizontalColor = border.colors[BORDER_BOTTOM];
             vVerticalColor = border.colors[BORDER_RIGHT];
             vRadii = vec4(border.radii[1].xy,
                           border.radii[1].xy - border.widths.zw);
             break;
         case PST_BOTTOM_LEFT:
-            segment_rect = vec4(bl_outer.x,
-                                bl_inner.y,
-                                bl_inner.x - bl_outer.x,
-                                bl_outer.y - bl_inner.y);
+            segment_rect.p0 = vec2(bl_outer.x, bl_inner.y);
+            segment_rect.size = vec2(bl_inner.x - bl_outer.x, bl_outer.y - bl_inner.y);
             vBorderStyle = int(border.style.w);
             vHorizontalColor = border.colors[BORDER_BOTTOM];
             vVerticalColor = border.colors[BORDER_LEFT];
             vRadii = vec4(border.radii[1].zw,
                           border.radii[1].zw - border.widths.xw);
             break;
         case PST_LEFT:
-            segment_rect = vec4(tl_outer.x,
-                                tl_inner.y,
-                                border.widths.x,
-                                bl_inner.y - tl_inner.y);
+            segment_rect.p0 = vec2(tl_outer.x, tl_inner.y);
+            segment_rect.size = vec2(border.widths.x, bl_inner.y - tl_inner.y);
             vBorderStyle = int(border.style.x);
             vHorizontalColor = border.colors[BORDER_LEFT];
             vVerticalColor = border.colors[BORDER_LEFT];
             vRadii = vec4(0.0);
             break;
         case PST_RIGHT:
-            segment_rect = vec4(tr_outer.x - border.widths.z,
-                                tr_inner.y,
-                                border.widths.z,
-                                br_inner.y - tr_inner.y);
+            segment_rect.p0 = vec2(tr_outer.x - border.widths.z, tr_inner.y);
+            segment_rect.size = vec2(border.widths.z, br_inner.y - tr_inner.y);
             vBorderStyle = int(border.style.z);
             vHorizontalColor = border.colors[BORDER_RIGHT];
             vVerticalColor = border.colors[BORDER_RIGHT];
             vRadii = vec4(0.0);
             break;
         case PST_BOTTOM:
-            segment_rect = vec4(bl_inner.x,
-                                bl_outer.y - border.widths.w,
-                                br_inner.x - bl_inner.x,
-                                border.widths.w);
+            segment_rect.p0 = vec2(bl_inner.x, bl_outer.y - border.widths.w);
+            segment_rect.size = vec2(br_inner.x - bl_inner.x, border.widths.w);
             vBorderStyle = int(border.style.w);
             vHorizontalColor = border.colors[BORDER_BOTTOM];
             vVerticalColor = border.colors[BORDER_BOTTOM];
             vRadii = vec4(0.0);
             break;
         case PST_TOP:
-            segment_rect = vec4(tl_inner.x,
-                                tl_outer.y,
-                                tr_inner.x - tl_inner.x,
-                                border.widths.y);
+            segment_rect.p0 = vec2(tl_inner.x, tl_outer.y);
+            segment_rect.size = vec2(tr_inner.x - tl_inner.x, border.widths.y);
             vBorderStyle = int(border.style.y);
             vHorizontalColor = border.colors[BORDER_TOP];
             vVerticalColor = border.colors[BORDER_TOP];
             vRadii = vec4(0.0);
             break;
     }
 
 #ifdef WR_FEATURE_TRANSFORM
@@ -147,63 +137,63 @@ void main(void) {
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task);
     vLocalPos = vi.local_pos.xy;
 
     // Local space
-    vLocalRect = prim.local_rect;
+    vLocalRect = vec4(prim.local_rect.p0, prim.local_rect.size);
 #endif
 
     float x0, y0, x1, y1;
     switch (sub_part) {
         // These are the layer tile part PrimitivePart as uploaded by the tiling.rs
         case PST_TOP_LEFT:
-            x0 = segment_rect.x;
-            y0 = segment_rect.y;
+            x0 = segment_rect.p0.x;
+            y0 = segment_rect.p0.y;
             // These are width / heights
-            x1 = segment_rect.x + segment_rect.z;
-            y1 = segment_rect.y + segment_rect.w;
+            x1 = segment_rect.p0.x + segment_rect.size.x;
+            y1 = segment_rect.p0.y + segment_rect.size.y;
 
             // The radius here is the border-radius. This is 0, so vRefPoint will
             // just be the top left (x,y) corner.
             vRefPoint = vec2(x0, y0) + vRadii.xy;
             break;
         case PST_TOP_RIGHT:
-            x0 = segment_rect.x + segment_rect.z;
-            y0 = segment_rect.y;
-            x1 = segment_rect.x;
-            y1 = segment_rect.y + segment_rect.w;
+            x0 = segment_rect.p0.x + segment_rect.size.x;
+            y0 = segment_rect.p0.y;
+            x1 = segment_rect.p0.x;
+            y1 = segment_rect.p0.y + segment_rect.size.y;
             vRefPoint = vec2(x0, y0) + vec2(-vRadii.x, vRadii.y);
             break;
         case PST_BOTTOM_LEFT:
-            x0 = segment_rect.x;
-            y0 = segment_rect.y + segment_rect.w;
-            x1 = segment_rect.x + segment_rect.z;
-            y1 = segment_rect.y;
+            x0 = segment_rect.p0.x;
+            y0 = segment_rect.p0.y + segment_rect.size.y;
+            x1 = segment_rect.p0.x + segment_rect.size.x;
+            y1 = segment_rect.p0.y;
             vRefPoint = vec2(x0, y0) + vec2(vRadii.x, -vRadii.y);
             break;
         case PST_BOTTOM_RIGHT:
-            x0 = segment_rect.x;
-            y0 = segment_rect.y;
-            x1 = segment_rect.x + segment_rect.z;
-            y1 = segment_rect.y + segment_rect.w;
+            x0 = segment_rect.p0.x;
+            y0 = segment_rect.p0.y;
+            x1 = segment_rect.p0.x + segment_rect.size.x;
+            y1 = segment_rect.p0.y + segment_rect.size.y;
             vRefPoint = vec2(x1, y1) + vec2(-vRadii.x, -vRadii.y);
             break;
         case PST_TOP:
         case PST_LEFT:
         case PST_BOTTOM:
         case PST_RIGHT:
-            vRefPoint = segment_rect.xy;
-            x0 = segment_rect.x;
-            y0 = segment_rect.y;
-            x1 = segment_rect.x + segment_rect.z;
-            y1 = segment_rect.y + segment_rect.w;
+            vRefPoint = segment_rect.p0.xy;
+            x0 = segment_rect.p0.x;
+            y0 = segment_rect.p0.y;
+            x1 = segment_rect.p0.x + segment_rect.size.x;
+            y1 = segment_rect.p0.y + segment_rect.size.y;
             break;
     }
 
     // y1 - y0 is the height of the corner / line
     // x1 - x0 is the width of the corner / line.
     float width = x1 - x0;
     float height = y1 - y0;
 
--- a/gfx/webrender/res/ps_box_shadow.vs.glsl
+++ b/gfx/webrender/res/ps_box_shadow.vs.glsl
@@ -1,32 +1,32 @@
 #line 1
 /* 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/. */
 
 void main(void) {
     Primitive prim = load_primitive();
     BoxShadow bs = fetch_boxshadow(prim.prim_index);
-    vec4 segment_rect = fetch_instance_geometry(prim.sub_index);
+    RectWithSize segment_rect = fetch_instance_geometry(prim.sub_index);
 
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task);
 
     RenderTaskData child_task = fetch_render_task(prim.user_data.x);
     vUv.z = child_task.data1.x;
 
     // Constant offsets to inset from bilinear filtering border.
     vec2 patch_origin = child_task.data0.xy + vec2(1.0);
     vec2 patch_size_device_pixels = child_task.data0.zw - vec2(2.0);
     vec2 patch_size = patch_size_device_pixels / uDevicePixelRatio;
 
-    vUv.xy = (vi.local_pos - prim.local_rect.xy) / patch_size;
-    vMirrorPoint = 0.5 * prim.local_rect.zw / patch_size;
+    vUv.xy = (vi.local_pos - prim.local_rect.p0) / patch_size;
+    vMirrorPoint = 0.5 * prim.local_rect.size / patch_size;
 
     vec2 texture_size = vec2(textureSize(sCache, 0));
     vCacheUvRectCoords = vec4(patch_origin, patch_origin + patch_size_device_pixels) / texture_size.xyxy;
 
     vColor = bs.color;
 }
--- a/gfx/webrender/res/ps_cache_image.vs.glsl
+++ b/gfx/webrender/res/ps_cache_image.vs.glsl
@@ -17,12 +17,12 @@ void main(void) {
 
     RenderTaskData child_task = fetch_render_task(prim.user_data.x);
     vUv.z = child_task.data1.x;
 
     vec2 texture_size = vec2(textureSize(sCache, 0));
     vec2 uv0 = child_task.data0.xy / texture_size;
     vec2 uv1 = (child_task.data0.xy + child_task.data0.zw) / texture_size;
 
-    vec2 f = (vi.local_pos - prim.local_rect.xy) / prim.local_rect.zw;
+    vec2 f = (vi.local_pos - prim.local_rect.p0) / prim.local_rect.size;
 
     vUv.xy = mix(uv0, uv1, f);
 }
--- a/gfx/webrender/res/ps_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_gradient.vs.glsl
@@ -5,58 +5,56 @@
 
 void main(void) {
     Primitive prim = load_primitive();
     Gradient gradient = fetch_gradient(prim.prim_index);
 
     GradientStop g0 = fetch_gradient_stop(prim.sub_index + 0);
     GradientStop g1 = fetch_gradient_stop(prim.sub_index + 1);
 
-    vec4 segment_rect;
+    RectWithSize segment_rect;
     vec2 axis;
     if (gradient.start_end_point.y == gradient.start_end_point.w) {
         float x0 = mix(gradient.start_end_point.x,
                        gradient.start_end_point.z,
                        g0.offset.x);
         float x1 = mix(gradient.start_end_point.x,
                        gradient.start_end_point.z,
                        g1.offset.x);
-        segment_rect.yw = prim.local_rect.yw;
-        segment_rect.x = x0;
-        segment_rect.z = x1 - x0;
+        segment_rect.p0 = vec2(x0, prim.local_rect.p0.y);
+        segment_rect.size = vec2(x1 - x0, prim.local_rect.size.y);
         axis = vec2(1.0, 0.0);
     } else {
         float y0 = mix(gradient.start_end_point.y,
                        gradient.start_end_point.w,
                        g0.offset.x);
         float y1 = mix(gradient.start_end_point.y,
                        gradient.start_end_point.w,
                        g1.offset.x);
-        segment_rect.xz = prim.local_rect.xz;
-        segment_rect.y = y0;
-        segment_rect.w = y1 - y0;
+        segment_rect.p0 = vec2(prim.local_rect.p0.x, y0);
+        segment_rect.size = vec2(prim.local_rect.size.x, y1 - y0);
         axis = vec2(0.0, 1.0);
     }
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task);
     vLocalRect = vi.clipped_local_rect;
     vLocalPos = vi.local_pos;
-    vec2 f = (vi.local_pos.xy - prim.local_rect.xy) / prim.local_rect.zw;
+    vec2 f = (vi.local_pos.xy - prim.local_rect.p0) / prim.local_rect.size;
 #else
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task);
 
-    vec2 f = (vi.local_pos - segment_rect.xy) / segment_rect.zw;
+    vec2 f = (vi.local_pos - segment_rect.p0) / segment_rect.size;
     vPos = vi.local_pos;
 #endif
 
     write_clip(vi.screen_pos, prim.clip_area);
 
     vColor = mix(g0.color, g1.color, dot(f, axis));
 }
--- a/gfx/webrender/res/ps_text_run.vs.glsl
+++ b/gfx/webrender/res/ps_text_run.vs.glsl
@@ -4,27 +4,28 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
     Primitive prim = load_primitive();
     TextRun text = fetch_text_run(prim.prim_index);
     Glyph glyph = fetch_glyph(prim.sub_index);
     ResourceRect res = fetch_resource_rect(prim.user_data.x);
 
-    vec4 local_rect = vec4(glyph.offset.xy, (res.uv_rect.zw - res.uv_rect.xy) / uDevicePixelRatio);
+    RectWithSize local_rect = RectWithSize(glyph.offset.xy,
+                                           (res.uv_rect.zw - res.uv_rect.xy) / uDevicePixelRatio);
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task);
     vLocalRect = vi.clipped_local_rect;
     vLocalPos = vi.local_pos;
-    vec2 f = (vi.local_pos.xy / vi.local_pos.z - local_rect.xy) / local_rect.zw;
+    vec2 f = (vi.local_pos.xy / vi.local_pos.z - local_rect.p0) / local_rect.size;
 #else
     VertexInfo vi = write_vertex(local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task);
     vec2 f = (vi.local_pos - vi.local_rect.p0) / (vi.local_rect.p1 - vi.local_rect.p0);
 #endif
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -0,0 +1,288 @@
+/* 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 euclid::Point3D;
+use geometry::ray_intersects_rect;
+use spring::{DAMPING, STIFFNESS, Spring};
+use webrender_traits::{LayerPoint, LayerRect, LayerSize, LayerToScrollTransform};
+use webrender_traits::{LayerToWorldTransform, PipelineId, ScrollEventPhase, ScrollLayerId};
+use webrender_traits::{ScrollLayerRect, ScrollLocation, ScrollToWorldTransform, WorldPoint};
+use webrender_traits::{WorldPoint4D};
+
+#[cfg(target_os = "macos")]
+const CAN_OVERSCROLL: bool = true;
+
+#[cfg(not(target_os = "macos"))]
+const CAN_OVERSCROLL: bool = false;
+
+
+/// Contains scrolling and transform information stacking contexts.
+#[derive(Clone)]
+pub struct ClipScrollNode {
+    /// Manages scrolling offset, overscroll state etc.
+    pub scrolling: ScrollingState,
+
+    /// Size of the content inside the scroll region (in logical pixels)
+    pub content_size: LayerSize,
+
+    /// Viewing rectangle
+    pub local_viewport_rect: LayerRect,
+
+    /// Viewing rectangle clipped against parent layer(s)
+    pub combined_local_viewport_rect: LayerRect,
+
+    /// World transform for the viewport rect itself.
+    pub world_viewport_transform: LayerToWorldTransform,
+
+    /// World transform for content within this layer
+    pub world_content_transform: LayerToWorldTransform,
+
+    /// Transform for this layer, relative to parent scrollable layer.
+    pub local_transform: LayerToScrollTransform,
+
+    /// Pipeline that this layer belongs to
+    pub pipeline_id: PipelineId,
+
+    /// Child layers
+    pub children: Vec<ScrollLayerId>,
+}
+
+impl ClipScrollNode {
+    pub fn new(local_viewport_rect: &LayerRect,
+               content_size: LayerSize,
+               local_transform: &LayerToScrollTransform,
+               pipeline_id: PipelineId)
+               -> ClipScrollNode {
+        ClipScrollNode {
+            scrolling: ScrollingState::new(),
+            content_size: content_size,
+            local_viewport_rect: *local_viewport_rect,
+            combined_local_viewport_rect: *local_viewport_rect,
+            world_viewport_transform: LayerToWorldTransform::identity(),
+            world_content_transform: LayerToWorldTransform::identity(),
+            local_transform: *local_transform,
+            children: Vec::new(),
+            pipeline_id: pipeline_id,
+        }
+    }
+
+    pub fn add_child(&mut self, child: ScrollLayerId) {
+        self.children.push(child);
+    }
+
+    pub fn finalize(&mut self, scrolling: &ScrollingState) {
+        self.scrolling = *scrolling;
+    }
+
+    pub fn overscroll_amount(&self) -> LayerSize {
+        let scrollable_width = self.scrollable_width();
+        let overscroll_x = if self.scrolling.offset.x > 0.0 {
+            -self.scrolling.offset.x
+        } else if self.scrolling.offset.x < -scrollable_width {
+            -scrollable_width - self.scrolling.offset.x
+        } else {
+            0.0
+        };
+
+        let scrollable_height = self.scrollable_height();
+        let overscroll_y = if self.scrolling.offset.y > 0.0 {
+            -self.scrolling.offset.y
+        } else if self.scrolling.offset.y < -scrollable_height {
+            -scrollable_height - self.scrolling.offset.y
+        } else {
+            0.0
+        };
+
+        LayerSize::new(overscroll_x, overscroll_y)
+    }
+
+    pub fn set_scroll_origin(&mut self, origin: &LayerPoint) -> bool {
+        let scrollable_height = self.scrollable_height();
+        let scrollable_width = self.scrollable_width();
+        if scrollable_height <= 0. && scrollable_width <= 0. {
+            return false;
+        }
+
+        let new_offset = LayerPoint::new((-origin.x).max(-scrollable_width).min(0.0).round(),
+                                         (-origin.y).max(-scrollable_height).min(0.0).round());
+        if new_offset == self.scrolling.offset {
+            return false;
+        }
+
+        self.scrolling.offset = new_offset;
+        self.scrolling.bouncing_back = false;
+        self.scrolling.started_bouncing_back = false;
+        return true;
+    }
+
+    pub fn update_transform(&mut self,
+                            parent_world_transform: &ScrollToWorldTransform,
+                            parent_viewport_rect: &ScrollLayerRect) {
+        let inv_transform = match self.local_transform.inverse() {
+            Some(transform) => transform,
+            None => {
+                // If a transform function causes the current transformation matrix of an object
+                // to be non-invertible, the object and its content do not get displayed.
+                self.combined_local_viewport_rect = LayerRect::zero();
+                return;
+            }
+        };
+
+        let parent_viewport_rect_in_local_space = inv_transform.transform_rect(parent_viewport_rect)
+                                                               .translate(&-self.scrolling.offset);
+        let local_viewport_rect = self.local_viewport_rect.translate(&-self.scrolling.offset);
+        let viewport_rect = parent_viewport_rect_in_local_space.intersection(&local_viewport_rect)
+                                                               .unwrap_or(LayerRect::zero());
+
+        self.combined_local_viewport_rect = viewport_rect;
+        self.world_viewport_transform = parent_world_transform.pre_mul(&self.local_transform);
+        self.world_content_transform = self.world_viewport_transform
+                                                     .pre_translated(self.scrolling.offset.x,
+                                                                     self.scrolling.offset.y,
+                                                                     0.0);
+    }
+
+    pub fn scrollable_height(&self) -> f32 {
+        self.content_size.height - self.local_viewport_rect.size.height
+    }
+
+    pub fn scrollable_width(&self) -> f32 {
+        self.content_size.width - self.local_viewport_rect.size.width
+    }
+
+    pub fn scroll(&mut self, scroll_location: ScrollLocation, phase: ScrollEventPhase) -> bool {
+        if self.scrolling.started_bouncing_back && phase == ScrollEventPhase::Move(false) {
+            return false;
+        }
+
+        let mut delta = match scroll_location {
+            ScrollLocation::Delta(delta) => delta,
+            ScrollLocation::Start => {
+                if self.scrolling.offset.y.round() >= 0.0 {
+                    // Nothing to do on this layer.
+                    return false;
+                }
+
+                self.scrolling.offset.y = 0.0;
+                return true;
+            },
+            ScrollLocation::End => {
+                let end_pos = self.local_viewport_rect.size.height - self.content_size.height;
+
+                if self.scrolling.offset.y.round() <= end_pos {
+                    // Nothing to do on this layer.
+                    return false;
+                }
+
+                self.scrolling.offset.y = end_pos;
+                return true;
+            }
+        };
+
+        let overscroll_amount = self.overscroll_amount();
+        let overscrolling = CAN_OVERSCROLL && (overscroll_amount.width != 0.0 ||
+                                               overscroll_amount.height != 0.0);
+        if overscrolling {
+            if overscroll_amount.width != 0.0 {
+                delta.x /= overscroll_amount.width.abs()
+            }
+            if overscroll_amount.height != 0.0 {
+                delta.y /= overscroll_amount.height.abs()
+            }
+        }
+
+        let scrollable_width = self.scrollable_width();
+        let scrollable_height = self.scrollable_height();
+        let is_unscrollable = scrollable_width <= 0. && scrollable_height <= 0.;
+        let original_layer_scroll_offset = self.scrolling.offset;
+
+        if scrollable_width > 0. {
+            self.scrolling.offset.x = self.scrolling.offset.x + delta.x;
+            if is_unscrollable || !CAN_OVERSCROLL {
+                self.scrolling.offset.x =
+                    self.scrolling.offset.x.min(0.0).max(-scrollable_width).round();
+            }
+        }
+
+        if scrollable_height > 0. {
+            self.scrolling.offset.y = self.scrolling.offset.y + delta.y;
+            if is_unscrollable || !CAN_OVERSCROLL {
+                self.scrolling.offset.y =
+                    self.scrolling.offset.y.min(0.0).max(-scrollable_height).round();
+            }
+        }
+
+        if phase == ScrollEventPhase::Start || phase == ScrollEventPhase::Move(true) {
+            self.scrolling.started_bouncing_back = false
+        } else if overscrolling &&
+                ((delta.x < 1.0 && delta.y < 1.0) || phase == ScrollEventPhase::End) {
+            self.scrolling.started_bouncing_back = true;
+            self.scrolling.bouncing_back = true
+        }
+
+        if CAN_OVERSCROLL {
+            self.stretch_overscroll_spring();
+        }
+
+        self.scrolling.offset != original_layer_scroll_offset ||
+            self.scrolling.started_bouncing_back
+    }
+
+    pub fn stretch_overscroll_spring(&mut self) {
+        let overscroll_amount = self.overscroll_amount();
+        self.scrolling.spring.coords(self.scrolling.offset,
+                                     self.scrolling.offset,
+                                     self.scrolling.offset + overscroll_amount);
+    }
+
+    pub fn tick_scrolling_bounce_animation(&mut self) {
+        let finished = self.scrolling.spring.animate();
+        self.scrolling.offset = self.scrolling.spring.current();
+        if finished {
+            self.scrolling.bouncing_back = false
+        }
+    }
+
+    pub fn ray_intersects_node(&self, cursor: &WorldPoint) -> bool {
+        let inv = self.world_viewport_transform.inverse().unwrap();
+        let z0 = -10000.0;
+        let z1 =  10000.0;
+
+        let p0 = inv.transform_point4d(&WorldPoint4D::new(cursor.x, cursor.y, z0, 1.0));
+        let p0 = Point3D::new(p0.x / p0.w,
+                              p0.y / p0.w,
+                              p0.z / p0.w);
+        let p1 = inv.transform_point4d(&WorldPoint4D::new(cursor.x, cursor.y, z1, 1.0));
+        let p1 = Point3D::new(p1.x / p1.w,
+                              p1.y / p1.w,
+                              p1.z / p1.w);
+
+        if self.scrollable_width() <= 0. && self.scrollable_height() <= 0. {
+            return false;
+        }
+        ray_intersects_rect(p0, p1, self.local_viewport_rect.to_untyped())
+    }
+}
+
+#[derive(Copy, Clone)]
+pub struct ScrollingState {
+    pub offset: LayerPoint,
+    pub spring: Spring,
+    pub started_bouncing_back: bool,
+    pub bouncing_back: bool,
+    pub should_handoff_scroll: bool
+}
+
+impl ScrollingState {
+    pub fn new() -> ScrollingState {
+        ScrollingState {
+            offset: LayerPoint::zero(),
+            spring: Spring::at(LayerPoint::zero(), STIFFNESS, DAMPING),
+            started_bouncing_back: false,
+            bouncing_back: false,
+            should_handoff_scroll: false
+        }
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -0,0 +1,394 @@
+/* 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 fnv::FnvHasher;
+use clip_scroll_node::{ClipScrollNode, ScrollingState};
+use std::collections::{HashMap, HashSet};
+use std::hash::BuildHasherDefault;
+use webrender_traits::{LayerPoint, LayerRect, LayerSize, LayerToScrollTransform, PipelineId};
+use webrender_traits::{ScrollEventPhase, ScrollLayerId, ScrollLayerInfo, ScrollLayerPixel};
+use webrender_traits::{ScrollLayerRect, ScrollLayerState, ScrollLocation, ScrollToWorldTransform};
+use webrender_traits::{ServoScrollRootId, WorldPoint, as_scroll_parent_rect};
+
+pub type ScrollStates = HashMap<ScrollLayerId, ScrollingState, BuildHasherDefault<FnvHasher>>;
+
+pub struct ClipScrollTree {
+    pub nodes: HashMap<ScrollLayerId, ClipScrollNode, BuildHasherDefault<FnvHasher>>,
+    pub pending_scroll_offsets: HashMap<(PipelineId, ServoScrollRootId), LayerPoint>,
+
+    /// The ScrollLayerId 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 current_scroll_layer_id: Option<ScrollLayerId>,
+
+    /// The current reference frame id, used for giving a unique id to all new
+    /// reference frames. The ClipScrollTree increments this by one every time a
+    /// reference frame is created.
+    current_reference_frame_id: usize,
+
+    /// The root reference frame, which is the true root of the ClipScrollTree. Initially
+    /// this ID is not valid, which is indicated by ```node``` being empty.
+    pub root_reference_frame_id: ScrollLayerId,
+
+    /// 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 topmost_scroll_layer_id: ScrollLayerId,
+
+    /// A set of pipelines which should be discarded the next time this
+    /// tree is drained.
+    pub pipelines_to_discard: HashSet<PipelineId>,
+}
+
+impl ClipScrollTree {
+    pub fn new() -> ClipScrollTree {
+        let dummy_pipeline = PipelineId(0, 0);
+        ClipScrollTree {
+            nodes: HashMap::with_hasher(Default::default()),
+            pending_scroll_offsets: HashMap::new(),
+            current_scroll_layer_id: None,
+            root_reference_frame_id: ScrollLayerId::root_reference_frame(dummy_pipeline),
+            topmost_scroll_layer_id: ScrollLayerId::root_scroll_layer(dummy_pipeline),
+            current_reference_frame_id: 1,
+            pipelines_to_discard: HashSet::new(),
+        }
+    }
+
+    pub fn root_reference_frame_id(&self) -> ScrollLayerId {
+        // TODO(mrobinson): We should eventually make this impossible to misuse.
+        debug_assert!(!self.nodes.is_empty());
+        debug_assert!(self.nodes.contains_key(&self.root_reference_frame_id));
+        self.root_reference_frame_id
+    }
+
+    pub fn topmost_scroll_layer_id(&self) -> ScrollLayerId {
+        // TODO(mrobinson): We should eventually make this impossible to misuse.
+        debug_assert!(!self.nodes.is_empty());
+        debug_assert!(self.nodes.contains_key(&self.topmost_scroll_layer_id));
+        self.topmost_scroll_layer_id
+    }
+
+    pub fn establish_root(&mut self,
+                          pipeline_id: PipelineId,
+                          viewport_size: &LayerSize,
+                          content_size: &LayerSize) {
+        debug_assert!(self.nodes.is_empty());
+
+        let identity = LayerToScrollTransform::identity();
+        let viewport = LayerRect::new(LayerPoint::zero(), *viewport_size);
+
+        let root_reference_frame_id = ScrollLayerId::root_reference_frame(pipeline_id);
+        self.root_reference_frame_id = root_reference_frame_id;
+        let reference_frame = ClipScrollNode::new(&viewport, viewport.size, &identity, pipeline_id);
+        self.nodes.insert(self.root_reference_frame_id, reference_frame);
+
+        let scroll_node = ClipScrollNode::new(&viewport, *content_size, &identity, pipeline_id);
+        let topmost_scroll_layer_id = ScrollLayerId::root_scroll_layer(pipeline_id);
+        self.topmost_scroll_layer_id = topmost_scroll_layer_id;
+        self.add_node(scroll_node, topmost_scroll_layer_id, root_reference_frame_id);
+    }
+
+    pub fn collect_nodes_bouncing_back(&self)
+                                       -> HashSet<ScrollLayerId, BuildHasherDefault<FnvHasher>> {
+        let mut nodes_bouncing_back = HashSet::with_hasher(Default::default());
+        for (scroll_layer_id, node) in self.nodes.iter() {
+            if node.scrolling.bouncing_back {
+                nodes_bouncing_back.insert(*scroll_layer_id);
+            }
+        }
+        nodes_bouncing_back
+    }
+
+    fn find_scrolling_node_at_point_in_node(&self,
+                                            cursor: &WorldPoint,
+                                            scroll_layer_id: ScrollLayerId)
+                                            -> Option<ScrollLayerId> {
+        self.nodes.get(&scroll_layer_id).and_then(|node| {
+            for child_layer_id in node.children.iter().rev() {
+            if let Some(layer_id) =
+                self.find_scrolling_node_at_point_in_node(cursor, *child_layer_id) {
+                    return Some(layer_id);
+                }
+            }
+
+            if let ScrollLayerInfo::ReferenceFrame(_) = scroll_layer_id.info {
+                return None;
+            }
+
+            if node.ray_intersects_node(cursor) {
+                Some(scroll_layer_id)
+            } else {
+                None
+            }
+        })
+    }
+
+    pub fn find_scrolling_node_at_point(&self, cursor: &WorldPoint) -> ScrollLayerId {
+        self.find_scrolling_node_at_point_in_node(cursor, self.root_reference_frame_id())
+            .unwrap_or(self.topmost_scroll_layer_id())
+    }
+
+    pub fn get_scroll_node_state(&self) -> Vec<ScrollLayerState> {
+        let mut result = vec![];
+        for (scroll_layer_id, scroll_node) in self.nodes.iter() {
+            match scroll_layer_id.info {
+                ScrollLayerInfo::Scrollable(_, servo_scroll_root_id) => {
+                    result.push(ScrollLayerState {
+                        pipeline_id: scroll_node.pipeline_id,
+                        scroll_root_id: servo_scroll_root_id,
+                        scroll_offset: scroll_node.scrolling.offset,
+                    })
+                }
+                ScrollLayerInfo::ReferenceFrame(..) => {}
+            }
+        }
+        result
+    }
+
+    pub fn drain(&mut self) -> ScrollStates {
+        self.current_reference_frame_id = 1;
+
+        let mut scroll_states = HashMap::with_hasher(Default::default());
+        for (layer_id, old_node) in &mut self.nodes.drain() {
+            if !self.pipelines_to_discard.contains(&layer_id.pipeline_id) {
+                scroll_states.insert(layer_id, old_node.scrolling);
+            }
+        }
+
+        self.pipelines_to_discard.clear();
+        scroll_states
+    }
+
+    pub fn scroll_nodes(&mut self,
+                        origin: LayerPoint,
+                        pipeline_id: PipelineId,
+                        scroll_root_id: ServoScrollRootId)
+                        -> bool {
+        if self.nodes.is_empty() {
+            self.pending_scroll_offsets.insert((pipeline_id, scroll_root_id), origin);
+            return false;
+        }
+
+        let origin = LayerPoint::new(origin.x.max(0.0), origin.y.max(0.0));
+
+        let mut scrolled_a_node = false;
+        let mut found_node = false;
+        for (layer_id, node) in self.nodes.iter_mut() {
+            if layer_id.pipeline_id != pipeline_id {
+                continue;
+            }
+
+            match layer_id.info {
+                ScrollLayerInfo::Scrollable(_, id) if id != scroll_root_id => continue,
+                ScrollLayerInfo::ReferenceFrame(..) => continue,
+                ScrollLayerInfo::Scrollable(..) => {}
+            }
+
+            found_node = true;
+            scrolled_a_node |= node.set_scroll_origin(&origin);
+        }
+
+        if !found_node {
+            self.pending_scroll_offsets.insert((pipeline_id, scroll_root_id), origin);
+        }
+
+        scrolled_a_node
+    }
+
+    pub fn scroll(&mut self,
+                  scroll_location: ScrollLocation,
+                  cursor: WorldPoint,
+                  phase: ScrollEventPhase)
+                  -> bool {
+        if self.nodes.is_empty() {
+            return false;
+        }
+
+        let scroll_layer_id = match (
+            phase,
+            self.find_scrolling_node_at_point(&cursor),
+            self.current_scroll_layer_id) {
+            (ScrollEventPhase::Start, scroll_node_at_point_id, _) => {
+                self.current_scroll_layer_id = Some(scroll_node_at_point_id);
+                scroll_node_at_point_id
+            },
+            (_, scroll_node_at_point_id, Some(cached_scroll_layer_id)) => {
+                let scroll_layer_id = match self.nodes.get(&cached_scroll_layer_id) {
+                    Some(_) => cached_scroll_layer_id,
+                    None => {
+                        self.current_scroll_layer_id = Some(scroll_node_at_point_id);
+                        scroll_node_at_point_id
+                    },
+                };
+                scroll_layer_id
+            },
+            (_, _, None) => return false,
+        };
+
+        let topmost_scroll_layer_id = self.topmost_scroll_layer_id();
+        let non_root_overscroll = if scroll_layer_id != topmost_scroll_layer_id {
+            // true if the current node is overscrolling,
+            // and it is not the root scroll node.
+            let child_node = self.nodes.get(&scroll_layer_id).unwrap();
+            let overscroll_amount = child_node.overscroll_amount();
+            overscroll_amount.width != 0.0 || overscroll_amount.height != 0.0
+        } else {
+            false
+        };
+
+        let switch_node = 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.
+                let mut current_node = self.nodes.get_mut(&scroll_layer_id).unwrap();
+                current_node.scrolling.should_handoff_scroll = non_root_overscroll;
+                false
+            },
+            ScrollEventPhase::Move(_) => {
+                // Switch node if movement originated in a new gesture,
+                // from a non root node in overscroll.
+                let current_node = self.nodes.get_mut(&scroll_layer_id).unwrap();
+                current_node.scrolling.should_handoff_scroll && non_root_overscroll
+            },
+            ScrollEventPhase::End => {
+                // clean-up when gesture ends.
+                let mut current_node = self.nodes.get_mut(&scroll_layer_id).unwrap();
+                current_node.scrolling.should_handoff_scroll = false;
+                false
+            },
+        };
+
+        let scroll_node_info = if switch_node {
+            topmost_scroll_layer_id.info
+        } else {
+            scroll_layer_id.info
+        };
+
+        let scroll_root_id = match scroll_node_info {
+             ScrollLayerInfo::Scrollable(_, scroll_root_id) => scroll_root_id,
+             _ => unreachable!("Tried to scroll a reference frame."),
+
+        };
+
+        let mut scrolled_a_node = false;
+        for (layer_id, node) in self.nodes.iter_mut() {
+            if layer_id.pipeline_id != scroll_layer_id.pipeline_id {
+                continue;
+            }
+
+            match layer_id.info {
+                ScrollLayerInfo::Scrollable(_, id) if id != scroll_root_id => continue,
+                ScrollLayerInfo::ReferenceFrame(..) => continue,
+                _ => {}
+            }
+
+            let scrolled_this_node = node.scroll(scroll_location, phase);
+            scrolled_a_node = scrolled_a_node || scrolled_this_node;
+        }
+        scrolled_a_node
+    }
+
+    pub fn update_all_node_transforms(&mut self) {
+        if self.nodes.is_empty() {
+            return;
+        }
+
+        let root_reference_frame_id = self.root_reference_frame_id();
+        let root_viewport = self.nodes[&root_reference_frame_id].local_viewport_rect;
+        self.update_node_transform(root_reference_frame_id,
+                                    &ScrollToWorldTransform::identity(),
+                                    &as_scroll_parent_rect(&root_viewport));
+    }
+
+    fn update_node_transform(&mut self,
+                             layer_id: ScrollLayerId,
+                             parent_world_transform: &ScrollToWorldTransform,
+                             parent_viewport_rect: &ScrollLayerRect) {
+        // TODO(gw): This is an ugly borrow check workaround to clone these.
+        //           Restructure this to avoid the clones!
+        let (node_transform_for_children, viewport_rect, node_children) = {
+            match self.nodes.get_mut(&layer_id) {
+                Some(node) => {
+                    node.update_transform(parent_world_transform, parent_viewport_rect);
+
+                    (node.world_content_transform.with_source::<ScrollLayerPixel>(),
+                     as_scroll_parent_rect(&node.combined_local_viewport_rect),
+                     node.children.clone())
+                }
+                None => return,
+            }
+        };
+
+        for child_layer_id in node_children {
+            self.update_node_transform(child_layer_id,
+                                       &node_transform_for_children,
+                                       &viewport_rect);
+        }
+    }
+
+    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) {
+        // TODO(gw): These are all independent - can be run through thread pool if it shows up
+        // in the profile!
+        for (scroll_layer_id, node) in &mut self.nodes {
+            let scrolling_state = match old_states.get(&scroll_layer_id) {
+                Some(old_scrolling_state) => *old_scrolling_state,
+                None => ScrollingState::new(),
+            };
+
+            node.finalize(&scrolling_state);
+
+            let scroll_root_id = match scroll_layer_id.info {
+                ScrollLayerInfo::Scrollable(_, scroll_root_id) => scroll_root_id,
+                _ => continue,
+            };
+
+
+            let pipeline_id = scroll_layer_id.pipeline_id;
+            if let Some(pending_offset) =
+                self.pending_scroll_offsets.remove(&(pipeline_id, scroll_root_id)) {
+                node.set_scroll_origin(&pending_offset);
+            }
+        }
+
+    }
+
+    pub fn add_reference_frame(&mut self,
+                               rect: LayerRect,
+                               transform: LayerToScrollTransform,
+                               pipeline_id: PipelineId,
+                               parent_id: ScrollLayerId) -> ScrollLayerId {
+        let reference_frame_id = ScrollLayerId {
+            pipeline_id: pipeline_id,
+            info: ScrollLayerInfo::ReferenceFrame(self.current_reference_frame_id),
+        };
+        self.current_reference_frame_id += 1;
+
+        let node = ClipScrollNode::new(&rect, rect.size, &transform, pipeline_id);
+        self.add_node(node, reference_frame_id, parent_id);
+        reference_frame_id
+    }
+
+    pub fn add_node(&mut self, node: ClipScrollNode, id: ScrollLayerId, parent_id: ScrollLayerId) {
+        debug_assert!(!self.nodes.contains_key(&id));
+        self.nodes.insert(id, node);
+
+        debug_assert!(parent_id != id);
+        self.nodes.get_mut(&parent_id).unwrap().add_child(id);
+    }
+
+    pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
+        self.pipelines_to_discard.insert(pipeline_id);
+
+        match self.current_scroll_layer_id {
+            Some(id) if id.pipeline_id == pipeline_id => self.current_scroll_layer_id = None,
+            _ => {}
+        }
+    }
+}
+
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -37,22 +37,16 @@ const GL_FORMAT_BGRA: gl::GLuint = gl::B
 #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
 const SHADER_VERSION: &'static str = "#version 150\n";
 
 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
 const SHADER_VERSION: &'static str = "#version 300 es\n";
 
 static SHADER_PREAMBLE: &'static str = "shared";
 
-lazy_static! {
-    pub static ref MAX_TEXTURE_SIZE: gl::GLint = {
-        gl::get_integer_v(gl::MAX_TEXTURE_SIZE)
-    };
-}
-
 #[repr(u32)]
 pub enum DepthFunction {
     Less = gl::LESS,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum TextureTarget {
     Default,
@@ -820,16 +814,18 @@ pub struct Device {
 
     // misc.
     shader_preamble: String,
     //file_watcher: FileWatcherThread,
 
     // Used on android only
     #[allow(dead_code)]
     next_vao_id: gl::GLuint,
+
+    max_texture_size: u32,
 }
 
 impl Device {
     pub fn new(resource_override_path: Option<PathBuf>,
                _file_changed_handler: Box<FileWatcherHandler>) -> Device {
         //let file_watcher = FileWatcherThread::new(file_changed_handler);
 
         let shader_preamble = get_shader_source(SHADER_PREAMBLE, &resource_override_path);
@@ -858,19 +854,25 @@ impl Device {
             textures: HashMap::with_hasher(Default::default()),
             programs: HashMap::with_hasher(Default::default()),
             vaos: HashMap::with_hasher(Default::default()),
 
             shader_preamble: shader_preamble,
 
             next_vao_id: 1,
             //file_watcher: file_watcher,
+
+            max_texture_size: gl::get_integer_v(gl::MAX_TEXTURE_SIZE) as u32
         }
     }
 
+    pub fn max_texture_size(&self) -> u32 {
+        self.max_texture_size
+    }
+
     pub fn get_capabilities(&self) -> &Capabilities {
         &self.capabilities
     }
 
     pub fn compile_shader(name: &str,
                           source_str: &str,
                           shader_type: gl::GLenum,
                           shader_preamble: &[String])
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -3,20 +3,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
 use fnv::FnvHasher;
 use internal_types::{ANGLE_FLOAT_TO_FIXED, AxisDirection};
 use internal_types::{LowLevelFilterOp};
 use internal_types::{RendererFrame};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
-use layer::Layer;
+use clip_scroll_node::ClipScrollNode;
 use resource_cache::ResourceCache;
 use scene::{Scene, SceneProperties};
-use scroll_tree::{ScrollTree, ScrollStates};
+use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
 use tiling::{AuxiliaryListsMap, CompositeOps, PrimitiveFlags};
 use webrender_traits::{AuxiliaryLists, ClipRegion, ColorF, DisplayItem, Epoch, FilterOp};
 use webrender_traits::{LayerPoint, LayerRect, LayerSize, LayerToScrollTransform, LayoutTransform};
 use webrender_traits::{MixBlendMode, PipelineId, ScrollEventPhase, ScrollLayerId, ScrollLayerState};
 use webrender_traits::{ScrollLocation, ScrollPolicy, ServoScrollRootId, SpecificDisplayItem};
 use webrender_traits::{StackingContext, WorldPoint};
@@ -28,17 +28,17 @@ static DEFAULT_SCROLLBAR_COLOR: ColorF =
 
 struct FlattenContext<'a> {
     scene: &'a Scene,
     builder: &'a mut FrameBuilder,
 }
 
 // TODO: doc
 pub struct Frame {
-    pub scroll_tree: ScrollTree,
+    pub clip_scroll_tree: ClipScrollTree,
     pub pipeline_epoch_map: HashMap<PipelineId, Epoch, BuildHasherDefault<FnvHasher>>,
     pub pipeline_auxiliary_lists: AuxiliaryListsMap,
     id: FrameId,
     frame_builder_config: FrameBuilderConfig,
     frame_builder: Option<FrameBuilder>,
 }
 
 trait DisplayListHelpers {
@@ -175,60 +175,60 @@ impl<'a> Iterator for DisplayListTravers
     }
 }
 
 impl Frame {
     pub fn new(config: FrameBuilderConfig) -> Frame {
         Frame {
             pipeline_epoch_map: HashMap::with_hasher(Default::default()),
             pipeline_auxiliary_lists: HashMap::with_hasher(Default::default()),
-            scroll_tree: ScrollTree::new(),
+            clip_scroll_tree: ClipScrollTree::new(),
             id: FrameId(0),
             frame_builder: None,
             frame_builder_config: config,
         }
     }
 
     pub fn reset(&mut self) -> ScrollStates {
         self.pipeline_epoch_map.clear();
 
         // Advance to the next frame.
         self.id.0 += 1;
 
-        self.scroll_tree.drain()
+        self.clip_scroll_tree.drain()
     }
 
-    pub fn get_scroll_layer_state(&self) -> Vec<ScrollLayerState> {
-        self.scroll_tree.get_scroll_layer_state()
+    pub fn get_scroll_node_state(&self) -> Vec<ScrollLayerState> {
+        self.clip_scroll_tree.get_scroll_node_state()
     }
 
-    /// Returns true if any layers actually changed position or false otherwise.
-    pub fn scroll_layers(&mut self,
-                         origin: LayerPoint,
-                         pipeline_id: PipelineId,
-                         scroll_root_id: ServoScrollRootId)
-                          -> bool {
-        self.scroll_tree.scroll_layers(origin, pipeline_id, scroll_root_id)
+    /// Returns true if any nodes actually changed position or false otherwise.
+    pub fn scroll_nodes(&mut self,
+                        origin: LayerPoint,
+                        pipeline_id: PipelineId,
+                        scroll_root_id: ServoScrollRootId)
+                         -> bool {
+        self.clip_scroll_tree.scroll_nodes(origin, pipeline_id, scroll_root_id)
     }
 
-    /// Returns true if any layers actually changed position or false otherwise.
+    /// 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.scroll_tree.scroll(scroll_location, cursor, phase,)
+        self.clip_scroll_tree.scroll(scroll_location, cursor, phase,)
     }
 
     pub fn tick_scrolling_bounce_animations(&mut self) {
-        self.scroll_tree.tick_scrolling_bounce_animations();
+        self.clip_scroll_tree.tick_scrolling_bounce_animations();
     }
 
     pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
-        self.scroll_tree.discard_frame_state_for_pipeline(pipeline_id);
+        self.clip_scroll_tree.discard_frame_state_for_pipeline(pipeline_id);
     }
 
     pub fn create(&mut self, scene: &Scene) {
         let root_pipeline_id = match scene.root_pipeline_id {
             Some(root_pipeline_id) => root_pipeline_id,
             None => return,
         };
 
@@ -251,19 +251,19 @@ impl Frame {
         let (root_stacking_context, root_clip) = match display_list.starting_stacking_context() {
             Some(some) => some,
             None => {
                 warn!("Pipeline display list does not start with a stacking context.");
                 return;
             }
         };
 
-        self.scroll_tree.establish_root(root_pipeline_id,
-                                        &root_pipeline.viewport_size,
-                                        &root_clip.main.size);
+        self.clip_scroll_tree.establish_root(root_pipeline_id,
+                                             &root_pipeline.viewport_size,
+                                             &root_clip.main.size);
 
         let background_color = root_pipeline.background_color.and_then(|color| {
             if color.a > 0.0 {
                 Some(color)
             } else {
                 None
             }
         });
@@ -274,47 +274,47 @@ impl Frame {
 
         {
             let mut context = FlattenContext {
                 scene: scene,
                 builder: &mut frame_builder,
             };
 
             let mut traversal = DisplayListTraversal::new_skipping_first(display_list);
-            let reference_frame_id = self.scroll_tree.root_reference_frame_id();
-            let topmost_scroll_layer_id = self.scroll_tree.topmost_scroll_layer_id();
+            let reference_frame_id = self.clip_scroll_tree.root_reference_frame_id();
+            let topmost_scroll_layer_id = self.clip_scroll_tree.topmost_scroll_layer_id();
             debug_assert!(reference_frame_id != topmost_scroll_layer_id);
 
             let viewport_rect = LayerRect::new(LayerPoint::zero(), root_pipeline.viewport_size);
             let clip = ClipRegion::simple(&viewport_rect);
-            context.builder.push_scroll_layer(reference_frame_id,
-                                              &clip,
-                                              &LayerPoint::zero(),
-                                              &root_pipeline.viewport_size);
-            context.builder.push_scroll_layer(topmost_scroll_layer_id,
-                                              &clip,
-                                              &LayerPoint::zero(),
-                                              &root_clip.main.size);
+            context.builder.push_clip_scroll_node(reference_frame_id,
+                                                  &clip,
+                                                  &LayerPoint::zero(),
+                                                  &root_pipeline.viewport_size);
+            context.builder.push_clip_scroll_node(topmost_scroll_layer_id,
+                                                  &clip,
+                                                  &LayerPoint::zero(),
+                                                  &root_clip.main.size);
 
             self.flatten_stacking_context(&mut traversal,
                                           root_pipeline_id,
                                           &mut context,
                                           reference_frame_id,
                                           topmost_scroll_layer_id,
                                           LayerToScrollTransform::identity(),
                                           0,
                                           &root_stacking_context,
                                           root_clip);
 
-            context.builder.pop_scroll_layer();
-            context.builder.pop_scroll_layer();
+            context.builder.pop_clip_scroll_node();
+            context.builder.pop_clip_scroll_node();
         }
 
         self.frame_builder = Some(frame_builder);
-        self.scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
+        self.clip_scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
     }
 
     fn flatten_scroll_layer<'a>(&mut self,
                                 traversal: &mut DisplayListTraversal<'a>,
                                 pipeline_id: PipelineId,
                                 context: &mut FlattenContext,
                                 current_reference_frame_id: ScrollLayerId,
                                 parent_scroll_layer_id: ScrollLayerId,
@@ -325,32 +325,35 @@ impl Frame {
                                 new_scroll_layer_id: ScrollLayerId) {
         // Avoid doing unnecessary work for empty stacking contexts.
         if traversal.current_stacking_context_empty() {
             traversal.skip_current_stacking_context();
             return;
         }
 
         let clip_rect = clip.main;
-        let layer = Layer::new(&clip_rect, *content_size, &layer_relative_transform, pipeline_id);
-        self.scroll_tree.add_layer(layer, new_scroll_layer_id, parent_scroll_layer_id);
-        context.builder.push_scroll_layer(new_scroll_layer_id,
-                                          clip,
-                                          &clip_rect.origin,
-                                          &content_size);
+        let node = ClipScrollNode::new(&clip_rect,
+                                       *content_size,
+                                       &layer_relative_transform,
+                                       pipeline_id);
+        self.clip_scroll_tree.add_node(node, new_scroll_layer_id, parent_scroll_layer_id);
+        context.builder.push_clip_scroll_node(new_scroll_layer_id,
+                                              clip,
+                                              &clip_rect.origin,
+                                              &content_size);
 
         self.flatten_items(traversal,
                            pipeline_id,
                            context,
                            current_reference_frame_id,
                            new_scroll_layer_id,
                            LayerToScrollTransform::identity(),
                            level);
 
-        context.builder.pop_scroll_layer();
+        context.builder.pop_clip_scroll_node();
     }
 
     fn flatten_stacking_context<'a>(&mut self,
                                     traversal: &mut DisplayListTraversal<'a>,
                                     pipeline_id: PipelineId,
                                     context: &mut FlattenContext,
                                     current_reference_frame_id: ScrollLayerId,
                                     current_scroll_layer_id: ScrollLayerId,
@@ -394,20 +397,20 @@ impl Frame {
             ScrollPolicy::Fixed => current_reference_frame_id,
             ScrollPolicy::Scrollable => current_scroll_layer_id,
         };
 
         // If we have a transformation, we establish a new reference frame. This means
         // that fixed position stacking contexts are positioned relative to us.
         if stacking_context_transform != LayoutTransform::identity() ||
            stacking_context.perspective != LayoutTransform::identity() {
-            scroll_layer_id = self.scroll_tree.add_reference_frame(clip_region.main,
-                                                                   transform,
-                                                                   pipeline_id,
-                                                                   scroll_layer_id);
+            scroll_layer_id = self.clip_scroll_tree.add_reference_frame(clip_region.main,
+                                                                        transform,
+                                                                        pipeline_id,
+                                                                        scroll_layer_id);
             reference_frame_id = scroll_layer_id;
             transform = LayerToScrollTransform::identity();
         }
 
         if level == 0 {
             if let Some(pipeline) = context.scene.pipeline_map.get(&pipeline_id) {
                 if let Some(bg_color) = pipeline.background_color {
 
@@ -415,17 +418,17 @@ impl Frame {
                     let no_clip = ClipRegion::simple(&clip_region.main);
                     context.builder.push_stacking_context(clip_region.main,
                                                           transform,
                                                           pipeline_id,
                                                           scroll_layer_id,
                                                           CompositeOps::empty());
 
                     //Note: we don't use the original clip region here,
-                    // it's already processed by the layer we just pushed.
+                    // it's already processed by the node we just pushed.
                     context.builder.add_solid_rectangle(&clip_region.main,
                                                         &no_clip,
                                                         &bg_color,
                                                         PrimitiveFlags::None);
 
                     context.builder.pop_stacking_context();
                 }
             }
@@ -447,17 +450,17 @@ impl Frame {
                            level);
 
         if level == 0 && self.frame_builder_config.enable_scrollbars {
             let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0));
             context.builder.add_solid_rectangle(
                 &scrollbar_rect,
                 &ClipRegion::simple(&scrollbar_rect),
                 &DEFAULT_SCROLLBAR_COLOR,
-                PrimitiveFlags::Scrollbar(self.scroll_tree.topmost_scroll_layer_id, 4.0));
+                PrimitiveFlags::Scrollbar(self.clip_scroll_tree.topmost_scroll_layer_id(), 4.0));
         }
 
         context.builder.pop_stacking_context();
     }
 
     fn flatten_iframe<'a>(&mut self,
                           pipeline_id: PipelineId,
                           bounds: &LayerRect,
@@ -486,52 +489,52 @@ impl Frame {
 
         self.pipeline_epoch_map.insert(pipeline_id, pipeline.epoch);
 
         let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
         let transform = layer_relative_transform.pre_translated(bounds.origin.x,
                                                                 bounds.origin.y,
                                                                 0.0);
         let iframe_reference_frame_id =
-            self.scroll_tree.add_reference_frame(iframe_rect,
-                                                 transform,
-                                                 pipeline_id,
-                                                 current_scroll_layer_id);
+            self.clip_scroll_tree.add_reference_frame(iframe_rect,
+                                                      transform,
+                                                      pipeline_id,
+                                                      current_scroll_layer_id);
         let iframe_scroll_layer_id = ScrollLayerId::root_scroll_layer(pipeline_id);
-        let layer = Layer::new(&LayerRect::new(LayerPoint::zero(), iframe_rect.size),
-                               iframe_clip.main.size,
-                               &LayerToScrollTransform::identity(),
-                               pipeline_id);
-        self.scroll_tree.add_layer(layer.clone(),
-                                   iframe_scroll_layer_id,
-                                   iframe_reference_frame_id);
+        let node = ClipScrollNode::new(&LayerRect::new(LayerPoint::zero(), iframe_rect.size),
+                                       iframe_clip.main.size,
+                                       &LayerToScrollTransform::identity(),
+                                       pipeline_id);
+        self.clip_scroll_tree.add_node(node.clone(),
+                                       iframe_scroll_layer_id,
+                                       iframe_reference_frame_id);
 
-        context.builder.push_scroll_layer(iframe_reference_frame_id,
-                                          iframe_clip,
-                                          &LayerPoint::zero(),
-                                          &iframe_rect.size);
-        context.builder.push_scroll_layer(iframe_scroll_layer_id,
-                                          iframe_clip,
-                                          &LayerPoint::zero(),
-                                          &iframe_clip.main.size);
+        context.builder.push_clip_scroll_node(iframe_reference_frame_id,
+                                              iframe_clip,
+                                              &LayerPoint::zero(),
+                                              &iframe_rect.size);
+        context.builder.push_clip_scroll_node(iframe_scroll_layer_id,
+                                              iframe_clip,
+                                              &LayerPoint::zero(),
+                                              &iframe_clip.main.size);
 
         let mut traversal = DisplayListTraversal::new_skipping_first(display_list);
 
         self.flatten_stacking_context(&mut traversal,
                                       pipeline_id,
                                       context,
                                       iframe_reference_frame_id,
                                       iframe_scroll_layer_id,
                                       LayerToScrollTransform::identity(),
                                       0,
                                       &iframe_stacking_context,
                                       iframe_clip);
 
-        context.builder.pop_scroll_layer();
-        context.builder.pop_scroll_layer();
+        context.builder.pop_clip_scroll_node();
+        context.builder.pop_clip_scroll_node();
     }
 
     fn flatten_items<'a>(&mut self,
                          traversal: &mut DisplayListTraversal<'a>,
                          pipeline_id: PipelineId,
                          context: &mut FlattenContext,
                          current_reference_frame_id: ScrollLayerId,
                          current_scroll_layer_id: ScrollLayerId,
@@ -543,16 +546,17 @@ impl Frame {
                     context.builder.add_webgl_rectangle(item.rect,
                                                         &item.clip, info.context_id);
                 }
                 SpecificDisplayItem::Image(ref info) => {
                     context.builder.add_image(item.rect,
                                               &item.clip,
                                               &info.stretch_size,
                                               &info.tile_spacing,
+                                              None,
                                               info.image_key,
                                               info.image_rendering);
                 }
                 SpecificDisplayItem::YuvImage(ref info) => {
                     context.builder.add_yuv_image(item.rect,
                                                   &item.clip,
                                                   info.y_image_key,
                                                   info.u_image_key,
@@ -642,34 +646,34 @@ impl Frame {
         }
     }
 
     pub fn build(&mut self,
                  resource_cache: &mut ResourceCache,
                  auxiliary_lists_map: &AuxiliaryListsMap,
                  device_pixel_ratio: f32)
                  -> RendererFrame {
-        self.scroll_tree.update_all_layer_transforms();
+        self.clip_scroll_tree.update_all_node_transforms();
         let frame = self.build_frame(resource_cache,
                                      auxiliary_lists_map,
                                      device_pixel_ratio);
         resource_cache.expire_old_resources(self.id);
         frame
     }
 
     fn build_frame(&mut self,
                    resource_cache: &mut ResourceCache,
                    auxiliary_lists_map: &AuxiliaryListsMap,
                    device_pixel_ratio: f32) -> RendererFrame {
         let mut frame_builder = self.frame_builder.take();
         let frame = frame_builder.as_mut().map(|builder|
             builder.build(resource_cache,
                           self.id,
-                          &self.scroll_tree,
+                          &self.clip_scroll_tree,
                           auxiliary_lists_map,
                           device_pixel_ratio)
         );
         self.frame_builder = frame_builder;
 
-        let layers_bouncing_back = self.scroll_tree.collect_layers_bouncing_back();
-        RendererFrame::new(self.pipeline_epoch_map.clone(), layers_bouncing_back, frame)
+        let nodes_bouncing_back = self.clip_scroll_tree.collect_nodes_bouncing_back();
+        RendererFrame::new(self.pipeline_epoch_map.clone(), nodes_bouncing_back, frame)
     }
 }
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -8,35 +8,83 @@ use frame::FrameId;
 use gpu_store::GpuStoreAddress;
 use internal_types::{HardwareCompositeOp, SourceTexture};
 use mask_cache::{ClipSource, MaskCacheInfo};
 use prim_store::{BorderPrimitiveCpu, BorderPrimitiveGpu, BoxShadowPrimitiveGpu};
 use prim_store::{GradientPrimitiveCpu, GradientPrimitiveGpu, ImagePrimitiveCpu, ImagePrimitiveGpu};
 use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveGeometry, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu, RadialGradientPrimitiveGpu};
 use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextRunPrimitiveGpu};
-use prim_store::{YuvImagePrimitiveCpu, YuvImagePrimitiveGpu};
+use prim_store::{TexelRect, YuvImagePrimitiveCpu, YuvImagePrimitiveGpu};
 use profiler::FrameProfileCounters;
 use render_task::{AlphaRenderItem, MaskCacheKey, MaskResult, RenderTask, RenderTaskIndex};
 use render_task::RenderTaskLocation;
 use resource_cache::ResourceCache;
-use scroll_tree::ScrollTree;
+use clip_scroll_tree::ClipScrollTree;
 use std::{cmp, f32, i32, mem, usize};
-use tiling::{AuxiliaryListsMap, CompositeOps, Frame, PackedLayer, PackedLayerIndex};
-use tiling::{PrimitiveFlags, PrimitiveRunCmd, RenderPass, RenderTargetContext};
-use tiling::{RenderTaskCollection, ScrollbarPrimitive, ScrollLayer, ScrollLayerIndex};
-use tiling::{StackingContext, StackingContextIndex};
+use tiling::{AuxiliaryListsMap, ClipScrollGroup, ClipScrollGroupIndex, CompositeOps, Frame};
+use tiling::{PackedLayer, PackedLayerIndex, PrimitiveFlags, PrimitiveRunCmd, RenderPass};
+use tiling::{RenderTargetContext, RenderTaskCollection, ScrollbarPrimitive, ScrollLayer};
+use tiling::{ScrollLayerIndex, StackingContext, StackingContextIndex};
 use util::{self, pack_as_float, rect_from_points_f, subtract_rect, TransformedRect};
-use util::TransformedRectKind;
-use webrender_traits::{as_scroll_parent_rect, BorderDisplayItem, BorderSide, BorderStyle};
+use util::{RectHelpers, TransformedRectKind};
+use webrender_traits::{as_scroll_parent_rect, BorderDetails, BorderDisplayItem, BorderSide, BorderStyle};
 use webrender_traits::{BoxShadowClipMode, ClipRegion, ColorF, device_length, DeviceIntPoint};
 use webrender_traits::{DeviceIntRect, DeviceIntSize, DeviceUintSize, ExtendMode, FontKey};
 use webrender_traits::{FontRenderMode, GlyphOptions, ImageKey, ImageRendering, ItemRange};
 use webrender_traits::{LayerPoint, LayerRect, LayerSize, LayerToScrollTransform, PipelineId};
-use webrender_traits::{ScrollLayerId, ScrollLayerPixel, WebGLContextId, YuvColorSpace};
+use webrender_traits::{RepeatMode, ScrollLayerId, ScrollLayerPixel, WebGLContextId, YuvColorSpace};
+
+#[derive(Debug, Clone)]
+struct ImageBorderSegment {
+    geom_rect: LayerRect,
+    sub_rect: TexelRect,
+    stretch_size: LayerSize,
+    tile_spacing: LayerSize,
+}
+
+impl ImageBorderSegment {
+    fn new(rect: LayerRect,
+           sub_rect: TexelRect,
+           repeat_horizontal: RepeatMode,
+           repeat_vertical: RepeatMode) -> ImageBorderSegment {
+        let tile_spacing = LayerSize::zero();
+
+        debug_assert!(sub_rect.uv1.x >= sub_rect.uv0.x);
+        debug_assert!(sub_rect.uv1.y >= sub_rect.uv0.y);
+
+        let image_size = LayerSize::new(sub_rect.uv1.x - sub_rect.uv0.x,
+                                        sub_rect.uv1.y - sub_rect.uv0.y);
+
+        let stretch_size_x = match repeat_horizontal {
+            RepeatMode::Stretch => rect.size.width,
+            RepeatMode::Repeat => image_size.width,
+            RepeatMode::Round | RepeatMode::Space => {
+                println!("Round/Space not supported yet!");
+                rect.size.width
+            }
+        };
+
+        let stretch_size_y = match repeat_vertical {
+            RepeatMode::Stretch => rect.size.height,
+            RepeatMode::Repeat => image_size.height,
+            RepeatMode::Round | RepeatMode::Space => {
+                println!("Round/Space not supported yet!");
+                rect.size.height
+            }
+        };
+
+        ImageBorderSegment {
+            geom_rect: rect,
+            sub_rect: sub_rect,
+            stretch_size: LayerSize::new(stretch_size_x, stretch_size_y),
+            tile_spacing: tile_spacing,
+        }
+    }
+}
 
 #[derive(Clone, Copy)]
 pub struct FrameBuilderConfig {
     pub enable_scrollbars: bool,
     pub enable_subpixel_aa: bool,
     pub debug: bool,
 }
 
@@ -57,39 +105,42 @@ pub struct FrameBuilder {
     screen_rect: LayerRect,
     background_color: Option<ColorF>,
     prim_store: PrimitiveStore,
     cmds: Vec<PrimitiveRunCmd>,
     config: FrameBuilderConfig,
 
     stacking_context_store: Vec<StackingContext>,
     scroll_layer_store: Vec<ScrollLayer>,
+    clip_scroll_group_store: Vec<ClipScrollGroup>,
     packed_layers: Vec<PackedLayer>,
 
     scrollbar_prims: Vec<ScrollbarPrimitive>,
 
-    /// A stack of scroll layers used during building to properly parent new scroll layers.
-    scroll_layer_stack: Vec<ScrollLayerIndex>,
+    /// A stack of scroll nodes used during display list processing to properly
+    /// parent new scroll nodes.
+    clip_scroll_node_stack: Vec<ScrollLayerIndex>,
 }
 
 impl FrameBuilder {
     pub fn new(viewport_size: LayerSize,
                background_color: Option<ColorF>,
                config: FrameBuilderConfig) -> FrameBuilder {
         FrameBuilder {
             screen_rect: LayerRect::new(LayerPoint::zero(), viewport_size),
             background_color: background_color,
             stacking_context_store: Vec::new(),
             scroll_layer_store: Vec::new(),
+            clip_scroll_group_store: Vec::new(),
             prim_store: PrimitiveStore::new(),
             cmds: Vec::new(),
             packed_layers: Vec::new(),
             scrollbar_prims: Vec::new(),
             config: config,
-            scroll_layer_stack: Vec::new(),
+            clip_scroll_node_stack: Vec::new(),
         }
     }
 
     fn add_primitive(&mut self,
                      rect: &LayerRect,
                      clip_region: &ClipRegion,
                      container: PrimitiveContainer) -> PrimitiveIndex {
 
@@ -123,88 +174,103 @@ impl FrameBuilder {
             &mut PrimitiveRunCmd::PopScrollLayer => {}
         }
 
         self.cmds.push(PrimitiveRunCmd::PrimitiveRun(prim_index, 1));
 
         prim_index
     }
 
+    pub fn create_clip_scroll_group(&mut self,
+                                    stacking_context_index: StackingContextIndex,
+                                    scroll_layer_id: ScrollLayerId,
+                                    pipeline_id: PipelineId)
+                                    -> ClipScrollGroupIndex {
+        let packed_layer_index = PackedLayerIndex(self.packed_layers.len());
+        self.packed_layers.push(PackedLayer::empty());
+
+        self.clip_scroll_group_store.push(ClipScrollGroup {
+            stacking_context_index: stacking_context_index,
+            scroll_layer_id: scroll_layer_id,
+            packed_layer_index: packed_layer_index,
+            pipeline_id: pipeline_id,
+            xf_rect: None,
+         });
+
+        ClipScrollGroupIndex(self.clip_scroll_group_store.len() - 1)
+    }
+
     pub fn push_stacking_context(&mut self,
                                  rect: LayerRect,
                                  transform: LayerToScrollTransform,
                                  pipeline_id: PipelineId,
                                  scroll_layer_id: ScrollLayerId,
                                  composite_ops: CompositeOps) {
         let stacking_context_index = StackingContextIndex(self.stacking_context_store.len());
-        let packed_layer_index = PackedLayerIndex(self.packed_layers.len());
-
-        self.stacking_context_store.push(StackingContext {
-            local_rect: rect,
-            local_transform: transform,
-            scroll_layer_id: scroll_layer_id,
-            pipeline_id: pipeline_id,
-            xf_rect: None,
-            composite_ops: composite_ops,
-            packed_layer_index: packed_layer_index,
-        });
-        self.packed_layers.push(PackedLayer::empty());
+        let group_index = self.create_clip_scroll_group(stacking_context_index,
+                                                        scroll_layer_id,
+                                                        pipeline_id);
+        self.stacking_context_store.push(StackingContext::new(pipeline_id,
+                                                              transform,
+                                                              rect,
+                                                              composite_ops,
+                                                              group_index));
         self.cmds.push(PrimitiveRunCmd::PushStackingContext(stacking_context_index));
-
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.cmds.push(PrimitiveRunCmd::PopStackingContext);
     }
 
-    pub fn push_scroll_layer(&mut self,
-                             scroll_layer_id: ScrollLayerId,
-                             clip_region: &ClipRegion,
-                             iframe_origin: &LayerPoint,
-                             content_size: &LayerSize) {
+    pub fn push_clip_scroll_node(&mut self,
+                                 scroll_layer_id: ScrollLayerId,
+                                 clip_region: &ClipRegion,
+                                 node_origin: &LayerPoint,
+                                 content_size: &LayerSize) {
         let scroll_layer_index = ScrollLayerIndex(self.scroll_layer_store.len());
+        let parent_index = *self.clip_scroll_node_stack.last().unwrap_or(&scroll_layer_index);
+        self.clip_scroll_node_stack.push(scroll_layer_index);
+
         let packed_layer_index = PackedLayerIndex(self.packed_layers.len());
 
         let clip_source = ClipSource::Region(clip_region.clone());
         let clip_info = MaskCacheInfo::new(&clip_source,
                                            true, // needs an extra clip for the clip rectangle
                                            &mut self.prim_store.gpu_data32);
 
-        let parent_index = *self.scroll_layer_stack.last().unwrap_or(&scroll_layer_index);
         self.scroll_layer_store.push(ScrollLayer {
             scroll_layer_id: scroll_layer_id,
             parent_index: parent_index,
             clip_source: clip_source,
             clip_cache_info: clip_info,
             xf_rect: None,
             packed_layer_index: packed_layer_index,
         });
         self.packed_layers.push(PackedLayer::empty());
         self.cmds.push(PrimitiveRunCmd::PushScrollLayer(scroll_layer_index));
 
 
         // We need to push a fake stacking context here, because primitives that are
         // direct children of this stacking context, need to be adjusted by the scroll
         // offset of this layer. Eventually we should be able to remove this.
         let rect = LayerRect::new(LayerPoint::zero(),
-                                  LayerSize::new(content_size.width + iframe_origin.x,
-                                                 content_size.height + iframe_origin.y));
+                                  LayerSize::new(content_size.width + node_origin.x,
+                                                 content_size.height + node_origin.y));
         self.push_stacking_context(rect,
                                    LayerToScrollTransform::identity(),
                                    scroll_layer_id.pipeline_id,
                                    scroll_layer_id,
                                    CompositeOps::empty());
 
-        self.scroll_layer_stack.push(scroll_layer_index);
     }
 
-    pub fn pop_scroll_layer(&mut self) {
+    pub fn pop_clip_scroll_node(&mut self) {
         self.pop_stacking_context();
         self.cmds.push(PrimitiveRunCmd::PopScrollLayer);
-        self.scroll_layer_stack.pop();
+        self.clip_scroll_node_stack.pop();
     }
 
     pub fn add_solid_rectangle(&mut self,
                                rect: &LayerRect,
                                clip_region: &ClipRegion,
                                color: &ColorF,
                                flags: PrimitiveFlags) {
         if color.a == 0.0 {
@@ -249,128 +315,233 @@ impl FrameBuilder {
                 return false;
             }
         }
     }
 
     pub fn add_border(&mut self,
                       rect: LayerRect,
                       clip_region: &ClipRegion,
-                      border: &BorderDisplayItem) {
-        let radius = &border.radius;
-        let left = &border.left;
-        let right = &border.right;
-        let top = &border.top;
-        let bottom = &border.bottom;
-
-        if !self.supported_style(left) || !self.supported_style(right) ||
-           !self.supported_style(top) || !self.supported_style(bottom) {
-            println!("Unsupported border style, not rendering border");
-            return;
-        }
+                      border_item: &BorderDisplayItem) {
+        match border_item.details {
+            BorderDetails::Image(ref border) => {
+                // Calculate the modified rect as specific by border-image-outset
+                let origin = LayerPoint::new(rect.origin.x - border.outset.left,
+                                             rect.origin.y - border.outset.top);
+                let size = LayerSize::new(rect.size.width + border.outset.left + border.outset.right,
+                                          rect.size.height + border.outset.top + border.outset.bottom);
+                let rect = LayerRect::new(origin, size);
 
-        // These colors are used during inset/outset scaling.
-        let left_color      = left.border_color(1.0, 2.0/3.0, 0.3, 0.7);
-        let top_color       = top.border_color(1.0, 2.0/3.0, 0.3, 0.7);
-        let right_color     = right.border_color(2.0/3.0, 1.0, 0.7, 0.3);
-        let bottom_color    = bottom.border_color(2.0/3.0, 1.0, 0.7, 0.3);
+                // Calculate the local texel coords of the slices.
+                let px0 = 0;
+                let px1 = border.patch.slice.left;
+                let px2 = border.patch.width - border.patch.slice.right;
+                let px3 = border.patch.width;
 
-        let tl_outer = LayerPoint::new(rect.origin.x, rect.origin.y);
-        let tl_inner = tl_outer + LayerPoint::new(radius.top_left.width.max(left.width),
-                                                  radius.top_left.height.max(top.width));
+                let py0 = 0;
+                let py1 = border.patch.slice.top;
+                let py2 = border.patch.height - border.patch.slice.bottom;
+                let py3 = border.patch.height;
 
-        let tr_outer = LayerPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
-        let tr_inner = tr_outer + LayerPoint::new(-radius.top_right.width.max(right.width),
-                                                  radius.top_right.height.max(top.width));
+                let tl_outer = LayerPoint::new(rect.origin.x, rect.origin.y);
+                let tl_inner = tl_outer + LayerPoint::new(border_item.widths.left, border_item.widths.top);
 
-        let bl_outer = LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
-        let bl_inner = bl_outer + LayerPoint::new(radius.bottom_left.width.max(left.width),
-                                                  -radius.bottom_left.height.max(bottom.width));
+                let tr_outer = LayerPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
+                let tr_inner = tr_outer + LayerPoint::new(-border_item.widths.right, border_item.widths.top);
+
+                let bl_outer = LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
+                let bl_inner = bl_outer + LayerPoint::new(border_item.widths.left, -border_item.widths.bottom);
 
-        let br_outer = LayerPoint::new(rect.origin.x + rect.size.width,
-                                       rect.origin.y + rect.size.height);
-        let br_inner = br_outer - LayerPoint::new(radius.bottom_right.width.max(right.width),
-                                                  radius.bottom_right.height.max(bottom.width));
+                let br_outer = LayerPoint::new(rect.origin.x + rect.size.width,
+                                               rect.origin.y + rect.size.height);
+                let br_inner = br_outer - LayerPoint::new(border_item.widths.right, border_item.widths.bottom);
+
+                // Build the list of image segments
+                let mut segments = vec![
+                    // Top left
+                    ImageBorderSegment::new(LayerRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
+                                            TexelRect::new(px0, py0, px1, py1),
+                                            RepeatMode::Stretch,
+                                            RepeatMode::Stretch),
 
-        // The border shader is quite expensive. For simple borders, we can just draw
-        // the border with a few rectangles. This generally gives better batching, and
-        // a GPU win in fragment shader time.
-        // More importantly, the software (OSMesa) implementation we run tests on is
-        // particularly slow at running our complex border shader, compared to the
-        // rectangle shader. This has the effect of making some of our tests time
-        // out more often on CI (the actual cause is simply too many Servo processes and
-        // threads being run on CI at once).
-        // TODO(gw): Detect some more simple cases and handle those with simpler shaders too.
-        // TODO(gw): Consider whether it's only worth doing this for large rectangles (since
-        //           it takes a little more CPU time to handle multiple rectangles compared
-        //           to a single border primitive).
-        if left.style == BorderStyle::Solid {
-            let same_color = left_color == top_color &&
-                             left_color == right_color &&
-                             left_color == bottom_color;
-            let same_style = left.style == top.style &&
-                             left.style == right.style &&
-                             left.style == bottom.style;
+                    // Top right
+                    ImageBorderSegment::new(LayerRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
+                                            TexelRect::new(px2, py0, px3, py1),
+                                            RepeatMode::Stretch,
+                                            RepeatMode::Stretch),
 
-            if same_color && same_style && radius.is_zero() {
-                let rects = [
-                    LayerRect::new(rect.origin,
-                                   LayerSize::new(rect.size.width, top.width)),
-                    LayerRect::new(LayerPoint::new(tl_outer.x, tl_inner.y),
-                                   LayerSize::new(left.width,
-                                                  rect.size.height - top.width - bottom.width)),
-                    LayerRect::new(tr_inner,
-                                   LayerSize::new(right.width,
-                                                  rect.size.height - top.width - bottom.width)),
-                    LayerRect::new(LayerPoint::new(bl_outer.x, bl_inner.y),
-                                   LayerSize::new(rect.size.width, bottom.width))
+                    // Bottom right
+                    ImageBorderSegment::new(LayerRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
+                                            TexelRect::new(px2, py2, px3, py3),
+                                            RepeatMode::Stretch,
+                                            RepeatMode::Stretch),
+
+                    // Bottom left
+                    ImageBorderSegment::new(LayerRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
+                                            TexelRect::new(px0, py2, px1, py3),
+                                            RepeatMode::Stretch,
+                                            RepeatMode::Stretch),
                 ];
 
-                for rect in &rects {
-                    self.add_solid_rectangle(rect,
-                                             clip_region,
-                                             &top_color,
-                                             PrimitiveFlags::None);
+                // Add edge segments if valid size.
+                if px1 < px2 && py1 < py2 {
+                    segments.extend_from_slice(&[
+                        // Top
+                        ImageBorderSegment::new(LayerRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
+                                                TexelRect::new(px1, py0, px2, py1),
+                                                border.repeat_horizontal,
+                                                RepeatMode::Stretch),
+
+                        // Bottom
+                        ImageBorderSegment::new(LayerRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
+                                                TexelRect::new(px1, py2, px2, py3),
+                                                border.repeat_horizontal,
+                                                RepeatMode::Stretch),
+
+                        // Left
+                        ImageBorderSegment::new(LayerRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
+                                                TexelRect::new(px0, py1, px1, py2),
+                                                RepeatMode::Stretch,
+                                                border.repeat_vertical),
+
+                        // Right
+                        ImageBorderSegment::new(LayerRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
+                                                TexelRect::new(px2, py1, px3, py2),
+                                                RepeatMode::Stretch,
+                                                border.repeat_vertical),
+                    ]);
+                }
+
+                for segment in segments {
+                    self.add_image(segment.geom_rect,
+                                   clip_region,
+                                   &segment.stretch_size,
+                                   &segment.tile_spacing,
+                                   Some(segment.sub_rect),
+                                   border.image_key,
+                                   ImageRendering::Auto);
+                }
+            }
+            BorderDetails::Normal(ref border) => {
+                let radius = &border.radius;
+                let left = &border.left;
+                let right = &border.right;
+                let top = &border.top;
+                let bottom = &border.bottom;
+
+                if !self.supported_style(left) || !self.supported_style(right) ||
+                   !self.supported_style(top) || !self.supported_style(bottom) {
+                    println!("Unsupported border style, not rendering border");
+                    return;
                 }
 
-                return;
+                // These colors are used during inset/outset scaling.
+                let left_color      = left.border_color(1.0, 2.0/3.0, 0.3, 0.7);
+                let top_color       = top.border_color(1.0, 2.0/3.0, 0.3, 0.7);
+                let right_color     = right.border_color(2.0/3.0, 1.0, 0.7, 0.3);
+                let bottom_color    = bottom.border_color(2.0/3.0, 1.0, 0.7, 0.3);
+
+                let tl_outer = LayerPoint::new(rect.origin.x, rect.origin.y);
+                let tl_inner = tl_outer + LayerPoint::new(radius.top_left.width.max(border_item.widths.left),
+                                                          radius.top_left.height.max(border_item.widths.top));
+
+                let tr_outer = LayerPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
+                let tr_inner = tr_outer + LayerPoint::new(-radius.top_right.width.max(border_item.widths.right),
+                                                          radius.top_right.height.max(border_item.widths.top));
+
+                let bl_outer = LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
+                let bl_inner = bl_outer + LayerPoint::new(radius.bottom_left.width.max(border_item.widths.left),
+                                                          -radius.bottom_left.height.max(border_item.widths.bottom));
+
+                let br_outer = LayerPoint::new(rect.origin.x + rect.size.width,
+                                               rect.origin.y + rect.size.height);
+                let br_inner = br_outer - LayerPoint::new(radius.bottom_right.width.max(border_item.widths.right),
+                                                          radius.bottom_right.height.max(border_item.widths.bottom));
+
+                // The border shader is quite expensive. For simple borders, we can just draw
+                // the border with a few rectangles. This generally gives better batching, and
+                // a GPU win in fragment shader time.
+                // More importantly, the software (OSMesa) implementation we run tests on is
+                // particularly slow at running our complex border shader, compared to the
+                // rectangle shader. This has the effect of making some of our tests time
+                // out more often on CI (the actual cause is simply too many Servo processes and
+                // threads being run on CI at once).
+                // TODO(gw): Detect some more simple cases and handle those with simpler shaders too.
+                // TODO(gw): Consider whether it's only worth doing this for large rectangles (since
+                //           it takes a little more CPU time to handle multiple rectangles compared
+                //           to a single border primitive).
+                if left.style == BorderStyle::Solid {
+                    let same_color = left_color == top_color &&
+                                     left_color == right_color &&
+                                     left_color == bottom_color;
+                    let same_style = left.style == top.style &&
+                                     left.style == right.style &&
+                                     left.style == bottom.style;
+
+                    if same_color && same_style && radius.is_zero() {
+                        let rects = [
+                            LayerRect::new(rect.origin,
+                                           LayerSize::new(rect.size.width, border_item.widths.top)),
+                            LayerRect::new(LayerPoint::new(tl_outer.x, tl_inner.y),
+                                           LayerSize::new(border_item.widths.left,
+                                                          rect.size.height - border_item.widths.top - border_item.widths.bottom)),
+                            LayerRect::new(tr_inner,
+                                           LayerSize::new(border_item.widths.right,
+                                                          rect.size.height - border_item.widths.top - border_item.widths.bottom)),
+                            LayerRect::new(LayerPoint::new(bl_outer.x, bl_inner.y),
+                                           LayerSize::new(rect.size.width, border_item.widths.bottom))
+                        ];
+
+                        for rect in &rects {
+                            self.add_solid_rectangle(rect,
+                                                     clip_region,
+                                                     &top_color,
+                                                     PrimitiveFlags::None);
+                        }
+
+                        return;
+                    }
+                }
+
+                //Note: while similar to `ComplexClipRegion::get_inner_rect()` in spirit,
+                // this code is a bit more complex and can not there for be merged.
+                let inner_rect = rect_from_points_f(tl_inner.x.max(bl_inner.x),
+                                                    tl_inner.y.max(tr_inner.y),
+                                                    tr_inner.x.min(br_inner.x),
+                                                    bl_inner.y.min(br_inner.y));
+
+                let prim_cpu = BorderPrimitiveCpu {
+                    inner_rect: LayerRect::from_untyped(&inner_rect),
+                };
+
+                let prim_gpu = BorderPrimitiveGpu {
+                    colors: [ left_color, top_color, right_color, bottom_color ],
+                    widths: [ border_item.widths.left,
+                              border_item.widths.top,
+                              border_item.widths.right,
+                              border_item.widths.bottom ],
+                    style: [
+                        pack_as_float(left.style as u32),
+                        pack_as_float(top.style as u32),
+                        pack_as_float(right.style as u32),
+                        pack_as_float(bottom.style as u32),
+                    ],
+                    radii: [
+                        radius.top_left,
+                        radius.top_right,
+                        radius.bottom_right,
+                        radius.bottom_left,
+                    ],
+                };
+
+                self.add_primitive(&rect,
+                                   clip_region,
+                                   PrimitiveContainer::Border(prim_cpu, prim_gpu));
             }
         }
-
-        //Note: while similar to `ComplexClipRegion::get_inner_rect()` in spirit,
-        // this code is a bit more complex and can not there for be merged.
-        let inner_rect = rect_from_points_f(tl_inner.x.max(bl_inner.x),
-                                            tl_inner.y.max(tr_inner.y),
-                                            tr_inner.x.min(br_inner.x),
-                                            bl_inner.y.min(br_inner.y));
-
-        let prim_cpu = BorderPrimitiveCpu {
-            inner_rect: LayerRect::from_untyped(&inner_rect),
-        };
-
-        let prim_gpu = BorderPrimitiveGpu {
-            colors: [ left_color, top_color, right_color, bottom_color ],
-            widths: [ left.width, top.width, right.width, bottom.width ],
-            style: [
-                pack_as_float(left.style as u32),
-                pack_as_float(top.style as u32),
-                pack_as_float(right.style as u32),
-                pack_as_float(bottom.style as u32),
-            ],
-            radii: [
-                radius.top_left,
-                radius.top_right,
-                radius.bottom_right,
-                radius.bottom_left,
-            ],
-        };
-
-        self.add_primitive(&rect,
-                           clip_region,
-                           PrimitiveContainer::Border(prim_cpu, prim_gpu));
     }
 
     pub fn add_gradient(&mut self,
                         rect: LayerRect,
                         clip_region: &ClipRegion,
                         start_point: LayerPoint,
                         end_point: LayerPoint,
                         stops: ItemRange,
@@ -536,67 +707,118 @@ impl FrameBuilder {
         if blur_radius == 0.0 && spread_radius == 0.0 && clip_mode == BoxShadowClipMode::None {
             self.add_solid_rectangle(&box_bounds,
                                      clip_region,
                                      color,
                                      PrimitiveFlags::None);
             return;
         }
 
+        // The local space box shadow rect. It is the element rect
+        // translated by the box shadow offset and inflated by the
+        // box shadow spread.
         let bs_rect = box_bounds.translate(box_offset)
                                 .inflate(spread_radius, spread_radius);
 
+        // Get the outer rectangle, based on the blur radius.
         let outside_edge_size = 2.0 * blur_radius;
         let inside_edge_size = outside_edge_size.max(border_radius);
         let edge_size = outside_edge_size + inside_edge_size;
         let outer_rect = bs_rect.inflate(outside_edge_size, outside_edge_size);
-        let mut instance_rects = Vec::new();
-        let (prim_rect, inverted) = match clip_mode {
+
+        // Box shadows are often used for things like text underline and other
+        // simple primitives, so we want to draw these simple cases with the
+        // solid rectangle shader wherever possible, to avoid invoking the
+        // expensive box-shadow shader.
+        enum BoxShadowKind {
+            Simple(Vec<LayerRect>),     // Can be drawn via simple rectangles only
+            Shadow(Vec<LayerRect>),     // Requires the full box-shadow code path
+        }
+
+        let shadow_kind = match clip_mode {
             BoxShadowClipMode::Outset | BoxShadowClipMode::None => {
-                subtract_rect(&outer_rect, box_bounds, &mut instance_rects);
-                (outer_rect, 0.0)
+                // For outset shadows, subtracting the element rectangle
+                // from the outer rectangle gives the rectangles we need
+                // to draw. In the simple case (no blur radius), we can
+                // just draw these as solid colors.
+                let mut rects = Vec::new();
+                subtract_rect(&outer_rect, box_bounds, &mut rects);
+                if edge_size == 0.0 {
+                    BoxShadowKind::Simple(rects)
+                } else {
+                    BoxShadowKind::Shadow(rects)
+                }
             }
             BoxShadowClipMode::Inset => {
-                subtract_rect(box_bounds, &bs_rect, &mut instance_rects);
-                (*box_bounds, 1.0)
+                // For inset shadows, in the simple case (no blur) we
+                // can draw the shadow area by subtracting the box
+                // shadow rect from the element rect (since inset box
+                // shadows never extend past the element rect). However,
+                // in the case of an inset box shadow with blur, we
+                // currently just draw the box shadow over the entire
+                // rect. The opaque parts of the shadow (past the outside
+                // edge of the box-shadow) are handled by the shadow
+                // shader.
+                // TODO(gw): We should be able to optimize the complex
+                //           inset shadow case to touch fewer pixels. We
+                //           can probably calculate the inner rect that
+                //           can't be affected, and subtract that from
+                //           the element rect?
+                let mut rects = Vec::new();
+                if edge_size == 0.0 {
+                    subtract_rect(box_bounds, &bs_rect, &mut rects);
+                    BoxShadowKind::Simple(rects)
+                } else {
+                    rects.push(*box_bounds);
+                    BoxShadowKind::Shadow(rects)
+                }
             }
         };
 
-        if edge_size == 0.0 {
-            for rect in &instance_rects {
-                self.add_solid_rectangle(rect,
-                                         clip_region,
-                                         color,
-                                         PrimitiveFlags::None)
+        match shadow_kind {
+            BoxShadowKind::Simple(rects) => {
+                for rect in &rects {
+                    self.add_solid_rectangle(rect,
+                                             clip_region,
+                                             color,
+                                             PrimitiveFlags::None)
+                }
             }
-        } else {
-            let prim_gpu = BoxShadowPrimitiveGpu {
-                src_rect: *box_bounds,
-                bs_rect: bs_rect,
-                color: *color,
-                blur_radius: blur_radius,
-                border_radius: border_radius,
-                edge_size: edge_size,
-                inverted: inverted,
-            };
+            BoxShadowKind::Shadow(rects) => {
+                let inverted = match clip_mode {
+                    BoxShadowClipMode::Outset | BoxShadowClipMode::None => 0.0,
+                    BoxShadowClipMode::Inset => 1.0,
+                };
 
-            self.add_primitive(&prim_rect,
-                               clip_region,
-                               PrimitiveContainer::BoxShadow(prim_gpu, instance_rects));
+                let prim_gpu = BoxShadowPrimitiveGpu {
+                    src_rect: *box_bounds,
+                    bs_rect: bs_rect,
+                    color: *color,
+                    blur_radius: blur_radius,
+                    border_radius: border_radius,
+                    edge_size: edge_size,
+                    inverted: inverted,
+                };
+
+                self.add_primitive(&outer_rect,
+                                   clip_region,
+                                   PrimitiveContainer::BoxShadow(prim_gpu, rects));
+            }
         }
     }
 
     pub fn add_webgl_rectangle(&mut self,
                                rect: LayerRect,
                                clip_region: &ClipRegion,
                                context_id: WebGLContextId) {
         let prim_cpu = ImagePrimitiveCpu {
             kind: ImagePrimitiveKind::WebGL(context_id),
             color_texture_id: SourceTexture::Invalid,
             resource_address: GpuStoreAddress(0),
+            sub_rect: None,
         };
 
         let prim_gpu = ImagePrimitiveGpu {
             stretch_size: rect.size,
             tile_spacing: LayerSize::zero(),
         };
 
         self.add_primitive(&rect,
@@ -604,24 +826,26 @@ impl FrameBuilder {
                            PrimitiveContainer::Image(prim_cpu, prim_gpu));
     }
 
     pub fn add_image(&mut self,
                      rect: LayerRect,
                      clip_region: &ClipRegion,
                      stretch_size: &LayerSize,
                      tile_spacing: &LayerSize,
+                     sub_rect: Option<TexelRect>,
                      image_key: ImageKey,
                      image_rendering: ImageRendering) {
         let prim_cpu = ImagePrimitiveCpu {
             kind: ImagePrimitiveKind::Image(image_key,
                                             image_rendering,
                                             *tile_spacing),
             color_texture_id: SourceTexture::Invalid,
             resource_address: GpuStoreAddress(0),
+            sub_rect: sub_rect,
         };
 
         let prim_gpu = ImagePrimitiveGpu {
             stretch_size: *stretch_size,
             tile_spacing: *tile_spacing,
         };
 
         self.add_primitive(&rect,
@@ -652,60 +876,60 @@ impl FrameBuilder {
                            clip_region,
                            PrimitiveContainer::YuvImage(prim_cpu, prim_gpu));
     }
 
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(&mut self,
                                                 screen_rect: &DeviceIntRect,
-                                                scroll_tree: &ScrollTree,
+                                                clip_scroll_tree: &ClipScrollTree,
                                                 auxiliary_lists_map: &AuxiliaryListsMap,
                                                 resource_cache: &mut ResourceCache,
                                                 profile_counters: &mut FrameProfileCounters,
                                                 device_pixel_ratio: f32) {
         profile_scope!("cull");
         LayerRectCalculationAndCullingPass::create_and_run(self,
                                                            screen_rect,
-                                                           scroll_tree,
+                                                           clip_scroll_tree,
                                                            auxiliary_lists_map,
                                                            resource_cache,
                                                            profile_counters,
                                                            device_pixel_ratio);
     }
 
-    fn update_scroll_bars(&mut self, scroll_tree: &ScrollTree) {
+    fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree) {
         let distance_from_edge = 8.0;
 
         for scrollbar_prim in &self.scrollbar_prims {
             let mut geom = (*self.prim_store.gpu_geometry.get(GpuStoreAddress(scrollbar_prim.prim_index.0 as i32))).clone();
-            let scroll_layer = &scroll_tree.layers[&scrollbar_prim.scroll_layer_id];
+            let clip_scroll_node = &clip_scroll_tree.nodes[&scrollbar_prim.scroll_layer_id];
 
-            let scrollable_distance = scroll_layer.scrollable_height();
+            let scrollable_distance = clip_scroll_node.scrollable_height();
 
             if scrollable_distance <= 0.0 {
                 geom.local_clip_rect.size = LayerSize::zero();
                 *self.prim_store.gpu_geometry.get_mut(GpuStoreAddress(scrollbar_prim.prim_index.0 as i32)) = geom;
                 continue;
             }
 
-            let f = -scroll_layer.scrolling.offset.y / scrollable_distance;
+            let f = -clip_scroll_node.scrolling.offset.y / scrollable_distance;
 
-            let min_y = scroll_layer.local_viewport_rect.origin.y -
-                        scroll_layer.scrolling.offset.y +
+            let min_y = clip_scroll_node.local_viewport_rect.origin.y -
+                        clip_scroll_node.scrolling.offset.y +
                         distance_from_edge;
 
-            let max_y = scroll_layer.local_viewport_rect.origin.y +
-                        scroll_layer.local_viewport_rect.size.height -
-                        scroll_layer.scrolling.offset.y -
+            let max_y = clip_scroll_node.local_viewport_rect.origin.y +
+                        clip_scroll_node.local_viewport_rect.size.height -
+                        clip_scroll_node.scrolling.offset.y -
                         geom.local_rect.size.height -
                         distance_from_edge;
 
-            geom.local_rect.origin.x = scroll_layer.local_viewport_rect.origin.x +
-                                       scroll_layer.local_viewport_rect.size.width -
+            geom.local_rect.origin.x = clip_scroll_node.local_viewport_rect.origin.x +
+                                       clip_scroll_node.local_viewport_rect.size.width -
                                        geom.local_rect.size.width -
                                        distance_from_edge;
 
             geom.local_rect.origin.y = util::lerp(min_y, max_y, f);
             geom.local_clip_rect = geom.local_rect;
 
             let clip_source = if scrollbar_prim.border_radius == 0.0 {
                 ClipSource::NoClip
@@ -731,38 +955,37 @@ impl FrameBuilder {
         let mut alpha_task_stack = Vec::new();
 
         for cmd in &self.cmds {
             match *cmd {
                 PrimitiveRunCmd::PushStackingContext(stacking_context_index) => {
                     let stacking_context = &self.stacking_context_store[stacking_context_index.0];
                     sc_stack.push(stacking_context_index);
 
-                    if !stacking_context.is_visible() {
+                    if !stacking_context.is_visible {
                         continue;
                     }
 
+                    let stacking_context_rect = &stacking_context.bounding_rect;
                     let composite_count = stacking_context.composite_ops.count();
                     for _ in 0..composite_count {
-                        let stacking_context_rect =
-                            stacking_context.xf_rect.as_ref().unwrap().bounding_rect;
                         let location = RenderTaskLocation::Dynamic(None, stacking_context_rect.size);
                         let new_task = RenderTask::new_alpha_batch(next_task_index,
                                                                    stacking_context_rect.origin,
                                                                    location);
                         next_task_index.0 += 1;
                         let prev_task = mem::replace(&mut current_task, new_task);
                         alpha_task_stack.push(prev_task);
                     }
                 }
                 PrimitiveRunCmd::PopStackingContext => {
                     let stacking_context_index = sc_stack.pop().unwrap();
                     let stacking_context = &self.stacking_context_store[stacking_context_index.0];
 
-                    if !stacking_context.is_visible() {
+                    if !stacking_context.is_visible {
                         continue;
                     }
 
                     for filter in &stacking_context.composite_ops.filters {
                         let mut prev_task = alpha_task_stack.pop().unwrap();
                         let item = AlphaRenderItem::Blend(stacking_context_index,
                                                           current_task.id,
                                                           *filter,
@@ -781,20 +1004,19 @@ impl FrameBuilder {
                                                                               op,
                                                                               next_z);
                                 next_z += 1;
                                 prev_task.as_alpha_batch().alpha_items.push(item);
                                 prev_task.children.push(current_task);
                                 current_task = prev_task;
                             }
                             None => {
-                                let stacking_context_rect =
-                                    stacking_context.xf_rect.as_ref().unwrap().bounding_rect;
                                 let readback_task =
-                                    RenderTask::new_readback(stacking_context_index, stacking_context_rect);
+                                    RenderTask::new_readback(stacking_context_index,
+                                                             stacking_context.bounding_rect);
 
                                 let mut prev_task = alpha_task_stack.pop().unwrap();
                                 let item = AlphaRenderItem::Composite(stacking_context_index,
                                                                       readback_task.id,
                                                                       current_task.id,
                                                                       mix_blend_mode,
                                                                       next_z);
                                 next_z += 1;
@@ -805,64 +1027,67 @@ impl FrameBuilder {
                             }
                         }
                     }
                 }
                 PrimitiveRunCmd::PrimitiveRun(first_prim_index, prim_count) => {
                     let stacking_context_index = *sc_stack.last().unwrap();
                     let stacking_context = &self.stacking_context_store[stacking_context_index.0];
 
-                    if !stacking_context.is_visible() {
+                    if !stacking_context.is_visible {
                         continue;
                     }
 
+                    let stacking_context_index = *sc_stack.last().unwrap();
+                    let group_index =
+                        self.stacking_context_store[stacking_context_index.0].clip_scroll_group();
+                    let clip_scroll_group = &self.clip_scroll_group_store[group_index.0];
+
                     for i in 0..prim_count {
                         let prim_index = PrimitiveIndex(first_prim_index.0 + i);
 
                         if self.prim_store.cpu_bounding_rects[prim_index.0].is_some() {
                             let prim_metadata = self.prim_store.get_metadata(prim_index);
 
                             // Add any dynamic render tasks needed to render this primitive
                             if let Some(ref render_task) = prim_metadata.render_task {
                                 current_task.children.push(render_task.clone());
                             }
                             if let Some(ref clip_task) = prim_metadata.clip_task {
                                 current_task.children.push(clip_task.clone());
                             }
 
-                            let transform_kind = stacking_context.xf_rect.as_ref().unwrap().kind;
+                            let transform_kind = clip_scroll_group.xf_rect.as_ref().unwrap().kind;
                             let needs_clipping = prim_metadata.clip_task.is_some();
                             let needs_blending = transform_kind == TransformedRectKind::Complex ||
                                                  !prim_metadata.is_opaque ||
                                                  needs_clipping;
 
                             let items = if needs_blending {
                                 &mut current_task.as_alpha_batch().alpha_items
                             } else {
                                 &mut current_task.as_alpha_batch().opaque_items
                             };
-                            items.push(AlphaRenderItem::Primitive(stacking_context_index,
-                                                                  prim_index,
-                                                                  next_z));
+                            items.push(AlphaRenderItem::Primitive(group_index, prim_index, next_z));
                             next_z += 1;
                         }
                     }
                 }
                 PrimitiveRunCmd::PushScrollLayer(_) | PrimitiveRunCmd::PopScrollLayer => { }
             }
         }
 
         debug_assert!(alpha_task_stack.is_empty());
         (current_task, next_task_index.0)
     }
 
     pub fn build(&mut self,
                  resource_cache: &mut ResourceCache,
                  frame_id: FrameId,
-                 scroll_tree: &ScrollTree,
+                 clip_scroll_tree: &ClipScrollTree,
                  auxiliary_lists_map: &AuxiliaryListsMap,
                  device_pixel_ratio: f32) -> Frame {
         profile_scope!("build");
 
         let mut profile_counters = FrameProfileCounters::new();
         profile_counters.total_primitives.set(self.prim_store.prim_count());
 
         resource_cache.begin_frame(frame_id);
@@ -876,20 +1101,20 @@ impl FrameBuilder {
 
         // Pick a size for the cache render targets to be. The main requirement is that it
         // has to be at least as large as the framebuffer size. This ensures that it will
         // always be able to allocate the worst case render task (such as a clip mask that
         // covers the entire screen).
         let cache_size = DeviceUintSize::new(cmp::max(1024, screen_rect.size.width as u32),
                                              cmp::max(1024, screen_rect.size.height as u32));
 
-        self.update_scroll_bars(scroll_tree);
+        self.update_scroll_bars(clip_scroll_tree);
 
         self.build_layer_screen_rects_and_cull_layers(&screen_rect,
-                                                      scroll_tree,
+                                                      clip_scroll_tree,
                                                       auxiliary_lists_map,
                                                       resource_cache,
                                                       &mut profile_counters,
                                                       device_pixel_ratio);
 
         let (main_render_task, static_render_task_count) = self.build_render_task();
         let mut render_tasks = RenderTaskCollection::new(static_render_task_count);
 
@@ -917,16 +1142,17 @@ impl FrameBuilder {
                                         cache_size));
         }
 
         main_render_task.assign_to_passes(passes.len() - 1, &mut passes);
 
         for pass in &mut passes {
             let ctx = RenderTargetContext {
                 stacking_context_store: &self.stacking_context_store,
+                clip_scroll_group_store: &self.clip_scroll_group_store,
                 prim_store: &self.prim_store,
                 resource_cache: resource_cache,
             };
 
             pass.build(&ctx, &mut render_tasks);
 
             profile_counters.passes.inc();
             profile_counters.targets.add(pass.targets.len());
@@ -948,22 +1174,23 @@ impl FrameBuilder {
             gpu_data64: self.prim_store.gpu_data64.build(),
             gpu_data128: self.prim_store.gpu_data128.build(),
             gpu_geometry: self.prim_store.gpu_geometry.build(),
             gpu_gradient_data: self.prim_store.gpu_gradient_data.build(),
             gpu_resource_rects: self.prim_store.gpu_resource_rects.build(),
             deferred_resolves: deferred_resolves,
         }
     }
+
 }
 
 struct LayerRectCalculationAndCullingPass<'a> {
     frame_builder: &'a mut FrameBuilder,
     screen_rect: &'a DeviceIntRect,
-    scroll_tree: &'a ScrollTree,
+    clip_scroll_tree: &'a ClipScrollTree,
     auxiliary_lists_map: &'a AuxiliaryListsMap,
     resource_cache: &'a mut ResourceCache,
     profile_counters: &'a mut FrameProfileCounters,
     device_pixel_ratio: f32,
     stacking_context_stack: Vec<StackingContextIndex>,
     scroll_layer_stack: Vec<ScrollLayerIndex>,
 
     /// A cached clip info stack, which should handle the most common situation,
@@ -973,64 +1200,137 @@ struct LayerRectCalculationAndCullingPas
 
     /// The scroll layer that defines the previous scroll layer info stack.
     current_clip_stack_scroll_layer: Option<ScrollLayerIndex>
 }
 
 impl<'a> LayerRectCalculationAndCullingPass<'a> {
     fn create_and_run(frame_builder: &'a mut FrameBuilder,
                       screen_rect: &'a DeviceIntRect,
-                      scroll_tree: &'a ScrollTree,
+                      clip_scroll_tree: &'a ClipScrollTree,
                       auxiliary_lists_map: &'a AuxiliaryListsMap,
                       resource_cache: &'a mut ResourceCache,
                       profile_counters: &'a mut FrameProfileCounters,
                       device_pixel_ratio: f32) {
 
         let mut pass = LayerRectCalculationAndCullingPass {
             frame_builder: frame_builder,
             screen_rect: screen_rect,
-            scroll_tree: scroll_tree,
+            clip_scroll_tree: clip_scroll_tree,
             auxiliary_lists_map: auxiliary_lists_map,
             resource_cache: resource_cache,
             profile_counters: profile_counters,
             device_pixel_ratio: device_pixel_ratio,
             stacking_context_stack: Vec::new(),
             scroll_layer_stack: Vec::new(),
             current_clip_stack: Vec::new(),
             current_clip_stack_scroll_layer: None,
         };
         pass.run();
     }
 
     fn run(&mut self) {
+        self.recalculate_clip_scroll_groups();
+        self.compute_stacking_context_visibility();
+
         let commands = mem::replace(&mut self.frame_builder.cmds, Vec::new());
         for cmd in &commands {
             match cmd {
                 &PrimitiveRunCmd::PushStackingContext(stacking_context_index) =>
                     self.handle_push_stacking_context(stacking_context_index),
                 &PrimitiveRunCmd::PushScrollLayer(scroll_layer_index) =>
                     self.handle_push_scroll_layer(scroll_layer_index),
                 &PrimitiveRunCmd::PrimitiveRun(prim_index, prim_count) =>
                     self.handle_primitive_run(prim_index, prim_count),
-                &PrimitiveRunCmd::PopStackingContext => {
-                    self.stacking_context_stack.pop();
-                }
+                &PrimitiveRunCmd::PopStackingContext => self.handle_pop_stacking_context(),
                 &PrimitiveRunCmd::PopScrollLayer => self.handle_pop_scroll_layer(),
             }
         }
 
         mem::replace(&mut self.frame_builder.cmds, commands);
     }
 
+    fn recalculate_clip_scroll_groups(&mut self) {
+        for ref mut group in &mut self.frame_builder.clip_scroll_group_store {
+            let stacking_context_index = group.stacking_context_index;
+            let stacking_context = &mut self.frame_builder
+                                            .stacking_context_store[stacking_context_index.0];
+
+            let scroll_tree_layer = &self.clip_scroll_tree.nodes[&group.scroll_layer_id];
+            let packed_layer = &mut self.frame_builder.packed_layers[group.packed_layer_index.0];
+            packed_layer.transform = scroll_tree_layer.world_content_transform
+                                                      .with_source::<ScrollLayerPixel>()
+                                                      .pre_mul(&stacking_context.local_transform);
+            packed_layer.inv_transform = packed_layer.transform.inverse().unwrap();
+
+            if !stacking_context.can_contribute_to_scene() {
+                return;
+            }
+
+            let inv_layer_transform = stacking_context.local_transform.inverse().unwrap();
+            let local_viewport_rect =
+                as_scroll_parent_rect(&scroll_tree_layer.combined_local_viewport_rect);
+            let viewport_rect = inv_layer_transform.transform_rect(&local_viewport_rect);
+            let layer_local_rect = stacking_context.local_rect.intersection(&viewport_rect);
+
+            group.xf_rect = None;
+
+            let layer_local_rect = match layer_local_rect {
+                Some(layer_local_rect) if !layer_local_rect.is_empty() => layer_local_rect,
+                _ => continue,
+            };
+
+            let layer_xf_rect = TransformedRect::new(&layer_local_rect,
+                                                     &packed_layer.transform,
+                                                     self.device_pixel_ratio);
+
+            if layer_xf_rect.bounding_rect.intersects(&self.screen_rect) {
+                packed_layer.screen_vertices = layer_xf_rect.vertices.clone();
+                packed_layer.local_clip_rect = layer_local_rect;
+                group.xf_rect = Some(layer_xf_rect);
+            }
+        }
+    }
+
+    fn compute_stacking_context_visibility(&mut self) {
+        for context_index in 0..self.frame_builder.stacking_context_store.len() {
+            let is_visible = {
+                let stacking_context = &self.frame_builder.stacking_context_store[context_index];
+                stacking_context.clip_scroll_groups.iter().any(|group_index| {
+                    self.frame_builder.clip_scroll_group_store[group_index.0].is_visible()
+                })
+            };
+            self.frame_builder.stacking_context_store[context_index].is_visible = is_visible;
+        }
+    }
+
+    fn handle_pop_stacking_context(&mut self) {
+        let stacking_context_index = self.stacking_context_stack.pop().unwrap();
+
+        let bounding_rect = {
+            let stacking_context =
+                &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
+            stacking_context.bounding_rect = stacking_context.bounding_rect
+                                                             .intersection(self.screen_rect)
+                                                             .unwrap_or(DeviceIntRect::zero());
+            stacking_context.bounding_rect.clone()
+        };
+
+        if let Some(ref mut parent_index) = self.stacking_context_stack.last_mut() {
+            let parent = &mut self.frame_builder.stacking_context_store[parent_index.0];
+            parent.bounding_rect = parent.bounding_rect.union(&bounding_rect);
+        }
+    }
+
     fn handle_push_scroll_layer(&mut self, scroll_layer_index: ScrollLayerIndex) {
         self.scroll_layer_stack.push(scroll_layer_index);
 
         let scroll_layer = &mut self.frame_builder.scroll_layer_store[scroll_layer_index.0];
         let packed_layer_index = scroll_layer.packed_layer_index;
-        let scroll_tree_layer = &self.scroll_tree.layers[&scroll_layer.scroll_layer_id];
+        let scroll_tree_layer = &self.clip_scroll_tree.nodes[&scroll_layer.scroll_layer_id];
         let packed_layer = &mut self.frame_builder.packed_layers[packed_layer_index.0];
 
         packed_layer.transform = scroll_tree_layer.world_viewport_transform;
         packed_layer.inv_transform = packed_layer.transform.inverse().unwrap();
 
         let local_rect = &scroll_tree_layer.combined_local_viewport_rect
                                            .translate(&scroll_tree_layer.scrolling.offset);
         if !local_rect.is_empty() {
@@ -1063,46 +1363,24 @@ impl<'a> LayerRectCalculationAndCullingP
             // We don't add the image mask for resolution, because layer masks are resolved later.
             self.resource_cache.request_image(mask.image, ImageRendering::Auto);
         }
     }
 
     fn handle_push_stacking_context(&mut self, stacking_context_index: StackingContextIndex) {
         self.stacking_context_stack.push(stacking_context_index);
 
+        // Reset bounding rect to zero. We will calculate it as we collect primitives
+        // from various scroll layers. In handle_pop_stacking_context , we use this to
+        // calculate the device bounding rect. In the future, we could cache this during
+        // the initial adding of items for the common case (where there is only a single
+        // scroll layer for items in a stacking context).
         let stacking_context = &mut self.frame_builder
                                         .stacking_context_store[stacking_context_index.0];
-        let packed_layer = &mut self.frame_builder
-                                    .packed_layers[stacking_context.packed_layer_index.0];
-        let scroll_layer = &self.scroll_tree.layers[&stacking_context.scroll_layer_id];
-        packed_layer.transform = scroll_layer.world_content_transform
-                                             .with_source::<ScrollLayerPixel>()
-                                             .pre_mul(&stacking_context.local_transform);
-        packed_layer.inv_transform = packed_layer.transform.inverse().unwrap();
-
-        if !stacking_context.can_contribute_to_scene() {
-            return;
-        }
-
-        let inv_layer_transform = stacking_context.local_transform.inverse().unwrap();
-        let local_viewport_rect = as_scroll_parent_rect(&scroll_layer.combined_local_viewport_rect);
-        let viewport_rect = inv_layer_transform.transform_rect(&local_viewport_rect);
-        let layer_local_rect = stacking_context.local_rect.intersection(&viewport_rect);
-
-        if let Some(layer_local_rect) = layer_local_rect {
-            let layer_xf_rect = TransformedRect::new(&layer_local_rect,
-                                                     &packed_layer.transform,
-                                                     self.device_pixel_ratio);
-
-            if layer_xf_rect.bounding_rect.intersects(&self.screen_rect) {
-                packed_layer.screen_vertices = layer_xf_rect.vertices.clone();
-                packed_layer.local_clip_rect = layer_local_rect;
-                stacking_context.xf_rect = Some(layer_xf_rect);
-            }
-        }
+        stacking_context.bounding_rect = DeviceIntRect::zero();
     }
 
     fn rebuild_clip_info_stack_if_necessary(&mut self, mut scroll_layer_index: ScrollLayerIndex) {
         if let Some(previous_scroll_layer) = self.current_clip_stack_scroll_layer {
             if previous_scroll_layer == scroll_layer_index {
                 return;
             }
         }
@@ -1125,28 +1403,38 @@ impl<'a> LayerRectCalculationAndCullingP
             }
             scroll_layer_index = scroll_layer.parent_index;
         }
 
         self.current_clip_stack.reverse();
     }
 
     fn handle_primitive_run(&mut self, prim_index: PrimitiveIndex, prim_count: usize) {
+        let stacking_context_index = *self.stacking_context_stack.last().unwrap();
+        let (packed_layer_index, pipeline_id) = {
+            let stacking_context =
+                &self.frame_builder.stacking_context_store[stacking_context_index.0];
+
+            if !stacking_context.is_visible {
+                return;
+            }
+
+            let group_index = stacking_context.clip_scroll_group();
+            let clip_scroll_group = &self.frame_builder.clip_scroll_group_store[group_index.0];
+            (clip_scroll_group.packed_layer_index, stacking_context.pipeline_id)
+        };
+
         let scroll_layer_index = *self.scroll_layer_stack.last().unwrap();
         self.rebuild_clip_info_stack_if_necessary(scroll_layer_index);
 
-        let stacking_context_index = self.stacking_context_stack.last().unwrap();
-        let stacking_context = &self.frame_builder.stacking_context_store[stacking_context_index.0];
-        if !stacking_context.is_visible() {
-            return;
-        }
+        let stacking_context =
+            &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
 
-        let packed_layer_index = stacking_context.packed_layer_index;
         let packed_layer = &self.frame_builder.packed_layers[packed_layer_index.0];
-        let auxiliary_lists = self.auxiliary_lists_map.get(&stacking_context.pipeline_id)
+        let auxiliary_lists = self.auxiliary_lists_map.get(&pipeline_id)
                                                       .expect("No auxiliary lists?");
 
         for i in 0..prim_count {
             let prim_index = PrimitiveIndex(prim_index.0 + i);
             if self.frame_builder.prim_store.build_bounding_rect(prim_index,
                                                                  self.screen_rect,
                                                                  &packed_layer.transform,
                                                                  &packed_layer.local_clip_rect,
@@ -1170,16 +1458,19 @@ impl<'a> LayerRectCalculationAndCullingP
                     Some(rect) => rect,
                     _ => continue,
                 };
 
                 let prim_metadata = &mut self.frame_builder.prim_store.cpu_metadata[prim_index.0];
                 let prim_clip_info = prim_metadata.clip_cache_info.as_ref();
                 let mut visible = true;
 
+                stacking_context.bounding_rect =
+                    stacking_context.bounding_rect.union(&prim_bounding_rect);
+
                 if let Some(info) = prim_clip_info {
                     self.current_clip_stack.push((packed_layer_index, info.clone()));
                 }
 
                 // Try to create a mask if we may need to.
                 if !self.current_clip_stack.is_empty() {
                     // If the primitive doesn't have a specific clip, key the task ID off the
                     // stacking context. This means that two primitives which are only clipped
deleted file mode 100644
--- a/gfx/webrender/src/layer.rs
+++ /dev/null
@@ -1,288 +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 euclid::Point3D;
-use geometry::ray_intersects_rect;
-use spring::{DAMPING, STIFFNESS, Spring};
-use webrender_traits::{LayerPoint, LayerRect, LayerSize, LayerToScrollTransform};
-use webrender_traits::{LayerToWorldTransform, PipelineId, ScrollEventPhase, ScrollLayerId};
-use webrender_traits::{ScrollLayerRect, ScrollLocation, ScrollToWorldTransform, WorldPoint};
-use webrender_traits::{WorldPoint4D};
-
-#[cfg(target_os = "macos")]
-const CAN_OVERSCROLL: bool = true;
-
-#[cfg(not(target_os = "macos"))]
-const CAN_OVERSCROLL: bool = false;
-
-
-/// Contains scrolling and transform information stacking contexts.
-#[derive(Clone)]
-pub struct Layer {
-    /// Manages scrolling offset, overscroll state etc.
-    pub scrolling: ScrollingState,
-
-    /// Size of the content inside the scroll region (in logical pixels)
-    pub content_size: LayerSize,
-
-    /// Viewing rectangle
-    pub local_viewport_rect: LayerRect,
-
-    /// Viewing rectangle clipped against parent layer(s)
-    pub combined_local_viewport_rect: LayerRect,
-
-    /// World transform for the viewport rect itself.
-    pub world_viewport_transform: LayerToWorldTransform,
-
-    /// World transform for content within this layer
-    pub world_content_transform: LayerToWorldTransform,
-
-    /// Transform for this layer, relative to parent scrollable layer.
-    pub local_transform: LayerToScrollTransform,
-
-    /// Pipeline that this layer belongs to
-    pub pipeline_id: PipelineId,
-
-    /// Child layers
-    pub children: Vec<ScrollLayerId>,
-}
-
-impl Layer {
-    pub fn new(local_viewport_rect: &LayerRect,
-               content_size: LayerSize,
-               local_transform: &LayerToScrollTransform,
-               pipeline_id: PipelineId)
-               -> Layer {
-        Layer {
-            scrolling: ScrollingState::new(),
-            content_size: content_size,
-            local_viewport_rect: *local_viewport_rect,
-            combined_local_viewport_rect: *local_viewport_rect,
-            world_viewport_transform: LayerToWorldTransform::identity(),
-            world_content_transform: LayerToWorldTransform::identity(),
-            local_transform: *local_transform,
-            children: Vec::new(),
-            pipeline_id: pipeline_id,
-        }
-    }
-
-    pub fn add_child(&mut self, child: ScrollLayerId) {
-        self.children.push(child);
-    }
-
-    pub fn finalize(&mut self, scrolling: &ScrollingState) {
-        self.scrolling = *scrolling;
-    }
-
-    pub fn overscroll_amount(&self) -> LayerSize {
-        let scrollable_width = self.scrollable_width();
-        let overscroll_x = if self.scrolling.offset.x > 0.0 {
-            -self.scrolling.offset.x
-        } else if self.scrolling.offset.x < -scrollable_width {
-            -scrollable_width - self.scrolling.offset.x
-        } else {
-            0.0
-        };
-
-        let scrollable_height = self.scrollable_height();
-        let overscroll_y = if self.scrolling.offset.y > 0.0 {
-            -self.scrolling.offset.y
-        } else if self.scrolling.offset.y < -scrollable_height {
-            -scrollable_height - self.scrolling.offset.y
-        } else {
-            0.0
-        };
-
-        LayerSize::new(overscroll_x, overscroll_y)
-    }
-
-    pub fn set_scroll_origin(&mut self, origin: &LayerPoint) -> bool {
-        let scrollable_height = self.scrollable_height();
-        let scrollable_width = self.scrollable_width();
-        if scrollable_height <= 0. && scrollable_width <= 0. {
-            return false;
-        }
-
-        let new_offset = LayerPoint::new((-origin.x).max(-scrollable_width).min(0.0).round(),
-                                         (-origin.y).max(-scrollable_height).min(0.0).round());
-        if new_offset == self.scrolling.offset {
-            return false;
-        }
-
-        self.scrolling.offset = new_offset;
-        self.scrolling.bouncing_back = false;
-        self.scrolling.started_bouncing_back = false;
-        return true;
-    }
-
-    pub fn update_transform(&mut self,
-                            parent_world_transform: &ScrollToWorldTransform,
-                            parent_viewport_rect: &ScrollLayerRect) {
-        let inv_transform = match self.local_transform.inverse() {
-            Some(transform) => transform,
-            None => {
-                // If a transform function causes the current transformation matrix of an object
-                // to be non-invertible, the object and its content do not get displayed.
-                self.combined_local_viewport_rect = LayerRect::zero();
-                return;
-            }
-        };
-
-        let parent_viewport_rect_in_local_space = inv_transform.transform_rect(parent_viewport_rect)
-                                                               .translate(&-self.scrolling.offset);
-        let local_viewport_rect = self.local_viewport_rect.translate(&-self.scrolling.offset);
-        let viewport_rect = parent_viewport_rect_in_local_space.intersection(&local_viewport_rect)
-                                                               .unwrap_or(LayerRect::zero());
-
-        self.combined_local_viewport_rect = viewport_rect;
-        self.world_viewport_transform = parent_world_transform.pre_mul(&self.local_transform);
-        self.world_content_transform = self.world_viewport_transform
-                                                     .pre_translated(self.scrolling.offset.x,
-                                                                     self.scrolling.offset.y,
-                                                                     0.0);
-    }
-
-    pub fn scrollable_height(&self) -> f32 {
-        self.content_size.height - self.local_viewport_rect.size.height
-    }
-
-    pub fn scrollable_width(&self) -> f32 {
-        self.content_size.width - self.local_viewport_rect.size.width
-    }
-
-    pub fn scroll(&mut self, scroll_location: ScrollLocation, phase: ScrollEventPhase) -> bool {
-        if self.scrolling.started_bouncing_back && phase == ScrollEventPhase::Move(false) {
-            return false;
-        }
-
-        let mut delta = match scroll_location {
-            ScrollLocation::Delta(delta) => delta,
-            ScrollLocation::Start => {
-                if self.scrolling.offset.y.round() >= 0.0 {
-                    // Nothing to do on this layer.
-                    return false;
-                }
-
-                self.scrolling.offset.y = 0.0;
-                return true;
-            },
-            ScrollLocation::End => {
-                let end_pos = self.local_viewport_rect.size.height - self.content_size.height;
-
-                if self.scrolling.offset.y.round() <= end_pos {
-                    // Nothing to do on this layer.
-                    return false;
-                }
-
-                self.scrolling.offset.y = end_pos;
-                return true;
-            }
-        };
-
-        let overscroll_amount = self.overscroll_amount();
-        let overscrolling = CAN_OVERSCROLL && (overscroll_amount.width != 0.0 ||
-                                               overscroll_amount.height != 0.0);
-        if overscrolling {
-            if overscroll_amount.width != 0.0 {
-                delta.x /= overscroll_amount.width.abs()
-            }
-            if overscroll_amount.height != 0.0 {
-                delta.y /= overscroll_amount.height.abs()
-            }
-        }
-
-        let scrollable_width = self.scrollable_width();
-        let scrollable_height = self.scrollable_height();
-        let is_unscrollable = scrollable_width <= 0. && scrollable_height <= 0.;
-        let original_layer_scroll_offset = self.scrolling.offset;
-
-        if scrollable_width > 0. {
-            self.scrolling.offset.x = self.scrolling.offset.x + delta.x;
-            if is_unscrollable || !CAN_OVERSCROLL {
-                self.scrolling.offset.x =
-                    self.scrolling.offset.x.min(0.0).max(-scrollable_width).round();
-            }
-        }
-
-        if scrollable_height > 0. {
-            self.scrolling.offset.y = self.scrolling.offset.y + delta.y;
-            if is_unscrollable || !CAN_OVERSCROLL {
-                self.scrolling.offset.y =
-                    self.scrolling.offset.y.min(0.0).max(-scrollable_height).round();
-            }
-        }
-
-        if phase == ScrollEventPhase::Start || phase == ScrollEventPhase::Move(true) {
-            self.scrolling.started_bouncing_back = false
-        } else if overscrolling &&
-                ((delta.x < 1.0 && delta.y < 1.0) || phase == ScrollEventPhase::End) {
-            self.scrolling.started_bouncing_back = true;
-            self.scrolling.bouncing_back = true
-        }
-
-        if CAN_OVERSCROLL {
-            self.stretch_overscroll_spring();
-        }
-
-        self.scrolling.offset != original_layer_scroll_offset ||
-            self.scrolling.started_bouncing_back
-    }
-
-    pub fn stretch_overscroll_spring(&mut self) {
-        let overscroll_amount = self.overscroll_amount();
-        self.scrolling.spring.coords(self.scrolling.offset,
-                                     self.scrolling.offset,
-                                     self.scrolling.offset + overscroll_amount);
-    }
-
-    pub fn tick_scrolling_bounce_animation(&mut self) {
-        let finished = self.scrolling.spring.animate();
-        self.scrolling.offset = self.scrolling.spring.current();
-        if finished {
-            self.scrolling.bouncing_back = false
-        }
-    }
-
-    pub fn ray_intersects_layer(&self, cursor: &WorldPoint) -> bool {
-        let inv = self.world_viewport_transform.inverse().unwrap();
-        let z0 = -10000.0;
-        let z1 =  10000.0;
-
-        let p0 = inv.transform_point4d(&WorldPoint4D::new(cursor.x, cursor.y, z0, 1.0));
-        let p0 = Point3D::new(p0.x / p0.w,
-                              p0.y / p0.w,
-                              p0.z / p0.w);
-        let p1 = inv.transform_point4d(&WorldPoint4D::new(cursor.x, cursor.y, z1, 1.0));
-        let p1 = Point3D::new(p1.x / p1.w,
-                              p1.y / p1.w,
-                              p1.z / p1.w);
-
-        if self.scrollable_width() <= 0. && self.scrollable_height() <= 0. {
-            return false;
-        }
-        ray_intersects_rect(p0, p1, self.local_viewport_rect.to_untyped())
-    }
-}
-
-#[derive(Copy, Clone)]
-pub struct ScrollingState {
-    pub offset: LayerPoint,
-    pub spring: Spring,
-    pub started_bouncing_back: bool,
-    pub bouncing_back: bool,
-    pub should_handoff_scroll: bool
-}
-
-impl ScrollingState {
-    pub fn new() -> ScrollingState {
-        ScrollingState {
-            offset: LayerPoint::zero(),
-            spring: Spring::at(LayerPoint::zero(), STIFFNESS, DAMPING),
-            started_bouncing_back: false,
-            bouncing_back: false,
-            should_handoff_scroll: false
-        }
-    }
-}
-
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -47,36 +47,36 @@ extern crate lazy_static;
 #[macro_use]
 extern crate log;
 #[macro_use]
 extern crate bitflags;
 #[macro_use]
 extern crate thread_profiler;
 
 mod batch_builder;
+mod clip_scroll_node;
+mod clip_scroll_tree;
 mod debug_colors;
 mod debug_font_data;
 mod debug_render;
 mod device;
 mod frame;
 mod frame_builder;
 mod freelist;
 mod geometry;
 mod gpu_store;
 mod internal_types;
-mod layer;
 mod mask_cache;
 mod prim_store;
 mod profiler;
 mod record;
 mod render_backend;
 mod render_task;
 mod resource_cache;
 mod scene;
-mod scroll_tree;
 mod spring;
 mod texture_cache;
 mod tiling;
 mod util;
 
 mod shader_source {
     include!(concat!(env!("OUT_DIR"), "/shaders.rs"));
 }
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -77,28 +77,34 @@ fn dwrite_render_mode(font_face: &dwrote
         return dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC;
     }
 
     dwrite_render_mode
 }
 
 fn get_glyph_dimensions_with_analysis(analysis: dwrote::GlyphRunAnalysis,
                                       texture_type: dwrote::DWRITE_TEXTURE_TYPE)
-                                      -> GlyphDimensions {
+                                      -> Option<GlyphDimensions> {
     let bounds = analysis.get_alpha_texture_bounds(texture_type);
 
     let width = (bounds.right - bounds.left) as u32;
     let height = (bounds.bottom - bounds.top) as u32;
-    assert!(width > 0 && height > 0);
-    GlyphDimensions {
+
+    // Alpha texture bounds can sometimes return an empty rect
+    // Such as for spaces
+    if width == 0 || height == 0 {
+        return None
+    }
+
+    Some(GlyphDimensions {
         left: bounds.left,
         top: -bounds.top,
         width: width,
         height: height,
-    }
+    })
 }
 
 impl FontContext {
     pub fn new() -> FontContext {
         // These are the default values we use in Gecko.
         // We use a gamma value of 2.3 for gdi fonts
         // TODO: Fetch this data from Gecko itself.
         let contrast = 1.0;
@@ -198,17 +204,17 @@ impl FontContext {
     pub fn get_glyph_dimensions(&self,
                                 key: &GlyphKey)
                                 -> Option<GlyphDimensions> {
         // Probably have to default to something else here.
         let render_mode = FontRenderMode::Subpixel;
         let analysis = self.create_glyph_analysis(key, render_mode, None);
 
         let texture_type = dwrite_texture_type(render_mode);
-        Some(get_glyph_dimensions_with_analysis(analysis, texture_type))
+        get_glyph_dimensions_with_analysis(analysis, texture_type)
     }
 
     // DWRITE gives us values in RGB. WR doesn't really touch it after. Note, CG returns in BGR
     // TODO: Decide whether all fonts should return RGB or BGR
     fn convert_to_rgba(&self, pixels: &Vec<u8>, render_mode: FontRenderMode) -> Vec<u8> {
         match render_mode {
             FontRenderMode::Mono => {
                 let mut rgba_pixels: Vec<u8> = vec![0; pixels.len() * 4];
@@ -261,16 +267,20 @@ impl FontContext {
                                                   render_mode,
                                                   glyph_options);
         let texture_type = dwrite_texture_type(render_mode);
 
         let bounds = analysis.get_alpha_texture_bounds(texture_type);
         let width = (bounds.right - bounds.left) as usize;
         let height = (bounds.bottom - bounds.top) as usize;
 
+        // We should not get here since glyph_dimensions would return
+        // None for empty glyphs.
+        assert!(width > 0 && height > 0);
+
         let mut pixels = analysis.create_alpha_texture(texture_type, bounds);
 
         let lut_correction = match glyph_options {
             Some(option) => {
                 if option.force_gdi_rendering {
                     &self.gdi_gamma_lut
                 } else {
                     &self.gamma_lut
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -25,31 +25,40 @@ use webrender_traits::{ExtendMode, Gradi
 pub const CLIP_DATA_GPU_SIZE: usize = 5;
 pub const MASK_DATA_GPU_SIZE: usize = 1;
 
 /// Stores two coordinates in texel space. The coordinates
 /// are stored in texel coordinates because the texture atlas
 /// may grow. Storing them as texel coords and normalizing
 /// the UVs in the vertex shader means nothing needs to be
 /// updated on the CPU when the texture size changes.
-#[derive(Clone)]
+#[derive(Copy, Clone, Debug)]
 pub struct TexelRect {
     pub uv0: DevicePoint,
     pub uv1: DevicePoint,
 }
 
 impl Default for TexelRect {
     fn default() -> TexelRect {
         TexelRect {
             uv0: DevicePoint::zero(),
             uv1: DevicePoint::zero(),
         }
     }
 }
 
+impl TexelRect {
+    pub fn new(u0: u32, v0: u32, u1: u32, v1: u32) -> TexelRect {
+        TexelRect {
+            uv0: DevicePoint::new(u0 as f32, v0 as f32),
+            uv1: DevicePoint::new(u1 as f32, v1 as f32),
+        }
+    }
+}
+
 /// For external images, it's not possible to know the
 /// UV coords of the image (or the image data itself)
 /// until the render thread receives the frame and issues
 /// callbacks to the client application. For external
 /// images that are visible, a DeferredResolve is created
 /// that is stored in the frame. This allows the render
 /// thread to iterate this list and update any changed
 /// texture data and update the UV rect.
@@ -131,16 +140,17 @@ pub enum ImagePrimitiveKind {
     WebGL(WebGLContextId),
 }
 
 #[derive(Debug)]
 pub struct ImagePrimitiveCpu {
     pub kind: ImagePrimitiveKind,
     pub color_texture_id: SourceTexture,
     pub resource_address: GpuStoreAddress,
+    pub sub_rect: Option<TexelRect>,
 }
 
 #[derive(Debug, Clone)]
 #[repr(C)]
 pub struct ImagePrimitiveGpu {
     pub stretch_size: LayerSize,
     pub tile_spacing: LayerSize,
 }
@@ -915,18 +925,28 @@ impl PrimitiveStore {
                         ImagePrimitiveKind::WebGL(context_id) => {
                             let cache_item = resource_cache.get_webgl_texture(&context_id);
                             (cache_item.texture_id, Some(cache_item))
                         }
                     };
 
                     if let Some(cache_item) = cache_item {
                         let resource_rect = self.gpu_resource_rects.get_mut(image_cpu.resource_address);
-                        resource_rect.uv0 = cache_item.uv0;
-                        resource_rect.uv1 = cache_item.uv1;
+                        match image_cpu.sub_rect {
+                            Some(sub_rect) => {
+                                resource_rect.uv0.x = cache_item.uv0.x + sub_rect.uv0.x;
+                                resource_rect.uv0.y = cache_item.uv0.y + sub_rect.uv0.y;
+                                resource_rect.uv1.x = cache_item.uv0.x + sub_rect.uv1.x;
+                                resource_rect.uv1.y = cache_item.uv0.y + sub_rect.uv1.y;
+                            }
+                            None => {
+                                resource_rect.uv0 = cache_item.uv0;
+                                resource_rect.uv1 = cache_item.uv1;
+                            }
+                        }
                     }
                     image_cpu.color_texture_id = texture_id;
                 }
                 PrimitiveKind::YuvImage => {
                     let image_cpu = &mut self.cpu_yuv_images[metadata.cpu_prim_index.0];
                     let image_gpu: &mut YuvImagePrimitiveGpu = unsafe {
                         mem::transmute(self.gpu_data64.get_mut(metadata.gpu_prim_index))
                     };
--- a/gfx/webrender/src/record.rs
+++ b/gfx/webrender/src/record.rs
@@ -1,14 +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 bincode::serde::serialize;
-use bincode;
+use bincode::{SizeLimit, serialize};
 use std::fmt::Debug;
 use std::mem;
 use std::any::TypeId;
 use std::fs::File;
 use std::io::Write;
 use std::path::PathBuf;
 use webrender_traits::ApiMsg;
 use byteorder::{LittleEndian, WriteBytesExt};
@@ -46,17 +45,17 @@ impl BinaryRecorder {
         self.file.write_u32::<LittleEndian>(data.len() as u32).ok();
         self.file.write(data).ok();
     }
 }
 
 impl ApiRecordingReceiver for BinaryRecorder {
     fn write_msg(&mut self, _: u32, msg: &ApiMsg) {
         if should_record_msg(msg) {
-            let buf = serialize(msg, bincode::SizeLimit::Infinite).unwrap();
+            let buf = serialize(msg, SizeLimit::Infinite).unwrap();
             self.write_length_and_data(&buf);
         }
     }
 
     fn write_payload(&mut self, _: u32, data: &[u8]) {
         // signal payload with a 0 length
         self.file.write_u32::<LittleEndian>(0).ok();
         self.write_length_and_data(data);
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -16,17 +16,17 @@ use std::io::{Cursor, Read};
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::Sender;
 use texture_cache::TextureCache;
 use thread_profiler::register_thread_with_profiler;
 use threadpool::ThreadPool;
 use webrender_traits::{ApiMsg, AuxiliaryLists, BuiltDisplayList, IdNamespace, ImageData};
 use webrender_traits::{PipelineId, RenderNotifier, RenderDispatcher, WebGLCommand, WebGLContextId};
 use webrender_traits::channel::{PayloadHelperMethods, PayloadReceiver, PayloadSender, MsgReceiver};
-use webrender_traits::{VRCompositorCommand, VRCompositorHandler};
+use webrender_traits::{BlobImageRenderer, VRCompositorCommand, VRCompositorHandler};
 use offscreen_gl_context::GLContextDispatcher;
 
 /// The render backend is responsible for transforming high level display lists into
 /// GPU-friendly work which is then submitted to the renderer in the form of a frame::Frame.
 ///
 /// The render backend operates on its own thread.
 pub struct RenderBackend {
     api_rx: MsgReceiver<ApiMsg>,
@@ -63,19 +63,20 @@ impl RenderBackend {
                texture_cache: TextureCache,
                enable_aa: bool,
                workers: Arc<Mutex<ThreadPool>>,
                notifier: Arc<Mutex<Option<Box<RenderNotifier>>>>,
                webrender_context_handle: Option<GLContextHandleWrapper>,
                config: FrameBuilderConfig,
                recorder: Option<Box<ApiRecordingReceiver>>,
                main_thread_dispatcher: Arc<Mutex<Option<Box<RenderDispatcher>>>>,
+               blob_image_renderer: Option<Box<BlobImageRenderer>>,
                vr_compositor_handler: Arc<Mutex<Option<Box<VRCompositorHandler>>>>) -> RenderBackend {
 
-        let resource_cache = ResourceCache::new(texture_cache, workers, enable_aa);
+        let resource_cache = ResourceCache::new(texture_cache, workers, blob_image_renderer, enable_aa);
 
         register_thread_with_profiler("Backend".to_string());
 
         RenderBackend {
             api_rx: api_rx,
             payload_rx: payload_rx,
             payload_tx: payload_tx,
             result_tx: result_tx,
@@ -226,17 +227,17 @@ impl RenderBackend {
                                     self.notify_compositor_of_new_scroll_frame(true)
                                 }
                                 None => self.notify_compositor_of_new_scroll_frame(false),
                             }
                         }
                         ApiMsg::ScrollLayersWithScrollId(origin, pipeline_id, scroll_root_id) => {
                             profile_scope!("ScrollLayersWithScrollId");
                             let frame = profile_counters.total_time.profile(|| {
-                                if self.frame.scroll_layers(origin, pipeline_id, scroll_root_id) {
+                                if self.frame.scroll_nodes(origin, pipeline_id, scroll_root_id) {
                                     Some(self.render())
                                 } else {
                                     None
                                 }
                             });
 
                             match frame {
                                 Some(frame) => {
@@ -256,17 +257,17 @@ impl RenderBackend {
 
                             self.publish_frame_and_notify_compositor(frame, &mut profile_counters);
                         }
                         ApiMsg::TranslatePointToLayerSpace(..) => {
                             panic!("unused api - remove from webrender_traits");
                         }
                         ApiMsg::GetScrollLayerState(tx) => {
                             profile_scope!("GetScrollLayerState");
-                            tx.send(self.frame.get_scroll_layer_state())
+                            tx.send(self.frame.get_scroll_node_state())
                               .unwrap()
                         }
                         ApiMsg::RequestWebGLContext(size, attributes, tx) => {
                             if let Some(ref wrapper) = self.webrender_context_handle {
                                 let dispatcher: Option<Box<GLContextDispatcher>> = if cfg!(target_os = "windows") {
                                     Some(Box::new(WebRenderGLDispatcher {
                                         dispatcher: self.main_thread_dispatcher.clone()
                                     }))
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,18 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use internal_types::{HardwareCompositeOp, LowLevelFilterOp};
 use mask_cache::MaskCacheInfo;
 use prim_store::{PrimitiveCacheKey, PrimitiveIndex};
 use std::{cmp, f32, i32, mem, usize};
-use tiling::{PackedLayerIndex, RenderPass, RenderTargetIndex, ScrollLayerIndex};
-use tiling::StackingContextIndex;
+use tiling::{ClipScrollGroupIndex, PackedLayerIndex, RenderPass, RenderTargetIndex};
+use tiling::{ScrollLayerIndex, StackingContextIndex};
 use webrender_traits::{DeviceIntLength, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use webrender_traits::MixBlendMode;
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
 
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
 pub struct RenderTaskIndex(pub usize);
 
@@ -46,17 +46,17 @@ pub enum RenderTaskId {
 #[derive(Debug, Clone)]
 pub enum RenderTaskLocation {
     Fixed,
     Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
 }
 
 #[derive(Debug, Clone)]
 pub enum AlphaRenderItem {
-    Primitive(StackingContextIndex, PrimitiveIndex, i32),
+    Primitive(ClipScrollGroupIndex, PrimitiveIndex, i32),
     Blend(StackingContextIndex, RenderTaskId, LowLevelFilterOp, i32),
     Composite(StackingContextIndex, RenderTaskId, RenderTaskId, MixBlendMode, i32),
     HardwareComposite(StackingContextIndex, RenderTaskId, HardwareCompositeOp, i32),
 }
 
 #[derive(Debug, Clone)]
 pub struct AlphaRenderTask {
     screen_origin: DeviceIntPoint,
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -43,17 +43,17 @@ use threadpool::ThreadPool;
 use tiling::{AlphaBatchKind, BlurCommand, Frame, PrimitiveBatch, PrimitiveBatchData};
 use tiling::{CacheClipInstance, PrimitiveInstance, RenderTarget};
 use time::precise_time_ns;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use util::TransformedRectKind;
 use webrender_traits::{ColorF, Epoch, PipelineId, RenderNotifier, RenderDispatcher};
 use webrender_traits::{ExternalImageId, ImageData, ImageFormat, RenderApiSender, RendererKind};
 use webrender_traits::{DeviceIntRect, DevicePoint, DeviceIntPoint, DeviceIntSize, DeviceUintSize};
-use webrender_traits::ImageDescriptor;
+use webrender_traits::{ImageDescriptor, BlobImageRenderer};
 use webrender_traits::channel;
 use webrender_traits::VRCompositorHandler;
 
 pub const GPU_DATA_TEXTURE_POOL: usize = 5;
 pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024;
 
 const GPU_TAG_CACHE_BOX_SHADOW: GpuProfileTag = GpuProfileTag { label: "C_BoxShadow", color: debug_colors::BLACK };
 const GPU_TAG_CACHE_CLIP: GpuProfileTag = GpuProfileTag { label: "C_Clip", color: debug_colors::PURPLE };
@@ -458,16 +458,21 @@ pub struct Renderer {
     /// by the backend thread / texture cache. We free the
     /// texture memory associated with a TextureId when its
     /// texture cache ID is freed by the texture cache, but
     /// reuse the TextureId when the texture caches's free
     /// list reuses the texture cache ID. This saves having to
     /// use a hashmap, and allows a flat vector for performance.
     cache_texture_id_map: Vec<TextureId>,
 
+    /// A special 1x1 dummy cache texture used for shaders that expect to work
+    /// with the cache but are actually running in the first pass
+    /// when no target is yet provided as a cache texture input.
+    dummy_cache_texture_id: TextureId,
+
     /// Optional trait object that allows the client
     /// application to provide external buffers for image data.
     external_image_handler: Option<Box<ExternalImageHandler>>,
 
     /// Map of external image IDs to native textures.
     external_images: HashMap<ExternalImageId, TextureId, BuildHasherDefault<FnvHasher>>,
 
     // Optional trait object that handles WebVR commands.
@@ -670,17 +675,20 @@ impl Renderer {
         let ps_hw_composite = try!{
             LazilyCompiledShader::new(ShaderKind::Primitive,
                                      "ps_hardware_composite",
                                      &[],
                                      &mut device,
                                      options.precache_shaders)
         };
 
-        let mut texture_cache = TextureCache::new();
+        let device_max_size = device.max_texture_size();
+        let max_texture_size = cmp::min(device_max_size, options.max_texture_size.unwrap_or(device_max_size));
+
+        let mut texture_cache = TextureCache::new(max_texture_size);
 
         let white_pixels: Vec<u8> = vec![
             0xff, 0xff, 0xff, 0xff,
             0xff, 0xff, 0xff, 0xff,
             0xff, 0xff, 0xff, 0xff,
             0xff, 0xff, 0xff, 0xff,
         ];
         let mask_pixels: Vec<u8> = vec![
@@ -707,16 +715,25 @@ impl Renderer {
                                 height: 2,
                                 stride: None,
                                 format: ImageFormat::A8,
                                 is_opaque: false,
                              },
                              TextureFilter::Linear,
                              ImageData::Raw(Arc::new(mask_pixels)));
 
+        let dummy_cache_texture_id = device.create_texture_ids(1, TextureTarget::Array)[0];
+        device.init_texture(dummy_cache_texture_id,
+                            1,
+                            1,
+                            ImageFormat::RGBA8,
+                            TextureFilter::Linear,
+                            RenderTargetMode::LayerRenderTarget(1),
+                            None);
+
         let debug_renderer = DebugRenderer::new(&mut device);
 
         let gpu_data_textures = [
             GpuDataTextures::new(&mut device),
             GpuDataTextures::new(&mut device),
             GpuDataTextures::new(&mut device),
             GpuDataTextures::new(&mut device),
             GpuDataTextures::new(&mut device),
@@ -775,30 +792,33 @@ impl Renderer {
         let (device_pixel_ratio, enable_aa) = (options.device_pixel_ratio, options.enable_aa);
         let render_target_debug = options.render_target_debug;
         let payload_tx_for_backend = payload_tx.clone();
         let recorder = options.recorder;
         let workers = options.workers.take().unwrap_or_else(||{
             // TODO(gw): Use a heuristic to select best # of worker threads.
             Arc::new(Mutex::new(ThreadPool::new_with_name("WebRender:Worker".to_string(), 4)))
         });
+
+        let blob_image_renderer = options.blob_image_renderer.take();
         try!{ thread::Builder::new().name("RenderBackend".to_string()).spawn(move || {
             let mut backend = RenderBackend::new(api_rx,
                                                  payload_rx,
                                                  payload_tx_for_backend,
                                                  result_tx,
                                                  device_pixel_ratio,
                                                  texture_cache,
                                                  enable_aa,
                                                  workers,
                                                  backend_notifier,
                                                  context_handle,
                                                  config,
                                                  recorder,
                                                  backend_main_thread_dispatcher,
+                                                 blob_image_renderer,
                                                  backend_vr_compositor);
             backend.run();
         })};
 
         let renderer = Renderer {
             result_rx: result_rx,
             device: device,
             current_frame: None,
@@ -839,16 +859,17 @@ impl Renderer {
             prim_vao_id: prim_vao_id,
             blur_vao_id: blur_vao_id,
             clip_vao_id: clip_vao_id,
             gdt_index: 0,
             gpu_data_textures: gpu_data_textures,
             pipeline_epoch_map: HashMap::with_hasher(Default::default()),
             main_thread_dispatcher: main_thread_dispatcher,
             cache_texture_id_map: Vec::new(),
+            dummy_cache_texture_id: dummy_cache_texture_id,
             external_image_handler: None,
             external_images: HashMap::with_hasher(Default::default()),
             vr_compositor_handler: vr_compositor
         };
 
         let sender = RenderApiSender::new(api_tx, payload_tx);
         Ok((renderer, sender))
     }
@@ -1158,17 +1179,17 @@ impl Renderer {
         self.profile_counters.vertices.add(6 * data.len());
         self.profile_counters.draw_calls.inc();
     }
 
     fn submit_batch(&mut self,
                     batch: &PrimitiveBatch,
                     projection: &Matrix4D<f32>,
                     render_task_data: &Vec<RenderTaskData>,
-                    cache_texture: Option<TextureId>,
+                    cache_texture: TextureId,
                     render_target: Option<(TextureId, i32)>,
                     target_dimensions: DeviceUintSize) {
         let transform_kind = batch.key.flags.transform_kind();
         let needs_clipping = batch.key.flags.needs_clipping();
         debug_assert!(!needs_clipping || batch.key.blend_mode == BlendMode::Alpha);
 
         match batch.data {
             PrimitiveBatchData::Instances(ref data) => {
@@ -1251,28 +1272,27 @@ impl Renderer {
                 // on pulling specific values from the render target data
                 // and also cloning the single primitive instance to be
                 // able to pass to draw_instanced_batch(). We should
                 // think about a cleaner way to achieve this!
 
                 // Before submitting the composite batch, do the
                 // framebuffer readbacks that are needed for each
                 // composite operation in this batch.
-                let cache_texture_id = cache_texture.unwrap();
-                let cache_texture_dimensions = self.device.get_texture_dimensions(cache_texture_id);
+                let cache_texture_dimensions = self.device.get_texture_dimensions(cache_texture);
 
                 let backdrop = &render_task_data[instance.task_index as usize];
                 let readback = &render_task_data[instance.user_data[0] as usize];
                 let source = &render_task_data[instance.user_data[1] as usize];
 
                 // Bind the FBO to blit the backdrop to.
                 // Called per-instance in case the layer (and therefore FBO)
                 // changes. The device will skip the GL call if the requested
                 // target is already bound.
-                let cache_draw_target = (cache_texture_id, readback.data[4] as i32);
+                let cache_draw_target = (cache_texture, readback.data[4] as i32);
                 self.device.bind_draw_target(Some(cache_draw_target), Some(cache_texture_dimensions));
 
                 let src_x = backdrop.data[0] - backdrop.data[4] + source.data[4];
                 let src_y = backdrop.data[1] - backdrop.data[5] + source.data[5];
 
                 let dest_x = readback.data[0];
                 let dest_y = readback.data[1];
 
@@ -1309,32 +1329,30 @@ impl Renderer {
             }
         }
     }
 
     fn draw_target(&mut self,
                    render_target: Option<(TextureId, i32)>,
                    target: &RenderTarget,
                    target_size: DeviceUintSize,
-                   cache_texture: Option<TextureId>,
+                   cache_texture: TextureId,
                    should_clear: bool,
                    background_color: Option<ColorF>,
                    render_task_data: &Vec<RenderTaskData>) {
         self.device.disable_depth();
         self.device.enable_depth_write();
 
         let projection = {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_SETUP_TARGET);
             self.device.bind_draw_target(render_target, Some(target_size));
 
             self.device.set_blend(false);
             self.device.set_blend_mode_alpha();
-            if let Some(cache_texture) = cache_texture {
-                self.device.bind_texture(TextureSampler::Cache, cache_texture);
-            }
+            self.device.bind_texture(TextureSampler::Cache, cache_texture);
 
             let (color, projection) = match render_target {
                 Some(..) => (
                     // The clear color here is chosen specifically such that:
                     // - The red channel is cleared to 1, so that the clip mask
                     //   generation (which reads/writes the red channel) can
                     //   assume that each allocated rect is opaque / non-clipped
                     //   initially.
@@ -1627,17 +1645,17 @@ impl Renderer {
 
             // TODO(gw): This is a hack / workaround for #728.
             // We should find a better way to implement these updates rather
             // than wasting this extra memory, but for now it removes a large
             // number of driver stalls.
             self.gpu_data_textures[self.gdt_index].init_frame(&mut self.device, frame);
             self.gdt_index = (self.gdt_index + 1) % GPU_DATA_TEXTURE_POOL;
 
-            let mut src_id = None;
+            let mut src_id = self.dummy_cache_texture_id;
 
             for (pass_index, pass) in frame.passes.iter().enumerate() {
                 let (do_clear, size, target_id) = if pass.is_framebuffer {
                     (self.clear_framebuffer || needs_clear,
                      framebuffer_size,
                      None)
                 } else {
                     (true, &frame.cache_size, Some(self.render_targets[pass_index]))
@@ -1652,17 +1670,17 @@ impl Renderer {
                                      *size,
                                      src_id,
                                      do_clear,
                                      frame.background_color,
                                      &frame.render_task_data);
 
                 }
 
-                src_id = target_id;
+                src_id = target_id.unwrap_or(self.dummy_cache_texture_id);
             }
 
             self.draw_render_target_debug(framebuffer_size);
         }
 
         self.unlock_external_images();
     }
 
@@ -1711,16 +1729,24 @@ impl Renderer {
                                                    None,
                                                    dest_rect);
 
                     current_target += 1;
                 }
             }
         }
     }
+
+    // De-initialize the Renderer safely, assuming the GL is still alive and active.
+    pub fn deinit(mut self) {
+        //Note: this is a fake frame, only needed because texture deletion is require to happen inside a frame
+        self.device.begin_frame(1.0);
+        self.device.deinit_texture(self.dummy_cache_texture_id);
+        self.device.end_frame();
+    }
 }
 
 pub enum ExternalImageSource<'a> {
     RawData(&'a [u8]),      // raw buffers.
     NativeTexture(u32),     // Is a gl::GLuint texture handle
 }
 
 /// The data that an external client should provide about
@@ -1751,31 +1777,32 @@ pub trait ExternalImageHandler {
     fn lock(&mut self, key: ExternalImageId) -> ExternalImage;
     /// Unlock the external image. The WR should not read the image content
     /// after this call.
     fn unlock(&mut self, key: ExternalImageId);
     /// Tell the WR client that it could start to release this external image.
     fn release(&mut self, key: ExternalImageId);
 }
 
-#[derive(Debug)]
 pub struct RendererOptions {
     pub device_pixel_ratio: f32,
     pub resource_override_path: Option<PathBuf>,
     pub enable_aa: bool,
     pub enable_profiler: bool,
     pub debug: bool,
     pub enable_scrollbars: bool,
     pub precache_shaders: bool,
     pub renderer_kind: RendererKind,
     pub enable_subpixel_aa: bool,
     pub clear_framebuffer: bool,
     pub clear_color: ColorF,
     pub render_target_debug: bool,
+    pub max_texture_size: Option<u32>,
     pub workers: Option<Arc<Mutex<ThreadPool>>>,
+    pub blob_image_renderer: Option<Box<BlobImageRenderer>>,
     pub recorder: Option<Box<ApiRecordingReceiver>>,
 }
 
 impl Default for RendererOptions {
     fn default() -> RendererOptions {
         RendererOptions {
             device_pixel_ratio: 1.0,
             resource_override_path: None,
@@ -1784,13 +1811,15 @@ impl Default for RendererOptions {
             debug: false,
             enable_scrollbars: false,
             precache_shaders: false,
             renderer_kind: RendererKind::Native,
             enable_subpixel_aa: false,
             clear_framebuffer: true,
             clear_color: ColorF::new(1.0, 1.0, 1.0, 1.0),
             render_target_debug: false,
+            max_texture_size: None,
             workers: None,
+            blob_image_renderer: None,
             recorder: None,
         }
     }
 }
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -19,16 +19,17 @@ use std::sync::{Arc, Barrier, Mutex};
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
 use texture_cache::{TextureCache, TextureCacheItemId};
 use thread_profiler::register_thread_with_profiler;
 use webrender_traits::{Epoch, FontKey, GlyphKey, ImageKey, ImageFormat, ImageRendering};
 use webrender_traits::{FontRenderMode, ImageData, GlyphDimensions, WebGLContextId};
 use webrender_traits::{DevicePoint, DeviceIntSize, ImageDescriptor, ColorF};
 use webrender_traits::{ExternalImageId, GlyphOptions, GlyphInstance};
+use webrender_traits::{BlobImageRenderer, BlobImageDescriptor, BlobImageError};
 use threadpool::ThreadPool;
 use euclid::Point2D;
 
 thread_local!(pub static FONT_CONTEXT: RefCell<FontContext> = RefCell::new(FontContext::new()));
 
 type GlyphCache = ResourceClassCache<RenderedGlyphKey, Option<TextureCacheItemId>>;
 
 /// Message sent from the resource cache to the glyph cache thread.
@@ -204,21 +205,25 @@ pub struct ResourceCache {
     texture_cache: TextureCache,
 
     // TODO(gw): We should expire (parts of) this cache semi-regularly!
     cached_glyph_dimensions: HashMap<GlyphKey, Option<GlyphDimensions>, BuildHasherDefault<FnvHasher>>,
     pending_image_requests: Vec<ImageRequest>,
     glyph_cache_tx: Sender<GlyphCacheMsg>,
     glyph_cache_result_queue: Receiver<GlyphCacheResultMsg>,
     pending_external_image_update_list: ExternalImageUpdateList,
+
+    blob_image_renderer: Option<Box<BlobImageRenderer>>,
+    blob_image_requests: HashSet<ImageRequest>,
 }
 
 impl ResourceCache {
     pub fn new(texture_cache: TextureCache,
                workers: Arc<Mutex<ThreadPool>>,
+               blob_image_renderer: Option<Box<BlobImageRenderer>>,
                enable_aa: bool) -> ResourceCache {
         let (glyph_cache_tx, glyph_cache_result_queue) = spawn_glyph_cache_thread(workers);
 
         ResourceCache {
             cached_glyphs: Some(ResourceClassCache::new()),
             cached_images: ResourceClassCache::new(),
             webgl_textures: HashMap::with_hasher(Default::default()),
             font_templates: HashMap::with_hasher(Default::default()),
@@ -227,32 +232,45 @@ impl ResourceCache {
             texture_cache: texture_cache,
             state: State::Idle,
             enable_aa: enable_aa,
             current_frame_id: FrameId(0),
             pending_image_requests: Vec::new(),
             glyph_cache_tx: glyph_cache_tx,
             glyph_cache_result_queue: glyph_cache_result_queue,
             pending_external_image_update_list: ExternalImageUpdateList::new(),
+
+            blob_image_renderer: blob_image_renderer,
+            blob_image_requests: HashSet::new(),
         }
     }
 
+    pub fn max_texture_size(&self) -> u32 {
+        self.texture_cache.max_texture_size()
+    }
+
     pub fn add_font_template(&mut self, font_key: FontKey, template: FontTemplate) {
         // Push the new font to the glyph cache thread, and also store
         // it locally for glyph metric requests.
         self.glyph_cache_tx
             .send(GlyphCacheMsg::AddFont(font_key, template.clone()))
             .unwrap();
         self.font_templates.insert(font_key, template);
     }
 
     pub fn add_image_template(&mut self,
                               image_key: ImageKey,
                               descriptor: ImageDescriptor,
                               data: ImageData) {
+        if descriptor.width > self.max_texture_size() || descriptor.height > self.max_texture_size() {
+            // TODO: we need to support handle this case gracefully, cf. issue #620.
+            println!("Warning: texture size ({} {}) larger than the maximum size",
+                     descriptor.width, descriptor.height);
+        }
+
         let resource = ImageResource {
             descriptor: descriptor,
             data: data,
             epoch: Epoch(0),
         };
 
         self.image_templates.insert(image_key, resource);
     }
@@ -316,24 +334,48 @@ impl ResourceCache {
     pub fn update_webgl_texture(&mut self, id: WebGLContextId, texture_id: SourceTexture, size: DeviceIntSize) {
         let webgl_texture = self.webgl_textures.get_mut(&id).unwrap();
 
         // Update new texture id and size
         webgl_texture.id = texture_id;
         webgl_texture.size = size;
     }
 
-    pub fn request_image(&mut self,
-                         key: ImageKey,
-                         rendering: ImageRendering) {
+    pub fn request_image(&mut self, key: ImageKey, rendering: ImageRendering) {
         debug_assert!(self.state == State::AddResources);
-        self.pending_image_requests.push(ImageRequest {
+        let request = ImageRequest {
             key: key,
             rendering: rendering,
-        });
+        };
+
+        let template = self.image_templates.get(&key).unwrap();
+        if let ImageData::Blob(ref data) = template.data {
+            if let Some(ref mut renderer) = self.blob_image_renderer {
+                let same_epoch = match self.cached_images.resources.get(&request) {
+                    Some(entry) => entry.epoch == template.epoch,
+                    None => false,
+                };
+
+                if !same_epoch && self.blob_image_requests.insert(request) {
+                    renderer.request_blob_image(
+                        key,
+                        data.clone(),
+                        &BlobImageDescriptor {
+                            width: template.descriptor.width,
+                            height: template.descriptor.height,
+                            format: template.descriptor.format,
+                            // TODO(nical): figure out the scale factor (should change with zoom).
+                            scale_factor: 1.0,
+                        },
+                    );
+                }
+            }
+        } else {
+            self.pending_image_requests.push(request);
+        }
     }
 
     pub fn request_glyphs(&mut self,
                           key: FontKey,
                           size: Au,
                           color: ColorF,
                           glyph_instances: &[GlyphInstance],
                           render_mode: FontRenderMode,
@@ -448,17 +490,17 @@ impl ResourceCache {
     }
 
     pub fn get_image_properties(&self, image_key: ImageKey) -> ImageProperties {
         let image_template = &self.image_templates[&image_key];
 
         let external_id = match image_template.data {
             ImageData::ExternalHandle(id) => Some(id),
             // raw and externalBuffer are all use resource_cache.
-            ImageData::Raw(..) | ImageData::ExternalBuffer(..) => None,
+            ImageData::Raw(..) | ImageData::ExternalBuffer(..) | ImageData::Blob(..) => None,
         };
 
         ImageProperties {
             descriptor: image_template.descriptor,
             external_id: external_id,
         }
     }
 
@@ -534,61 +576,95 @@ impl ResourceCache {
                     }
 
                     self.cached_glyphs = Some(cache);
                     break;
                 }
             }
         }
 
-        for request in self.pending_image_requests.drain(..) {
-            let cached_images = &mut self.cached_images;
-            let image_template = &self.image_templates[&request.key];
-            let image_data = image_template.data.clone();
-
-            match image_template.data {
-                ImageData::ExternalHandle(..) => {
-                    // external handle doesn't need to update the texture_cache.
-                }
-                ImageData::Raw(..) | ImageData::ExternalBuffer(..) => {
-                    match cached_images.entry(request.clone(), self.current_frame_id) {
-                        Occupied(entry) => {
-                            let image_id = entry.get().texture_cache_id;
-
-                            if entry.get().epoch != image_template.epoch {
-                                self.texture_cache.update(image_id,
-                                                          image_template.descriptor,
-                                                          image_data);
+        let mut image_requests = mem::replace(&mut self.pending_image_requests, Vec::new());
+        for request in image_requests.drain(..) {
+            self.finalize_image_request(request, None);
+        }
 
-                                // Update the cached epoch
-                                *entry.into_mut() = CachedImageInfo {
-                                    texture_cache_id: image_id,
-                                    epoch: image_template.epoch,
-                                };
-                            }
-                        }
-                        Vacant(entry) => {
-                            let image_id = self.texture_cache.new_item_id();
+        let mut blob_image_requests = mem::replace(&mut self.blob_image_requests, HashSet::new());
+        if self.blob_image_renderer.is_some() {
+            for request in blob_image_requests.drain() {
+                match self.blob_image_renderer.as_mut().unwrap()
+                                                .resolve_blob_image(request.key) {
+                    Ok(image) => {
+                        self.finalize_image_request(request, Some(ImageData::new(image.data)));
+                    }
+                    // TODO(nical): I think that we should handle these somewhat gracefully,
+                    // at least in the out-of-memory scenario.
+                    Err(BlobImageError::Oom) => {
+                        // This one should be recoverable-ish.
+                        panic!("Failed to render a vector image (OOM)");
+                    }
+                    Err(BlobImageError::InvalidKey) => {
+                        panic!("Invalid vector image key");
+                    }
+                    Err(BlobImageError::InvalidData) => {
+                        // TODO(nical): If we run into this we should kill the content process.
+                        panic!("Invalid vector image data");
+                    }
+                    Err(BlobImageError::Other(msg)) => {
+                        panic!("Vector image error {}", msg);
+                    }
+                }
+            }
+        }
+    }
 
-                            let filter = match request.rendering {
-                                ImageRendering::Pixelated => TextureFilter::Nearest,
-                                ImageRendering::Auto | ImageRendering::CrispEdges => TextureFilter::Linear,
-                            };
+    fn finalize_image_request(&mut self, request: ImageRequest, image_data: Option<ImageData>) {
+        let image_template = &self.image_templates[&request.key];
+        let image_data = image_data.unwrap_or_else(||{
+            image_template.data.clone()
+        });
 
-                            self.texture_cache.insert(image_id,
+        match image_template.data {
+            ImageData::ExternalHandle(..) => {
+                // external handle doesn't need to update the texture_cache.
+            }
+            ImageData::Raw(..) | ImageData::ExternalBuffer(..) | ImageData::Blob(..) => {
+                match self.cached_images.entry(request.clone(), self.current_frame_id) {
+                    Occupied(entry) => {
+                        let image_id = entry.get().texture_cache_id;
+
+                        if entry.get().epoch != image_template.epoch {
+                            self.texture_cache.update(image_id,
                                                       image_template.descriptor,
-                                                      filter,
                                                       image_data);
 
-                            entry.insert(CachedImageInfo {
+                            // Update the cached epoch
+                            *entry.into_mut() = CachedImageInfo {
                                 texture_cache_id: image_id,
                                 epoch: image_template.epoch,
-                            });
+                            };
                         }
                     }
+                    Vacant(entry) => {
+                        let image_id = self.texture_cache.new_item_id();
+
+                        let filter = match request.rendering {
+                            ImageRendering::Pixelated => TextureFilter::Nearest,
+                            ImageRendering::Auto | ImageRendering::CrispEdges => TextureFilter::Linear,
+                        };
+
+                        self.texture_cache.insert(image_id,
+                                                  image_template.descriptor,
+                                                  filter,
+                                                  image_data);
+
+                        entry.insert(CachedImageInfo {
+                            texture_cache_id: image_id,
+                            epoch: image_template.epoch,
+                        });
+                    }
                 }
             }
         }
     }
 
     pub fn end_frame(&mut self) {
         debug_assert!(self.state == State::QueryResources);
         self.state = State::Idle;
deleted file mode 100644
--- a/gfx/webrender/src/scroll_tree.rs
+++ /dev/null
@@ -1,394 +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 fnv::FnvHasher;
-use layer::{Layer, ScrollingState};
-use std::collections::{HashMap, HashSet};
-use std::hash::BuildHasherDefault;
-use webrender_traits::{LayerPoint, LayerRect, LayerSize, LayerToScrollTransform, PipelineId};
-use webrender_traits::{ScrollEventPhase, ScrollLayerId, ScrollLayerInfo, ScrollLayerPixel};
-use webrender_traits::{ScrollLayerRect, ScrollLayerState, ScrollLocation, ScrollToWorldTransform};
-use webrender_traits::{ServoScrollRootId, WorldPoint, as_scroll_parent_rect};
-
-pub type ScrollStates = HashMap<ScrollLayerId, ScrollingState, BuildHasherDefault<FnvHasher>>;
-
-pub struct ScrollTree {
-    pub layers: HashMap<ScrollLayerId, Layer, BuildHasherDefault<FnvHasher>>,
-    pub pending_scroll_offsets: HashMap<(PipelineId, ServoScrollRootId), LayerPoint>,
-
-    /// The ScrollLayerId of the currently scrolling layer. Used to allow the same
-    /// layer to scroll even if a touch operation leaves the boundaries of that layer.
-    pub current_scroll_layer_id: Option<ScrollLayerId>,
-
-    /// The current reference frame id, used for giving a unique id to all new
-    /// reference frames. The ScrollTree increments this by one every time a
-    /// reference frame is created.
-    current_reference_frame_id: usize,
-
-    /// The root reference frame, which is the true root of the ScrollTree. Initially
-    /// this ID is not valid, which is indicated by ```layers``` being empty.
-    pub root_reference_frame_id: ScrollLayerId,
-
-    /// The root scroll layer, which is the first child of the root reference frame.
-    /// Initially this ID is not valid, which is indicated by ```layers``` being empty.
-    pub topmost_scroll_layer_id: ScrollLayerId,
-
-    /// A set of pipelines which should be discarded the next time this
-    /// tree is drained.
-    pub pipelines_to_discard: HashSet<PipelineId>,
-}
-
-impl ScrollTree {
-    pub fn new() -> ScrollTree {
-        let dummy_pipeline = PipelineId(0, 0);
-        ScrollTree {
-            layers: HashMap::with_hasher(Default::default()),
-            pending_scroll_offsets: HashMap::new(),
-            current_scroll_layer_id: None,
-            root_reference_frame_id: ScrollLayerId::root_reference_frame(dummy_pipeline),
-            topmost_scroll_layer_id: ScrollLayerId::root_scroll_layer(dummy_pipeline),
-            current_reference_frame_id: 1,
-            pipelines_to_discard: HashSet::new(),
-        }
-    }
-
-    pub fn root_reference_frame_id(&self) -> ScrollLayerId {
-        // TODO(mrobinson): We should eventually make this impossible to misuse.
-        debug_assert!(!self.layers.is_empty());
-        debug_assert!(self.layers.contains_key(&self.root_reference_frame_id));
-        self.root_reference_frame_id
-    }
-
-    pub fn topmost_scroll_layer_id(&self) -> ScrollLayerId {
-        // TODO(mrobinson): We should eventually make this impossible to misuse.
-        debug_assert!(!self.layers.is_empty());
-        debug_assert!(self.layers.contains_key(&self.topmost_scroll_layer_id));
-        self.topmost_scroll_layer_id
-    }
-
-    pub fn establish_root(&mut self,
-                          pipeline_id: PipelineId,
-                          viewport_size: &LayerSize,
-                          content_size: &LayerSize) {
-        debug_assert!(self.layers.is_empty());
-
-        let identity = LayerToScrollTransform::identity();
-        let viewport = LayerRect::new(LayerPoint::zero(), *viewport_size);
-
-        let root_reference_frame_id = ScrollLayerId::root_reference_frame(pipeline_id);
-        self.root_reference_frame_id = root_reference_frame_id;
-        let reference_frame = Layer::new(&viewport, viewport.size, &identity, pipeline_id);
-        self.layers.insert(self.root_reference_frame_id, reference_frame);
-
-        let scroll_layer = Layer::new(&viewport, *content_size, &identity, pipeline_id);
-        let topmost_scroll_layer_id = ScrollLayerId::root_scroll_layer(pipeline_id);
-        self.topmost_scroll_layer_id = topmost_scroll_layer_id;
-        self.add_layer(scroll_layer, topmost_scroll_layer_id, root_reference_frame_id);
-    }
-
-    pub fn collect_layers_bouncing_back(&self)
-                                        -> HashSet<ScrollLayerId, BuildHasherDefault<FnvHasher>> {
-        let mut layers_bouncing_back = HashSet::with_hasher(Default::default());
-        for (scroll_layer_id, layer) in self.layers.iter() {
-            if layer.scrolling.bouncing_back {
-                layers_bouncing_back.insert(*scroll_layer_id);
-            }
-        }
-        layers_bouncing_back
-    }
-
-    fn find_scrolling_layer_at_point_in_layer(&self,
-                                              cursor: &WorldPoint,
-                                              scroll_layer_id: ScrollLayerId)
-                                              -> Option<ScrollLayerId> {
-        self.layers.get(&scroll_layer_id).and_then(|layer| {
-            for child_layer_id in layer.children.iter().rev() {
-            if let Some(layer_id) =
-                self.find_scrolling_layer_at_point_in_layer(cursor, *child_layer_id) {
-                    return Some(layer_id);
-                }
-            }
-
-            if let ScrollLayerInfo::ReferenceFrame(_) = scroll_layer_id.info {
-                return None;
-            }
-
-            if layer.ray_intersects_layer(cursor) {
-                Some(scroll_layer_id)
-            } else {
-                None
-            }
-        })
-    }
-
-    pub fn find_scrolling_layer_at_point(&self, cursor: &WorldPoint) -> ScrollLayerId {
-        self.find_scrolling_layer_at_point_in_layer(cursor, self.root_reference_frame_id())
-            .unwrap_or(self.topmost_scroll_layer_id())
-    }
-
-    pub fn get_scroll_layer_state(&self) -> Vec<ScrollLayerState> {
-        let mut result = vec![];
-        for (scroll_layer_id, scroll_layer) in self.layers.iter() {
-            match scroll_layer_id.info {
-                ScrollLayerInfo::Scrollable(_, servo_scroll_root_id) => {
-                    result.push(ScrollLayerState {
-                        pipeline_id: scroll_layer.pipeline_id,
-                        scroll_root_id: servo_scroll_root_id,
-                        scroll_offset: scroll_layer.scrolling.offset,
-                    })
-                }
-                ScrollLayerInfo::ReferenceFrame(..) => {}
-            }
-        }
-        result
-    }
-
-    pub fn drain(&mut self) -> ScrollStates {
-        self.current_reference_frame_id = 1;
-
-        let mut scroll_states = HashMap::with_hasher(Default::default());
-        for (layer_id, old_layer) in &mut self.layers.drain() {
-            if !self.pipelines_to_discard.contains(&layer_id.pipeline_id) {
-                scroll_states.insert(layer_id, old_layer.scrolling);
-            }
-        }
-
-        self.pipelines_to_discard.clear();
-        scroll_states
-    }
-
-    pub fn scroll_layers(&mut self,
-                         origin: LayerPoint,
-                         pipeline_id: PipelineId,
-                         scroll_root_id: ServoScrollRootId)
-                         -> bool {
-        if self.layers.is_empty() {
-            self.pending_scroll_offsets.insert((pipeline_id, scroll_root_id), origin);
-            return false;
-        }
-
-        let origin = LayerPoint::new(origin.x.max(0.0), origin.y.max(0.0));
-
-        let mut scrolled_a_layer = false;
-        let mut found_layer = false;
-        for (layer_id, layer) in self.layers.iter_mut() {
-            if layer_id.pipeline_id != pipeline_id {
-                continue;
-            }
-
-            match layer_id.info {
-                ScrollLayerInfo::Scrollable(_, id) if id != scroll_root_id => continue,
-                ScrollLayerInfo::ReferenceFrame(..) => continue,
-                ScrollLayerInfo::Scrollable(..) => {}
-            }
-
-            found_layer = true;
-            scrolled_a_layer |= layer.set_scroll_origin(&origin);
-        }
-
-        if !found_layer {
-            self.pending_scroll_offsets.insert((pipeline_id, scroll_root_id), origin);
-        }
-
-        scrolled_a_layer
-    }
-
-    pub fn scroll(&mut self,
-                  scroll_location: ScrollLocation,
-                  cursor: WorldPoint,
-                  phase: ScrollEventPhase)
-                  -> bool {
-        if self.layers.is_empty() {
-            return false;
-        }
-
-        let scroll_layer_id = match (
-            phase,
-            self.find_scrolling_layer_at_point(&cursor),
-            self.current_scroll_layer_id) {
-            (ScrollEventPhase::Start, scroll_layer_at_point_id, _) => {
-                self.current_scroll_layer_id = Some(scroll_layer_at_point_id);
-                scroll_layer_at_point_id
-            },
-            (_, scroll_layer_at_point_id, Some(cached_scroll_layer_id)) => {
-                let scroll_layer_id = match self.layers.get(&cached_scroll_layer_id) {
-                    Some(_) => cached_scroll_layer_id,
-                    None => {
-                        self.current_scroll_layer_id = Some(scroll_layer_at_point_id);
-                        scroll_layer_at_point_id
-                    },
-                };
-                scroll_layer_id
-            },
-            (_, _, None) => return false,
-        };
-
-        let topmost_scroll_layer_id = self.topmost_scroll_layer_id();
-        let non_root_overscroll = if scroll_layer_id != topmost_scroll_layer_id {
-            // true if the current layer is overscrolling,
-            // and it is not the root scroll layer.
-            let child_layer = self.layers.get(&scroll_layer_id).unwrap();
-            let overscroll_amount = child_layer.overscroll_amount();
-            overscroll_amount.width != 0.0 || overscroll_amount.height != 0.0
-        } else {
-            false
-        };
-
-        let switch_layer = match phase {
-            ScrollEventPhase::Start => {
-                // if this is a new gesture, we do not switch layer,
-                // however we do save the state of non_root_overscroll,
-                // for use in the subsequent Move phase.
-                let mut current_layer = self.layers.get_mut(&scroll_layer_id).unwrap();
-                current_layer.scrolling.should_handoff_scroll = non_root_overscroll;
-                false
-            },
-            ScrollEventPhase::Move(_) => {
-                // Switch layer if movement originated in a new gesture,
-                // from a non root layer in overscroll.
-                let current_layer = self.layers.get_mut(&scroll_layer_id).unwrap();
-                current_layer.scrolling.should_handoff_scroll && non_root_overscroll
-            },
-            ScrollEventPhase::End => {
-                // clean-up when gesture ends.
-                let mut current_layer = self.layers.get_mut(&scroll_layer_id).unwrap();
-                current_layer.scrolling.should_handoff_scroll = false;
-                false
-            },
-        };
-
-        let scroll_layer_info = if switch_layer {
-            topmost_scroll_layer_id.info
-        } else {
-            scroll_layer_id.info
-        };
-
-        let scroll_root_id = match scroll_layer_info {
-             ScrollLayerInfo::Scrollable(_, scroll_root_id) => scroll_root_id,
-             _ => unreachable!("Tried to scroll a reference frame."),
-
-        };
-
-        let mut scrolled_a_layer = false;
-        for (layer_id, layer) in self.layers.iter_mut() {
-            if layer_id.pipeline_id != scroll_layer_id.pipeline_id {
-                continue;
-            }
-
-            match layer_id.info {
-                ScrollLayerInfo::Scrollable(_, id) if id != scroll_root_id => continue,
-                ScrollLayerInfo::ReferenceFrame(..) => continue,
-                _ => {}
-            }
-
-            let scrolled_this_layer = layer.scroll(scroll_location, phase);
-            scrolled_a_layer = scrolled_a_layer || scrolled_this_layer;
-        }
-        scrolled_a_layer
-    }
-
-    pub fn update_all_layer_transforms(&mut self) {
-        if self.layers.is_empty() {
-            return;
-        }
-
-        let root_reference_frame_id = self.root_reference_frame_id();
-        let root_viewport = self.layers[&root_reference_frame_id].local_viewport_rect;
-        self.update_layer_transform(root_reference_frame_id,
-                                    &ScrollToWorldTransform::identity(),
-                                    &as_scroll_parent_rect(&root_viewport));
-    }
-
-    fn update_layer_transform(&mut self,
-                              layer_id: ScrollLayerId,
-                              parent_world_transform: &ScrollToWorldTransform,
-                              parent_viewport_rect: &ScrollLayerRect) {
-        // TODO(gw): This is an ugly borrow check workaround to clone these.
-        //           Restructure this to avoid the clones!
-        let (layer_transform_for_children, viewport_rect, layer_children) = {
-            match self.layers.get_mut(&layer_id) {
-                Some(layer) => {
-                    layer.update_transform(parent_world_transform, parent_viewport_rect);
-
-                    (layer.world_content_transform.with_source::<ScrollLayerPixel>(),
-                     as_scroll_parent_rect(&layer.combined_local_viewport_rect),
-                     layer.children.clone())
-                }
-                None => return,
-            }
-        };
-
-        for child_layer_id in layer_children {
-            self.update_layer_transform(child_layer_id,
-                                        &layer_transform_for_children,
-                                        &viewport_rect);
-        }
-    }
-
-    pub fn tick_scrolling_bounce_animations(&mut self) {
-        for (_, layer) in &mut self.layers {
-            layer.tick_scrolling_bounce_animation()
-        }
-    }
-
-    pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
-        // TODO(gw): These are all independent - can be run through thread pool if it shows up
-        // in the profile!
-        for (scroll_layer_id, layer) in &mut self.layers {
-            let scrolling_state = match old_states.get(&scroll_layer_id) {
-                Some(old_scrolling_state) => *old_scrolling_state,
-                None => ScrollingState::new(),
-            };
-
-            layer.finalize(&scrolling_state);
-
-            let scroll_root_id = match scroll_layer_id.info {
-                ScrollLayerInfo::Scrollable(_, scroll_root_id) => scroll_root_id,
-                _ => continue,
-            };
-
-
-            let pipeline_id = scroll_layer_id.pipeline_id;
-            if let Some(pending_offset) =
-                self.pending_scroll_offsets.remove(&(pipeline_id, scroll_root_id)) {
-                layer.set_scroll_origin(&pending_offset);
-            }
-        }
-
-    }
-
-    pub fn add_reference_frame(&mut self,
-                               rect: LayerRect,
-                               transform: LayerToScrollTransform,
-                               pipeline_id: PipelineId,
-                               parent_id: ScrollLayerId) -> ScrollLayerId {
-        let reference_frame_id = ScrollLayerId {
-            pipeline_id: pipeline_id,
-            info: ScrollLayerInfo::ReferenceFrame(self.current_reference_frame_id),
-        };
-        self.current_reference_frame_id += 1;
-
-        let layer = Layer::new(&rect, rect.size, &transform, pipeline_id);
-        self.add_layer(layer, reference_frame_id, parent_id);
-        reference_frame_id
-    }
-
-    pub fn add_layer(&mut self, layer: Layer, id: ScrollLayerId, parent_id: ScrollLayerId) {
-        debug_assert!(!self.layers.contains_key(&id));
-        self.layers.insert(id, layer);
-
-        debug_assert!(parent_id != id);
-        self.layers.get_mut(&parent_id).unwrap().add_child(id);
-    }
-
-    pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
-        self.pipelines_to_discard.insert(pipeline_id);
-
-        match self.current_scroll_layer_id {
-            Some(id) if id.pipeline_id == pipeline_id => self.current_scroll_layer_id = None,
-            _ => {}
-        }
-    }
-}
-
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.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 device::{MAX_TEXTURE_SIZE, TextureFilter};
+use device::TextureFilter;
 use fnv::FnvHasher;
 use freelist::{FreeList, FreeListItem, FreeListItemId};
 use internal_types::{TextureUpdate, TextureUpdateOp};
 use internal_types::{CacheTextureId, RenderTargetMode, TextureUpdateList, RectUv};
 use std::cmp::{self, Ordering};
 use std::collections::HashMap;
 use std::collections::hash_map::Entry;
 use std::hash::BuildHasherDefault;
@@ -23,19 +23,16 @@ use webrender_traits::ImageDescriptor;
 const MAX_BYTES_PER_TEXTURE: u32 = 1024 * 1024 * 256;  // 256MB
 
 /// The number of RGBA pixels we're allowed to use for a texture.
 const MAX_RGBA_PIXELS_PER_TEXTURE: u32 = MAX_BYTES_PER_TEXTURE / 4;
 
 /// The desired initial size of each texture, in pixels.
 const INITIAL_TEXTURE_SIZE: u32 = 1024;
 
-/// The desired initial area of each texture, in pixels squared.
-const INITIAL_TEXTURE_AREA: u32 = INITIAL_TEXTURE_SIZE * INITIAL_TEXTURE_SIZE;
-
 /// The square root of the number of RGBA pixels we're allowed to use for a texture, rounded down.
 /// to the next power of two.
 const SQRT_MAX_RGBA_PIXELS_PER_TEXTURE: u32 = 8192;
 
 /// The minimum number of pixels on each side that we require for rects to be classified as
 /// "medium" within the free list.
 const MINIMUM_MEDIUM_RECT_SIZE: u32 = 16;
 
@@ -325,19 +322,18 @@ impl TexturePage {
             if rect.size.width > 0 && rect.size.height > 0 {
                 self.free_list.push(rect);
             }
         }
 
         self.texture_size = new_texture_size
     }
 
-    fn can_grow(&self) -> bool {
-        self.texture_size.width < max_texture_size() ||
-        self.texture_size.height < max_texture_size()
+    fn can_grow(&self, max_size: u32) -> bool {
+        self.texture_size.width < max_size || self.texture_size.height < max_size
     }
 }
 
 /// A binning free list. Binning is important to avoid sifting through lots of small strips when
 /// allocating many texture items.
 struct FreeRectList {
     small: Vec<DeviceUintRect>,
     medium: Vec<DeviceUintRect>,
@@ -536,41 +532,51 @@ impl CacheTextureIdList {
 }
 
 pub struct TextureCache {
     cache_id_list: CacheTextureIdList,
     free_texture_levels: HashMap<ImageFormat, Vec<FreeTextureLevel>, BuildHasherDefault<FnvHasher>>,
     items: FreeList<TextureCacheItem>,
     arena: TextureCacheArena,
     pending_updates: TextureUpdateList,
+    max_texture_size: u32,
 }
 
 #[derive(PartialEq, Eq, Debug)]
 pub enum AllocationKind {
     TexturePage,
     Standalone,
 }
 
 #[derive(Debug)]
 pub struct AllocationResult {
     kind: AllocationKind,
     item: TextureCacheItem,
 }
 
 impl TextureCache {
-    pub fn new() -> TextureCache {
+    pub fn new(mut max_texture_size: u32) -> TextureCache {
+        if max_texture_size * max_texture_size > MAX_RGBA_PIXELS_PER_TEXTURE {
+            max_texture_size = SQRT_MAX_RGBA_PIXELS_PER_TEXTURE;
+        }
+
         TextureCache {
             cache_id_list: CacheTextureIdList::new(),
             free_texture_levels: HashMap::with_hasher(Default::default()),
             items: FreeList::new(),
             pending_updates: TextureUpdateList::new(),
             arena: TextureCacheArena::new(),
+            max_texture_size: max_texture_size,
         }
     }
 
+    pub fn max_texture_size(&self) -> u32 {
+        self.max_texture_size
+    }
+
     pub fn pending_updates(&mut self) -> TextureUpdateList {
         mem::replace(&mut self.pending_updates, TextureUpdateList::new())
     }
 
     // TODO(gw): This API is a bit ugly (having to allocate an ID and
     //           then use it). But it has to be that way for now due to
     //           how the raster_jobs code works.
     pub fn new_item_id(&mut self) -> TextureCacheItemId {
@@ -621,18 +627,18 @@ impl TextureCache {
         let page_list = match format {
             ImageFormat::A8 => &mut self.arena.pages_a8,
             ImageFormat::RGBA8 => &mut self.arena.pages_rgba8,
             ImageFormat::RGB8 => &mut self.arena.pages_rgb8,
             ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(),
         };
 
         // TODO(gw): Handle this sensibly (support failing to render items that can't fit?)
-        assert!(requested_size.width < max_texture_size());
-        assert!(requested_size.height < max_texture_size());
+        assert!(requested_size.width < self.max_texture_size);
+        assert!(requested_size.height < self.max_texture_size);
 
         // Loop until an allocation succeeds, growing or adding new
         // texture pages as required.
         loop {
             let location = page_list.last_mut().and_then(|last_page| {
                 last_page.allocate(&requested_size)
             });
 
@@ -646,21 +652,21 @@ impl TextureCache {
                 *self.items.get_mut(image_id) = cache_item;
 
                 return AllocationResult {
                     item: self.items.get(image_id).clone(),
                     kind: AllocationKind::TexturePage,
                 }
             }
 
-            if !page_list.is_empty() && page_list.last().unwrap().can_grow() {
+            if !page_list.is_empty() && page_list.last().unwrap().can_grow(self.max_texture_size) {
                 let last_page = page_list.last_mut().unwrap();
                 // Grow the texture.
-                let new_width = cmp::min(last_page.texture_size.width * 2, max_texture_size());
-                let new_height = cmp::min(last_page.texture_size.height * 2, max_texture_size());
+                let new_width = cmp::min(last_page.texture_size.width * 2, self.max_texture_size);
+                let new_height = cmp::min(last_page.texture_size.height * 2, self.max_texture_size);
                 let texture_size = DeviceUintSize::new(new_width, new_height);
                 self.pending_updates.push(TextureUpdate {
                     id: last_page.texture_id,
                     op: texture_grow_op(texture_size, format, mode),
                 });
                 last_page.grow(texture_size);
 
                 self.items.for_each_item(|item| {
@@ -668,17 +674,17 @@ impl TextureCache {
                         item.texture_size = texture_size;
                     }
                 });
 
                 continue;
             }
 
             // We need a new page.
-            let texture_size = initial_texture_size();
+            let texture_size = initial_texture_size(self.max_texture_size);
             let free_texture_levels_entry = self.free_texture_levels.entry(format);
             let mut free_texture_levels = match free_texture_levels_entry {
                 Entry::Vacant(entry) => entry.insert(Vec::new()),
                 Entry::Occupied(entry) => entry.into_mut(),
             };
             if free_texture_levels.is_empty() {
                 let texture_id = self.cache_id_list.allocate();
 
@@ -709,16 +715,19 @@ impl TextureCache {
         // TODO(gw): Handle updates to size/format!
         debug_assert_eq!(existing_item.allocated_rect.size.width, descriptor.width);
         debug_assert_eq!(existing_item.allocated_rect.size.height, descriptor.height);
 
         let op = match data {
             ImageData::ExternalHandle(..) | ImageData::ExternalBuffer(..)=> {
                 panic!("Doesn't support Update() for external image.");
             }
+            ImageData::Blob(..) => {
+                panic!("The vector image should have been rasterized into a raw image.");
+            }
             ImageData::Raw(bytes) => {
                 TextureUpdateOp::Update {
                     page_pos_x: existing_item.allocated_rect.origin.x,
                     page_pos_y: existing_item.allocated_rect.origin.y,
                     width: descriptor.width,
                     height: descriptor.height,
                     data: bytes,
                     stride: descriptor.stride,
@@ -734,16 +743,20 @@ impl TextureCache {
         self.pending_updates.push(update_op);
     }
 
     pub fn insert(&mut self,
                   image_id: TextureCacheItemId,
                   descriptor: ImageDescriptor,
                   filter: TextureFilter,
                   data: ImageData) {
+        if let ImageData::Blob(..) = data {
+            panic!("must rasterize the vector image before adding to the cache");
+        }
+
         let width = descriptor.width;
         let height = descriptor.height;
         let format = descriptor.format;
         let stride = descriptor.stride;
 
         let result = self.allocate(image_id,
                                    width,
                                    height,
@@ -751,16 +764,19 @@ impl TextureCache {
                                    filter);
 
         match result.kind {
             AllocationKind::TexturePage => {
                 match data {
                     ImageData::ExternalHandle(..) => {
                         panic!("External handle should not go through texture_cache.");
                     }
+                    ImageData::Blob(..) => {
+                        panic!("The vector image should have been rasterized.");
+                    }
                     ImageData::Raw(bytes) => {
                         let update_op = TextureUpdate {
                             id: result.item.texture_id,
                             op: TextureUpdateOp::Update {
                                 page_pos_x: result.item.allocated_rect.origin.x,
                                 page_pos_y: result.item.allocated_rect.origin.y,
                                 width: result.item.allocated_rect.size.width,
                                 height: result.item.allocated_rect.size.height,
@@ -872,27 +888,12 @@ impl FitsInside for DeviceUintSize {
 
 /// FIXME(pcwalton): Would probably be more efficient as a bit vector.
 #[derive(Clone, Copy)]
 pub struct FreeTextureLevel {
     texture_id: CacheTextureId,
 }
 
 /// Returns the number of pixels on a side we start out with for our texture atlases.
-fn initial_texture_size() -> DeviceUintSize {
-    let max_hardware_texture_size = *MAX_TEXTURE_SIZE as u32;
-    let initial_size = if max_hardware_texture_size * max_hardware_texture_size > INITIAL_TEXTURE_AREA {
-        INITIAL_TEXTURE_SIZE
-    } else {
-        max_hardware_texture_size
-    };
+fn initial_texture_size(max_texture_size: u32) -> DeviceUintSize {
+    let initial_size = cmp::min(max_texture_size, INITIAL_TEXTURE_SIZE);
     DeviceUintSize::new(initial_size, initial_size)
 }
-
-/// Returns the number of pixels on a side we're allowed to use for our texture atlases.
-fn max_texture_size() -> u32 {
-    let max_hardware_texture_size = *MAX_TEXTURE_SIZE as u32;
-    if max_hardware_texture_size * max_hardware_texture_size > MAX_RGBA_PIXELS_PER_TEXTURE {
-        SQRT_MAX_RGBA_PIXELS_PER_TEXTURE
-    } else {
-        max_hardware_texture_size
-    }
-}
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -529,30 +529,31 @@ impl AlphaBatcher {
 
         for task in &mut self.tasks {
             let task_index = render_tasks.get_static_task_index(&task.task_id);
             let mut existing_opaque_batch_index = 0;
 
             for item in &task.alpha_items {
                 let (batch_key, item_bounding_rect) = match item {
                     &AlphaRenderItem::Blend(stacking_context_index, ..) => {
-                        let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
+                        let stacking_context =
+                            &ctx.stacking_context_store[stacking_context_index.0];
                         (AlphaBatchKey::new(AlphaBatchKind::Blend,
                                             AlphaBatchKeyFlags::empty(),
                                             BlendMode::Alpha,
                                             BatchTextures::no_texture()),
-                         &stacking_context.xf_rect.as_ref().unwrap().bounding_rect)
+                         &stacking_context.bounding_rect)
                     }
                     &AlphaRenderItem::HardwareComposite(stacking_context_index, _, composite_op, ..) => {
                         let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
                         (AlphaBatchKey::new(AlphaBatchKind::HardwareComposite,
                                             AlphaBatchKeyFlags::empty(),
                                             composite_op.to_blend_mode(),
                                             BatchTextures::no_texture()),
-                         &stacking_context.xf_rect.as_ref().unwrap().bounding_rect)
+                         &stacking_context.bounding_rect)
                     }
                     &AlphaRenderItem::Composite(stacking_context_index,
                                                 backdrop_id,
                                                 src_id,
                                                 info,
                                                 z) => {
                         // Composites always get added to their own batch.
                         // This is because the result of a composite can affect
@@ -562,21 +563,20 @@ impl AlphaBatcher {
                                                                   task_index,
                                                                   render_tasks.get_task_index(&backdrop_id, child_pass_index),
                                                                   render_tasks.get_static_task_index(&src_id),
                                                                   info,
                                                                   z);
                         alpha_batches.push(batch);
                         continue;
                     }
-                    &AlphaRenderItem::Primitive(stacking_context_index, prim_index, _) => {
-                        let stacking_context =
-                            &ctx.stacking_context_store[stacking_context_index.0];
+                    &AlphaRenderItem::Primitive(clip_scroll_group_index, prim_index, _) => {
+                        let group = &ctx.clip_scroll_group_store[clip_scroll_group_index.0];
                         let prim_metadata = ctx.prim_store.get_metadata(prim_index);
-                        let transform_kind = stacking_context.xf_rect.as_ref().unwrap().kind;
+                        let transform_kind = group.xf_rect.as_ref().unwrap().kind;
                         let needs_clipping = prim_metadata.clip_task.is_some();
                         let needs_blending = transform_kind == TransformedRectKind::Complex ||
                                              !prim_metadata.is_opaque ||
                                              needs_clipping;
                         let blend_mode = ctx.prim_store.get_blend_mode(needs_blending, prim_metadata);
                         let needs_clipping_flag = if needs_clipping {
                             NEEDS_CLIPPING
                         } else {
@@ -611,21 +611,17 @@ impl AlphaBatcher {
                     }
 
                     // check for intersections
                     for item in &batch.items {
                         let intersects = match *item {
                             PrimitiveBatchItem::StackingContext(stacking_context_index) => {
                                 let stacking_context =
                                     &ctx.stacking_context_store[stacking_context_index.0];
-                                stacking_context.xf_rect
-                                                .as_ref()
-                                                .unwrap()
-                                                .bounding_rect
-                                                .intersects(item_bounding_rect)
+                                stacking_context.bounding_rect.intersects(item_bounding_rect)
                             }
                             PrimitiveBatchItem::Primitive(prim_index) => {
                                 let bounding_rect = &ctx.prim_store.cpu_bounding_rects[prim_index.0];
                                 bounding_rect.as_ref().unwrap().intersects(item_bounding_rect)
                             }
                         };
 
                         if intersects {
@@ -660,45 +656,46 @@ impl AlphaBatcher {
                         ctx.prim_store.add_blend_to_batch(stacking_context_index,
                                                           batch,
                                                           task_index,
                                                           render_tasks.get_static_task_index(&src_id),
                                                           info,
                                                           z);
                     }
                     &AlphaRenderItem::HardwareComposite(stacking_context_index, src_id, _, z) => {
-                        ctx.prim_store.add_hardware_composite_to_batch(stacking_context_index,
-                                                                       batch,
-                                                                       task_index,
-                                                                       render_tasks.get_static_task_index(&src_id),
-                                                                       z);
+                        ctx.prim_store.add_hardware_composite_to_batch(
+                            stacking_context_index,
+                            batch,
+                            task_index,
+                            render_tasks.get_static_task_index(&src_id),
+                            z);
                     }
-                    &AlphaRenderItem::Primitive(stacking_context_index, prim_index, z) => {
-                        let stacking_context =
-                            &ctx.stacking_context_store[stacking_context_index.0];
+                    &AlphaRenderItem::Primitive(clip_scroll_group_index, prim_index, z) => {
+                        let packed_layer = ctx.clip_scroll_group_store[clip_scroll_group_index.0]
+                                              .packed_layer_index;
                         ctx.prim_store.add_prim_to_batch(prim_index,
                                                          batch,
-                                                         stacking_context.packed_layer_index,
+                                                         packed_layer,
                                                          task_index,
                                                          render_tasks,
                                                          child_pass_index,
                                                          z);
                     }
                 }
             }
 
             for item in task.opaque_items.iter().rev() {
                 let batch_key = match item {
                     &AlphaRenderItem::Composite(..) => unreachable!(),
                     &AlphaRenderItem::Blend(..) => unreachable!(),
                     &AlphaRenderItem::HardwareComposite(..) => unreachable!(),
-                    &AlphaRenderItem::Primitive(stacking_context_index, prim_index, _) => {
-                        let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
+                    &AlphaRenderItem::Primitive(clip_scroll_group_index, prim_index, _) => {
+                        let group = &ctx.clip_scroll_group_store[clip_scroll_group_index.0];
+                        let transform_kind = group.xf_rect.as_ref().unwrap().kind;
                         let prim_metadata = ctx.prim_store.get_metadata(prim_index);
-                        let transform_kind = stacking_context.xf_rect.as_ref().unwrap().kind;
                         let needs_clipping = prim_metadata.clip_task.is_some();
                         let needs_blending = transform_kind == TransformedRectKind::Complex ||
                                              !prim_metadata.is_opaque ||
                                              needs_clipping;
                         let blend_mode = ctx.prim_store.get_blend_mode(needs_blending, prim_metadata);
                         let needs_clipping_flag = if needs_clipping {
                             NEEDS_CLIPPING
                         } else {
@@ -740,22 +737,23 @@ impl AlphaBatcher {
                     opaque_batches.push(new_batch)
                 }
 
                 let batch = &mut opaque_batches[existing_opaque_batch_index];
                 match item {
                     &AlphaRenderItem::Composite(..) => unreachable!(),
                     &AlphaRenderItem::Blend(..) => unreachable!(),
                     &AlphaRenderItem::HardwareComposite(..) => unreachable!(),
-                    &AlphaRenderItem::Primitive(stacking_context_index, prim_index, z) => {
-                        let stacking_context =
-                            &ctx.stacking_context_store[stacking_context_index.0];
+                    &AlphaRenderItem::Primitive(clip_scroll_group_index, prim_index, z) => {
+                        let packed_layer_index =
+                            ctx.clip_scroll_group_store[clip_scroll_group_index.0]
+                               .packed_layer_index;
                         ctx.prim_store.add_prim_to_batch(prim_index,
                                                          batch,
-                                                         stacking_context.packed_layer_index,
+                                                         packed_layer_index,
                                                          task_index,
                                                          render_tasks,
                                                          child_pass_index,
                                                          z);
                     }
                 }
             }
         }
@@ -843,16 +841,17 @@ impl ClipBatcher {
                 })
             }
         }
     }
 }
 
 pub struct RenderTargetContext<'a> {
     pub stacking_context_store: &'a [StackingContext],
+    pub clip_scroll_group_store: &'a [ClipScrollGroup],
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'a ResourceCache,
 }
 
 /// A render target represents a number of rendering operations on a surface.
 pub struct RenderTarget {
     pub alpha_batcher: AlphaBatcher,
     pub clip_batcher: ClipBatcher,
@@ -1294,24 +1293,73 @@ impl PrimitiveBatch {
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 pub struct PackedLayerIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 pub struct StackingContextIndex(pub usize);
 
+#[derive(Debug)]
 pub struct StackingContext {
     pub pipeline_id: PipelineId,
     pub local_transform: LayerToScrollTransform,
     pub local_rect: LayerRect,
+    pub bounding_rect: DeviceIntRect,
+    pub composite_ops: CompositeOps,
+    pub clip_scroll_groups: Vec<ClipScrollGroupIndex>,
+    pub is_visible: bool,
+}
+
+impl StackingContext {
+    pub fn new(pipeline_id: PipelineId,
+               local_transform: LayerToScrollTransform,
+               local_rect: LayerRect,
+               composite_ops: CompositeOps,
+               clip_scroll_group_index: ClipScrollGroupIndex)
+               -> StackingContext {
+        StackingContext {
+            pipeline_id: pipeline_id,
+            local_transform: local_transform,
+            local_rect: local_rect,
+            bounding_rect: DeviceIntRect::zero(),
+            composite_ops: composite_ops,
+            clip_scroll_groups: vec![clip_scroll_group_index],
+            is_visible: false,
+        }
+    }
+
+    pub fn clip_scroll_group(&self) -> ClipScrollGroupIndex {
+        // Currently there is only one scrolled stacking context per context,
+        // but eventually this will be selected from the vector based on the
+        // scroll layer of this primitive.
+        self.clip_scroll_groups[0]
+    }
+
+    pub fn can_contribute_to_scene(&self) -> bool {
+        !self.composite_ops.will_make_invisible()
+    }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
+pub struct ClipScrollGroupIndex(pub usize);
+
+#[derive(Debug)]
+pub struct ClipScrollGroup {
+    pub stacking_context_index: StackingContextIndex,
     pub scroll_layer_id: ScrollLayerId,
+    pub packed_layer_index: PackedLayerIndex,
+    pub pipeline_id: PipelineId,
     pub xf_rect: Option<TransformedRect>,
-    pub composite_ops: CompositeOps,
-    pub packed_layer_index: PackedLayerIndex,
+}
+
+impl ClipScrollGroup {
+    pub fn is_visible(&self) -> bool {
+        self.xf_rect.is_some()
+    }
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 pub struct ScrollLayerIndex(pub usize);
 
 pub struct ScrollLayer {
     pub scroll_layer_id: ScrollLayerId,
     pub parent_index: ScrollLayerIndex,
@@ -1381,26 +1429,16 @@ impl CompositeOps {
                 &LowLevelFilterOp::Opacity(Au(0)) => return true,
                 _ => {}
             }
         }
         false
     }
 }
 
-impl StackingContext {
-    pub fn is_visible(&self) -> bool {
-        self.xf_rect.is_some()
-    }
-
-    pub fn can_contribute_to_scene(&self) -> bool {
-        !self.composite_ops.will_make_invisible()
-    }
-}
-
 /// A rendering-oriented representation of frame::Frame built by the render backend
 /// and presented to the renderer.
 pub struct Frame {
     pub viewport_size: LayerSize,
     pub background_color: Option<ColorF>,
     pub device_pixel_ratio: f32,
     pub cache_size: DeviceUintSize,
     pub passes: Vec<RenderPass>,
--- a/gfx/webrender_traits/Cargo.toml
+++ b/gfx/webrender_traits/Cargo.toml
@@ -1,35 +1,35 @@
 [package]
 name = "webrender_traits"
-version = "0.14.0"
+version = "0.19.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 build = "build.rs"
 
 [features]
 default = ["codegen"]
 nightly = ["euclid/unstable", "serde/unstable"]
 codegen = ["serde_codegen", "serde_codegen/with-syntex"]
 ipc = ["ipc-channel"]
 
 [dependencies]
-app_units = "0.3.0"
+app_units = "0.4"
 byteorder = "1.0"
-euclid = "0.10.3"
+euclid = "0.11"
 gleam = "0.2"
 heapsize = "0.3.6"
-offscreen_gl_context = {version = "0.5.0", features = ["serde_serialization"]}
-serde = "0.8"
-serde_derive = {version = "0.8", optional = true}
-ipc-channel = { version = "0.5.0", optional = true }
+ipc-channel = {version = "0.7", optional = true}
+offscreen_gl_context = {version = "0.6", features = ["serde"]}
+serde = "0.9"
+serde_derive = {version = "0.9", optional = true}
 
 [target.'cfg(target_os = "macos")'.dependencies]
-core-graphics = "0.6"
+core-graphics = "0.7"
 
 [target.'cfg(target_os = "windows")'.dependencies]
-dwrote = "0.1.1"
+servo-dwrote = "0.2"
 
 [build-dependencies.serde_codegen]
-version = "0.8"
+version = "0.9"
 default_features = false
 optional = true
--- a/gfx/webrender_traits/src/api.rs
+++ b/gfx/webrender_traits/src/api.rs
@@ -49,59 +49,57 @@ pub struct RenderApi {
     pub next_id: Cell<ResourceId>,
 }
 
 impl RenderApi {
     pub fn clone_sender(&self) -> RenderApiSender {
         RenderApiSender::new(self.api_sender.clone(), self.payload_sender.clone())
     }
 
-    pub fn add_raw_font(&self, bytes: Vec<u8>) -> FontKey {
+    pub fn generate_font_key(&self) -> FontKey {
         let new_id = self.next_unique_id();
-        let key = FontKey::new(new_id.0, new_id.1);
+        FontKey::new(new_id.0, new_id.1)
+    }
+
+    pub fn add_raw_font(&self, key: FontKey, bytes: Vec<u8>) {
         let msg = ApiMsg::AddRawFont(key, bytes);
         self.api_sender.send(msg).unwrap();
-        key
     }
 
-    pub fn add_native_font(&self, native_font_handle: NativeFontHandle) -> FontKey {
-        let new_id = self.next_unique_id();
-        let key = FontKey::new(new_id.0, new_id.1);
+    pub fn add_native_font(&self, key: FontKey, native_font_handle: NativeFontHandle) {
         let msg = ApiMsg::AddNativeFont(key, native_font_handle);
         self.api_sender.send(msg).unwrap();
-        key
     }
 
     /// Gets the dimensions for the supplied glyph keys
     ///
     /// Note: Internally, the internal texture cache doesn't store
     /// 'empty' textures (height or width = 0)
     /// This means that glyph dimensions e.g. for spaces (' ') will mostly be None.
     pub fn get_glyph_dimensions(&self, glyph_keys: Vec<GlyphKey>)
                                 -> Vec<Option<GlyphDimensions>> {
         let (tx, rx) = channel::msg_channel().unwrap();
         let msg = ApiMsg::GetGlyphDimensions(glyph_keys, tx);
         self.api_sender.send(msg).unwrap();
         rx.recv().unwrap()
     }
 
     /// Creates an `ImageKey`.
-    pub fn alloc_image(&self) -> ImageKey {
+    pub fn generate_image_key(&self) -> ImageKey {
         let new_id = self.next_unique_id();
         ImageKey::new(new_id.0, new_id.1)
     }
 
-    /// Adds an image and returns the corresponding `ImageKey`.
+    /// Adds an image identified by the `ImageKey`.
     pub fn add_image(&self,
+                     key: ImageKey,
                      descriptor: ImageDescriptor,
-                     data: ImageData) -> ImageKey {
-        let key = self.alloc_image();
+                     data: ImageData) {
         let msg = ApiMsg::AddImage(key, descriptor, data);
         self.api_sender.send(msg).unwrap();
-        key
     }
 
     /// Updates a specific image.
     ///
     /// Currently doesn't support changing dimensions or format by updating.
     // TODO: Support changing dimensions (and format) during image update?
     pub fn update_image(&self,
                         key: ImageKey,
--- a/gfx/webrender_traits/src/channel_mpsc.rs
+++ b/gfx/webrender_traits/src/channel_mpsc.rs
@@ -62,32 +62,32 @@ pub fn msg_channel<T>() -> Result<(MsgSe
 /// recording tool. The recording tool only outputs messages
 /// that don't contain Senders or Receivers, so in theory
 /// these should never be called in the in-process config.
 /// If they are called, there may be a bug in the messages
 /// that the replay tool is writing.
 ///
 
 impl<T> Serialize for MsgReceiver<T> {
-    fn serialize<S: Serializer>(&self, _: &mut S) -> Result<(), S::Error> {
+    fn serialize<S: Serializer>(&self, _: S) -> Result<S::Ok, S::Error> {
         unreachable!();
     }
 }
 
 impl<T> Serialize for MsgSender<T> {
-    fn serialize<S: Serializer>(&self, _: &mut S) -> Result<(), S::Error> {
+    fn serialize<S: Serializer>(&self, _: S) -> Result<S::Ok, S::Error> {
         unreachable!();
     }
 }
 
 impl<T> Deserialize for MsgReceiver<T> {
-    fn deserialize<D>(_: &mut D) -> Result<MsgReceiver<T>, D::Error>
+    fn deserialize<D>(_: D) -> Result<MsgReceiver<T>, D::Error>
                       where D: Deserializer {
         unreachable!();
     }
 }
 
 impl<T> Deserialize for MsgSender<T> {
-    fn deserialize<D>(_: &mut D) -> Result<MsgSender<T>, D::Error>
+    fn deserialize<D>(_: D) -> Result<MsgSender<T>, D::Error>
                       where D: Deserializer {
         unreachable!();
     }
 }
--- a/gfx/webrender_traits/src/display_item.rs
+++ b/gfx/webrender_traits/src/display_item.rs
@@ -1,40 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use display_list::AuxiliaryListsBuilder;
-use {BorderRadius, BorderDisplayItem, ClipRegion, ColorF, ComplexClipRegion};
+use {BorderRadius, ClipRegion, ColorF, ComplexClipRegion};
 use {FontKey, ImageKey, PipelineId, ScrollLayerId, ScrollLayerInfo, ServoScrollRootId};
 use {ImageMask, ItemRange};
 use {LayoutSize, LayoutPoint, LayoutRect};
 
-impl BorderDisplayItem {
-    pub fn top_left_inner_radius(&self) -> LayoutSize {
-        LayoutSize::new((self.radius.top_left.width - self.left.width).max(0.0),
-                     (self.radius.top_left.height - self.top.width).max(0.0))
-    }
-
-    pub fn top_right_inner_radius(&self) -> LayoutSize {
-        LayoutSize::new((self.radius.top_right.width - self.right.width).max(0.0),
-                     (self.radius.top_right.height - self.top.width).max(0.0))
-    }
-
-    pub fn bottom_left_inner_radius(&self) -> LayoutSize {
-        LayoutSize::new((self.radius.bottom_left.width - self.left.width).max(0.0),
-                     (self.radius.bottom_left.height - self.bottom.width).max(0.0))
-    }
-
-    pub fn bottom_right_inner_radius(&self) -> LayoutSize {
-        LayoutSize::new((self.radius.bottom_right.width - self.right.width).max(0.0),
-                     (self.radius.bottom_right.height - self.bottom.width).max(0.0))
-    }
-}
-
 impl BorderRadius {
     pub fn zero() -> BorderRadius {
         BorderRadius {
             top_left: LayoutSize::new(0.0, 0.0),
             top_right: LayoutSize::new(0.0, 0.0),
             bottom_left: LayoutSize::new(0.0, 0.0),
             bottom_right: LayoutSize::new(0.0, 0.0),
         }
@@ -44,30 +22,46 @@ impl BorderRadius {
         BorderRadius {
             top_left: LayoutSize::new(radius, radius),
             top_right: LayoutSize::new(radius, radius),
             bottom_left: LayoutSize::new(radius, radius),
             bottom_right: LayoutSize::new(radius, radius),
         }
     }
 
-    pub fn is_uniform(&self) -> Option<LayoutSize> {
+    pub fn uniform_size(radius: LayoutSize) -> BorderRadius {
+        BorderRadius {
+            top_left: radius,
+            top_right: radius,
+            bottom_left: radius,
+            bottom_right: radius,
+        }
+    }
+
+    pub fn is_uniform(&self) -> Option<f32> {
+        match self.is_uniform_size() {
+            Some(radius) if radius.width == radius.height => Some(radius.width),
+            _ => None
+        }
+    }
+
+    pub fn is_uniform_size(&self) -> Option<LayoutSize> {
         let uniform_radius = self.top_left;
         if self.top_right == uniform_radius &&
            self.bottom_left == uniform_radius &&
            self.bottom_right == uniform_radius {
             Some(uniform_radius)
         } else {
             None
         }
     }
 
     pub fn is_zero(&self) -> bool {
         if let Some(radius) = self.is_uniform() {
-            radius.width == 0.0 && radius.height == 0.0
+            radius == 0.0
         } else {
             false
         }
     }
 }
 
 impl ClipRegion {
     pub fn new(rect: &LayoutRect,
--- a/gfx/webrender_traits/src/display_list.rs
+++ b/gfx/webrender_traits/src/display_list.rs
@@ -1,26 +1,26 @@
 /* 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 app_units::Au;
 use std::mem;
 use std::slice;
-use {AuxiliaryLists, AuxiliaryListsDescriptor, BorderDisplayItem, BorderRadius};
-use {BorderSide, BoxShadowClipMode, BoxShadowDisplayItem, BuiltDisplayList};
+use {AuxiliaryLists, AuxiliaryListsDescriptor, BorderDisplayItem};
+use {BoxShadowClipMode, BoxShadowDisplayItem, BuiltDisplayList};
 use {BuiltDisplayListDescriptor, ClipRegion, ComplexClipRegion, ColorF};
 use {DisplayItem, DisplayListMode, ExtendMode, FilterOp, YuvColorSpace};
 use {FontKey, GlyphInstance, GradientDisplayItem, RadialGradientDisplayItem, GradientStop, IframeDisplayItem};
 use {ImageDisplayItem, ImageKey, ImageMask, ImageRendering, ItemRange, MixBlendMode, PipelineId};
 use {PushScrollLayerItem, PushStackingContextDisplayItem, RectangleDisplayItem, ScrollLayerId};
 use {ScrollPolicy, ServoScrollRootId, SpecificDisplayItem, StackingContext, TextDisplayItem};
 use {WebGLContextId, WebGLDisplayItem, YuvImageDisplayItem};
 use {LayoutTransform, LayoutPoint, LayoutRect, LayoutSize};
-use {GlyphOptions, PropertyBinding};
+use {BorderDetails, BorderWidths, GlyphOptions, PropertyBinding};
 
 impl BuiltDisplayListDescriptor {
     pub fn size(&self) -> usize {
         self.display_list_items_size + self.display_items_size
     }
 }
 
 impl BuiltDisplayList {
@@ -181,27 +181,21 @@ impl DisplayListBuilder {
 
             self.list.push(display_item);
         }
     }
 
     pub fn push_border(&mut self,
                        rect: LayoutRect,
                        clip: ClipRegion,
-                       left: BorderSide,
-                       top: BorderSide,
-                       right: BorderSide,
-                       bottom: BorderSide,
-                       radius: BorderRadius) {
+                       widths: BorderWidths,
+                       details: BorderDetails) {
         let item = BorderDisplayItem {
-            left: left,
-            top: top,
-            right: right,
-            bottom: bottom,
-            radius: radius,
+            details: details,
+            widths: widths,
         };
 
         let display_item = DisplayItem {
             item: SpecificDisplayItem::Border(item),
             rect: rect,
             clip: clip,
         };
 
--- a/gfx/webrender_traits/src/types.rs
+++ b/gfx/webrender_traits/src/types.rs
@@ -1,17 +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/. */
 
 // Every serialisable type is defined in this file to only codegen one file
 // for the serde implementations.
 
 use app_units::Au;
-use euclid::Point2D;
+use euclid::{Point2D, SideOffsets2D};
 use channel::{PayloadSender, MsgSender};
 #[cfg(feature = "nightly")]
 use core::nonzero::NonZero;
 use offscreen_gl_context::{GLContextAttributes, GLLimits};
 use std::fmt;
 use std::marker::PhantomData;
 use std::sync::Arc;
 
@@ -170,36 +170,79 @@ pub struct AuxiliaryLists {
 pub struct AuxiliaryListsDescriptor {
     gradient_stops_size: usize,
     complex_clip_regions_size: usize,
     filters_size: usize,
     glyph_instances_size: usize,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
-pub struct BorderDisplayItem {
+pub struct NormalBorder {
     pub left: BorderSide,
     pub right: BorderSide,
     pub top: BorderSide,
     pub bottom: BorderSide,
     pub radius: BorderRadius,
 }
 
+#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
+pub enum RepeatMode {
+    Stretch,
+    Repeat,
+    Round,
+    Space,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct NinePatchDescriptor {
+    pub width: u32,
+    pub height: u32,
+    pub slice: SideOffsets2D<u32>,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct ImageBorder {
+    pub image_key: ImageKey,
+    pub patch: NinePatchDescriptor,
+    pub outset: SideOffsets2D<f32>,
+    pub repeat_horizontal: RepeatMode,
+    pub repeat_vertical: RepeatMode,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub enum BorderDetails {
+    Normal(NormalBorder),
+    Image(ImageBorder),
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct BorderDisplayItem {
+    pub widths: BorderWidths,
+    pub details: BorderDetails,
+}
+
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct BorderRadius {
     pub top_left: LayoutSize,
     pub top_right: LayoutSize,
     pub bottom_left: LayoutSize,
     pub bottom_right: LayoutSize,
 }
 
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct BorderWidths {
+    pub left: f32,
+    pub top: f32,
+    pub right: f32,
+    pub bottom: f32,
+}
+
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct BorderSide {
-    pub width: f32,
     pub color: ColorF,
     pub style: BorderStyle,
 }
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum BorderStyle {
     None    = 0,
@@ -600,31 +643,74 @@ pub enum YuvColorSpace {
 }
 
 /// An arbitrary identifier for an external image provided by the
 /// application. It must be a unique identifier for each external
 /// image.
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
 pub struct ExternalImageId(pub u64);
 
+pub trait BlobImageRenderer: Send {
+    fn request_blob_image(&mut self,
+                            key: ImageKey,
+                            data: Arc<BlobImageData>,
+                            descriptor: &BlobImageDescriptor);
+    fn resolve_blob_image(&mut self, key: ImageKey) -> BlobImageResult;
+}
+
+pub type BlobImageData = Vec<u8>;
+
+#[derive(Copy, Clone, Debug)]
+pub struct BlobImageDescriptor {
+    pub width: u32,
+    pub height: u32,
+    pub format: ImageFormat,
+    pub scale_factor: f32,
+}
+
+pub struct RasterizedBlobImage {
+    pub width: u32,
+    pub height: u32,
+    pub data: Vec<u8>,
+}
+
+#[derive(Clone, Debug)]
+pub enum BlobImageError {
+    Oom,
+    InvalidKey,
+    InvalidData,
+    Other(String),
+}
+
+pub type BlobImageResult = Result<RasterizedBlobImage, BlobImageError>;
+
 #[derive(Clone, Serialize, Deserialize)]
 pub enum ImageData {
     Raw(Arc<Vec<u8>>),
+    Blob(Arc<BlobImageData>),
     ExternalHandle(ExternalImageId),
     ExternalBuffer(ExternalImageId),
 }
 
 impl ImageData {
     pub fn new(bytes: Vec<u8>) -> ImageData {
         ImageData::Raw(Arc::new(bytes))
     }
 
     pub fn new_shared(bytes: Arc<Vec<u8>>) -> ImageData {
         ImageData::Raw(bytes)
     }
+
+    pub fn new_blob_image(commands: Vec<u8>) -> ImageData {
+        ImageData::Blob(Arc::new(commands))
+    }
+
+    pub fn new_shared_blob_image(commands: Arc<Vec<u8>>) -> ImageData {
+        ImageData::Blob(commands)
+    }
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ImageKey(pub u32, pub u32);
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
@@ -982,30 +1068,30 @@ macro_rules! define_resource_id_struct {
     };
 }
 
 macro_rules! define_resource_id {
     ($name:ident) => {
         define_resource_id_struct!($name);
 
         impl ::serde::Deserialize for $name {
-            fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
+            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
                 where D: ::serde::Deserializer
             {
                 let id = try!(u32::deserialize(deserializer));
                 if id == 0 {
-                    Err(::serde::Error::invalid_value("expected a non-zero value"))
+                    Err(::serde::de::Error::custom("expected a non-zero value"))
                 } else {
                     Ok(unsafe { $name::new(id) })
                 }
             }
         }
 
         impl ::serde::Serialize for $name {
-            fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
+            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
                 where S: ::serde::Serializer
             {
                 self.get().serialize(serializer)
             }
         }
 
         impl ::std::fmt::Debug for $name {
             fn fmt(&self, fmt: &mut ::std::fmt::Formatter)