Bug 1447998 - Update webrender to commit 22493328352ba432a7cd89491d81bfaa19bc1bce. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 28 Mar 2018 09:11:21 -0400
changeset 773788 66f64cdc15147f019b2ccdaaca53f899bf0a7fb6
parent 773564 56d6db4ad38c869d0bbc2aea449a4a382f109163
child 773789 cb665f53ba56fced7a4053de9447ee55d5522d9d
push id104304
push userkgupta@mozilla.com
push dateWed, 28 Mar 2018 13:14:35 +0000
reviewersjrmuizel
bugs1447998
milestone61.0a1
Bug 1447998 - Update webrender to commit 22493328352ba432a7cd89491d81bfaa19bc1bce. r?jrmuizel MozReview-Commit-ID: 4krUpVqWkr
gfx/webrender/res/brush_line.glsl
gfx/webrender/res/cs_clip_box_shadow.glsl
gfx/webrender/res/cs_clip_line.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/box_shadow.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/gpu_types.rs
gfx/webrender/src/hit_test.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/shade.rs
gfx/webrender/src/tiling.rs
gfx/webrender/tests/angle_shader_validation.rs
gfx/webrender_api/Cargo.toml
gfx/webrender_bindings/revision.txt
gfx/wrench/src/main.rs
gfx/wrench/src/wrench.rs
gfx/wrench/src/yaml_frame_reader.rs
gfx/wrench/src/yaml_frame_writer.rs
deleted file mode 100644
--- a/gfx/webrender/res/brush_line.glsl
+++ /dev/null
@@ -1,205 +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/. */
-
-#define VECS_PER_SPECIFIC_BRUSH 2
-
-#include shared,prim_shared,brush
-
-varying vec2 vLocalPos;
-
-flat varying vec4 vColor;
-flat varying int vStyle;
-flat varying float vAxisSelect;
-flat varying vec4 vParams;
-flat varying vec2 vLocalOrigin;
-
-#ifdef WR_VERTEX_SHADER
-
-#define LINE_ORIENTATION_VERTICAL       0
-#define LINE_ORIENTATION_HORIZONTAL     1
-
-struct Line {
-    vec4 color;
-    float wavyLineThickness;
-    float style;
-    float orientation;
-};
-
-Line fetch_line(int address) {
-    vec4 data[2] = fetch_from_resource_cache_2(address);
-    return Line(data[0], data[1].x, data[1].y, data[1].z);
-}
-
-void brush_vs(
-    VertexInfo vi,
-    int prim_address,
-    RectWithSize local_rect,
-    ivec3 user_data,
-    PictureTask pic_task
-) {
-    vLocalPos = vi.local_pos;
-
-    // Note: `line` name is reserved in HLSL
-    Line line_prim = fetch_line(prim_address);
-
-    switch (int(abs(pic_task.pic_kind_and_raster_mode))) {
-        case PIC_TYPE_TEXT_SHADOW:
-            vColor = pic_task.color;
-            break;
-        default:
-            vColor = line_prim.color;
-            break;
-    }
-
-    vec2 pos, size;
-
-    switch (int(line_prim.orientation)) {
-        case LINE_ORIENTATION_HORIZONTAL:
-            vAxisSelect = 0.0;
-            pos = local_rect.p0;
-            size = local_rect.size;
-            break;
-        case LINE_ORIENTATION_VERTICAL:
-            vAxisSelect = 1.0;
-            pos = local_rect.p0.yx;
-            size = local_rect.size.yx;
-            break;
-        default:
-            vAxisSelect = 0.0;
-            pos = size = vec2(0.0);
-    }
-
-    vLocalOrigin = pos;
-    vStyle = int(line_prim.style);
-
-    switch (vStyle) {
-        case LINE_STYLE_SOLID: {
-            break;
-        }
-        case LINE_STYLE_DASHED: {
-            float dash_length = size.y * 3.0;
-            vParams = vec4(2.0 * dash_length, // period
-                           dash_length,       // dash length
-                           0.0,
-                           0.0);
-            break;
-        }
-        case LINE_STYLE_DOTTED: {
-            float diameter = size.y;
-            float period = diameter * 2.0;
-            float center_line = pos.y + 0.5 * size.y;
-            float max_x = floor(size.x / period) * period;
-            vParams = vec4(period,
-                           diameter / 2.0, // radius
-                           center_line,
-                           max_x);
-            break;
-        }
-        case LINE_STYLE_WAVY: {
-            // This logic copied from gecko to get the same results
-            float line_thickness = max(line_prim.wavyLineThickness, 1.0);
-            // Difference in height between peaks and troughs
-            // (and since slopes are 45 degrees, the length of each slope)
-            float slope_length = size.y - line_thickness;
-            // Length of flat runs
-            float flat_length = max((line_thickness - 1.0) * 2.0, 1.0);
-
-            vParams = vec4(line_thickness / 2.0,
-                           slope_length,
-                           flat_length,
-                           size.y);
-            break;
-        }
-        default:
-            vParams = vec4(0.0);
-    }
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-
-#define MAGIC_WAVY_LINE_AA_SNAP         0.5
-
-vec4 brush_fs() {
-    // Find the appropriate distance to apply the step over.
-    vec2 local_pos = vLocalPos;
-    float aa_range = compute_aa_range(local_pos);
-    float alpha = 1.0;
-
-    // Select the x/y coord, depending on which axis this edge is.
-    vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect);
-
-    switch (vStyle) {
-        case LINE_STYLE_SOLID: {
-            break;
-        }
-        case LINE_STYLE_DASHED: {
-            // Get the main-axis position relative to closest dot or dash.
-            float x = mod(pos.x - vLocalOrigin.x, vParams.x);
-
-            // Calculate dash alpha (on/off) based on dash length
-            alpha = step(x, vParams.y);
-            break;
-        }
-        case LINE_STYLE_DOTTED: {
-            // Get the main-axis position relative to closest dot or dash.
-            float x = mod(pos.x - vLocalOrigin.x, vParams.x);
-
-            // Get the dot alpha
-            vec2 dot_relative_pos = vec2(x, pos.y) - vParams.yz;
-            float dot_distance = length(dot_relative_pos) - vParams.y;
-            alpha = distance_aa(aa_range, dot_distance);
-            // Clip off partial dots
-            alpha *= step(pos.x - vLocalOrigin.x, vParams.w);
-            break;
-        }
-        case LINE_STYLE_WAVY: {
-            vec2 normalized_local_pos = pos - vLocalOrigin.xy;
-
-            float half_line_thickness = vParams.x;
-            float slope_length = vParams.y;
-            float flat_length = vParams.z;
-            float vertical_bounds = vParams.w;
-            // Our pattern is just two slopes and two flats
-            float half_period = slope_length + flat_length;
-
-            float mid_height = vertical_bounds / 2.0;
-            float peak_offset = mid_height - half_line_thickness;
-            // Flip the wave every half period
-            float flip = -2.0 * (step(mod(normalized_local_pos.x, 2.0 * half_period), half_period) - 0.5);
-            // float flip = -1.0;
-            peak_offset *= flip;
-            float peak_height = mid_height + peak_offset;
-
-            // Convert pos to a local position within one half period
-            normalized_local_pos.x = mod(normalized_local_pos.x, half_period);
-
-            // Compute signed distance to the 3 lines that make up an arc
-            float dist1 = distance_to_line(vec2(0.0, peak_height),
-                                           vec2(1.0, -flip),
-                                           normalized_local_pos);
-            float dist2 = distance_to_line(vec2(0.0, peak_height),
-                                           vec2(0, -flip),
-                                           normalized_local_pos);
-            float dist3 = distance_to_line(vec2(flat_length, peak_height),
-                                           vec2(-1.0, -flip),
-                                           normalized_local_pos);
-            float dist = abs(max(max(dist1, dist2), dist3));
-
-            // Apply AA based on the thickness of the wave
-            alpha = distance_aa(aa_range, dist - half_line_thickness);
-
-            // Disable AA for thin lines
-            if (half_line_thickness <= 1.0) {
-                alpha = 1.0 - step(alpha, MAGIC_WAVY_LINE_AA_SNAP);
-            }
-
-            break;
-        }
-        default: break;
-    }
-
-    return vColor * alpha;
-}
-#endif
--- a/gfx/webrender/res/cs_clip_box_shadow.glsl
+++ b/gfx/webrender/res/cs_clip_box_shadow.glsl
@@ -6,34 +6,40 @@
 
 varying vec3 vPos;
 varying vec2 vUv;
 flat varying vec4 vUvBounds;
 flat varying float vLayer;
 flat varying vec4 vEdge;
 flat varying vec4 vUvBounds_NoClamp;
 flat varying float vClipMode;
-flat varying int vStretchMode;
 
 #define MODE_STRETCH        0
 #define MODE_SIMPLE         1
 
 #ifdef WR_VERTEX_SHADER
 
 struct BoxShadowData {
     vec2 src_rect_size;
     float clip_mode;
-    int stretch_mode;
+    int stretch_mode_x;
+    int stretch_mode_y;
     RectWithSize dest_rect;
 };
 
 BoxShadowData fetch_data(ivec2 address) {
-    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
-    RectWithSize dest_rect = RectWithSize(data[1].xy, data[1].zw);
-    BoxShadowData bs_data = BoxShadowData(data[0].xy, data[0].z, int(data[0].w), dest_rect);
+    vec4 data[3] = fetch_from_resource_cache_3_direct(address);
+    RectWithSize dest_rect = RectWithSize(data[2].xy, data[2].zw);
+    BoxShadowData bs_data = BoxShadowData(
+        data[0].xy,
+        data[0].z,
+        int(data[1].x),
+        int(data[1].y),
+        dest_rect
+    );
     return bs_data;
 }
 
 void main(void) {
     ClipMaskInstance cmi = fetch_clip_item();
     ClipArea area = fetch_clip_area(cmi.render_task_address);
     ClipScrollNode scroll_node = fetch_clip_scroll_node(cmi.scroll_node_id);
     BoxShadowData bs_data = fetch_data(cmi.clip_data_address);
@@ -41,63 +47,65 @@ void main(void) {
 
     ClipVertexInfo vi = write_clip_tile_vertex(bs_data.dest_rect,
                                                scroll_node,
                                                area);
 
     vLayer = res.layer;
     vPos = vi.local_pos;
     vClipMode = bs_data.clip_mode;
-    vStretchMode = bs_data.stretch_mode;
 
     vec2 uv0 = res.uv_rect.p0;
     vec2 uv1 = res.uv_rect.p1;
 
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vec2 local_pos = vPos.xy / vPos.z;
 
-    switch (bs_data.stretch_mode) {
+    switch (bs_data.stretch_mode_x) {
         case MODE_STRETCH: {
-            vEdge.xy = vec2(0.5);
-            vEdge.zw = (bs_data.dest_rect.size / bs_data.src_rect_size) - vec2(0.5);
-            vUv = (local_pos - bs_data.dest_rect.p0) / bs_data.src_rect_size;
+            vEdge.x = 0.5;
+            vEdge.z = (bs_data.dest_rect.size.x / bs_data.src_rect_size.x) - 0.5;
+            vUv.x = (local_pos.x - bs_data.dest_rect.p0.x) / bs_data.src_rect_size.x;
             break;
         }
         case MODE_SIMPLE:
         default: {
-            vec2 f = (local_pos - bs_data.dest_rect.p0) / bs_data.dest_rect.size;
-            vUv = mix(uv0, uv1, f) / texture_size;
+            vEdge.xz = vec2(1.0);
+            vUv.x = (local_pos.x - bs_data.dest_rect.p0.x) / bs_data.dest_rect.size.x;
+            break;
+        }
+    }
+
+    switch (bs_data.stretch_mode_y) {
+        case MODE_STRETCH: {
+            vEdge.y = 0.5;
+            vEdge.w = (bs_data.dest_rect.size.y / bs_data.src_rect_size.y) - 0.5;
+            vUv.y = (local_pos.y - bs_data.dest_rect.p0.y) / bs_data.src_rect_size.y;
+            break;
+        }
+        case MODE_SIMPLE:
+        default: {
+            vEdge.yw = vec2(1.0);
+            vUv.y = (local_pos.y - bs_data.dest_rect.p0.y) / bs_data.dest_rect.size.y;
             break;
         }
     }
 
     vUvBounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy;
     vUvBounds_NoClamp = vec4(uv0, uv1) / texture_size.xyxy;
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 void main(void) {
     vec2 local_pos = vPos.xy / vPos.z;
-    vec2 uv;
 
-    switch (vStretchMode) {
-        case MODE_STRETCH: {
-            uv = clamp(vUv.xy, vec2(0.0), vEdge.xy);
-            uv += max(vec2(0.0), vUv.xy - vEdge.zw);
-            uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
-            break;
-        }
-        case MODE_SIMPLE:
-        default: {
-            uv = vUv.xy;
-            break;
-        }
-    }
-
+    vec2 uv = clamp(vUv.xy, vec2(0.0), vEdge.xy);
+    uv += max(vec2(0.0), vUv.xy - vEdge.zw);
+    uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
     uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
 
     float in_shadow_rect = point_inside_rect(
         local_pos,
         vLocalBounds.xy,
         vLocalBounds.zw
     );
 
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/cs_clip_line.glsl
@@ -0,0 +1,201 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared,prim_shared,clip_shared
+
+varying vec3 vLocalPos;
+
+flat varying int vStyle;
+flat varying float vAxisSelect;
+flat varying vec4 vParams;
+flat varying vec2 vLocalOrigin;
+
+#ifdef WR_VERTEX_SHADER
+
+#define LINE_ORIENTATION_VERTICAL       0
+#define LINE_ORIENTATION_HORIZONTAL     1
+
+struct LineDecorationData {
+    RectWithSize local_rect;
+    float wavyLineThickness;
+    float style;
+    float orientation;
+};
+
+LineDecorationData fetch_data(ivec2 address) {
+    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
+    RectWithSize local_rect = RectWithSize(data[0].xy, data[0].zw);
+    LineDecorationData line_data = LineDecorationData(
+        local_rect,
+        data[1].x,
+        data[1].y,
+        data[1].z
+    );
+    return line_data;
+}
+
+void main(void) {
+    ClipMaskInstance cmi = fetch_clip_item();
+    ClipArea area = fetch_clip_area(cmi.render_task_address);
+    ClipScrollNode scroll_node = fetch_clip_scroll_node(cmi.scroll_node_id);
+    LineDecorationData data = fetch_data(cmi.clip_data_address);
+
+    ClipVertexInfo vi = write_clip_tile_vertex(data.local_rect,
+                                               scroll_node,
+                                               area);
+
+
+    vLocalPos = vi.local_pos;
+
+    vec2 pos, size;
+
+    switch (int(data.orientation)) {
+        case LINE_ORIENTATION_HORIZONTAL:
+            vAxisSelect = 0.0;
+            pos = data.local_rect.p0;
+            size = data.local_rect.size;
+            break;
+        case LINE_ORIENTATION_VERTICAL:
+            vAxisSelect = 1.0;
+            pos = data.local_rect.p0.yx;
+            size = data.local_rect.size.yx;
+            break;
+        default:
+            vAxisSelect = 0.0;
+            pos = size = vec2(0.0);
+    }
+
+    vLocalOrigin = pos;
+    vStyle = int(data.style);
+
+    switch (vStyle) {
+        case LINE_STYLE_SOLID: {
+            break;
+        }
+        case LINE_STYLE_DASHED: {
+            float dash_length = size.y * 3.0;
+            vParams = vec4(2.0 * dash_length, // period
+                           dash_length,       // dash length
+                           0.0,
+                           0.0);
+            break;
+        }
+        case LINE_STYLE_DOTTED: {
+            float diameter = size.y;
+            float period = diameter * 2.0;
+            float center_line = pos.y + 0.5 * size.y;
+            float max_x = floor(size.x / period) * period;
+            vParams = vec4(period,
+                           diameter / 2.0, // radius
+                           center_line,
+                           max_x);
+            break;
+        }
+        case LINE_STYLE_WAVY: {
+            // This logic copied from gecko to get the same results
+            float line_thickness = max(data.wavyLineThickness, 1.0);
+            // Difference in height between peaks and troughs
+            // (and since slopes are 45 degrees, the length of each slope)
+            float slope_length = size.y - line_thickness;
+            // Length of flat runs
+            float flat_length = max((line_thickness - 1.0) * 2.0, 1.0);
+
+            vParams = vec4(line_thickness / 2.0,
+                           slope_length,
+                           flat_length,
+                           size.y);
+            break;
+        }
+        default:
+            vParams = vec4(0.0);
+    }
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+
+#define MAGIC_WAVY_LINE_AA_SNAP         0.5
+
+void main(void) {
+    // Find the appropriate distance to apply the step over.
+    vec2 local_pos = vLocalPos.xy / vLocalPos.z;
+    float aa_range = compute_aa_range(local_pos);
+    float alpha = 1.0;
+
+    // Select the x/y coord, depending on which axis this edge is.
+    vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect);
+
+    switch (vStyle) {
+        case LINE_STYLE_SOLID: {
+            break;
+        }
+        case LINE_STYLE_DASHED: {
+            // Get the main-axis position relative to closest dot or dash.
+            float x = mod(pos.x - vLocalOrigin.x, vParams.x);
+
+            // Calculate dash alpha (on/off) based on dash length
+            alpha = step(x, vParams.y);
+            break;
+        }
+        case LINE_STYLE_DOTTED: {
+            // Get the main-axis position relative to closest dot or dash.
+            float x = mod(pos.x - vLocalOrigin.x, vParams.x);
+
+            // Get the dot alpha
+            vec2 dot_relative_pos = vec2(x, pos.y) - vParams.yz;
+            float dot_distance = length(dot_relative_pos) - vParams.y;
+            alpha = distance_aa(aa_range, dot_distance);
+            // Clip off partial dots
+            alpha *= step(pos.x - vLocalOrigin.x, vParams.w);
+            break;
+        }
+        case LINE_STYLE_WAVY: {
+            vec2 normalized_local_pos = pos - vLocalOrigin.xy;
+
+            float half_line_thickness = vParams.x;
+            float slope_length = vParams.y;
+            float flat_length = vParams.z;
+            float vertical_bounds = vParams.w;
+            // Our pattern is just two slopes and two flats
+            float half_period = slope_length + flat_length;
+
+            float mid_height = vertical_bounds / 2.0;
+            float peak_offset = mid_height - half_line_thickness;
+            // Flip the wave every half period
+            float flip = -2.0 * (step(mod(normalized_local_pos.x, 2.0 * half_period), half_period) - 0.5);
+            // float flip = -1.0;
+            peak_offset *= flip;
+            float peak_height = mid_height + peak_offset;
+
+            // Convert pos to a local position within one half period
+            normalized_local_pos.x = mod(normalized_local_pos.x, half_period);
+
+            // Compute signed distance to the 3 lines that make up an arc
+            float dist1 = distance_to_line(vec2(0.0, peak_height),
+                                           vec2(1.0, -flip),
+                                           normalized_local_pos);
+            float dist2 = distance_to_line(vec2(0.0, peak_height),
+                                           vec2(0, -flip),
+                                           normalized_local_pos);
+            float dist3 = distance_to_line(vec2(flat_length, peak_height),
+                                           vec2(-1.0, -flip),
+                                           normalized_local_pos);
+            float dist = abs(max(max(dist1, dist2), dist3));
+
+            // Apply AA based on the thickness of the wave
+            alpha = distance_aa(aa_range, dist - half_line_thickness);
+
+            // Disable AA for thin lines
+            if (half_line_thickness <= 1.0) {
+                alpha = 1.0 - step(alpha, MAGIC_WAVY_LINE_AA_SNAP);
+            }
+
+            break;
+        }
+        default: break;
+    }
+
+    oFragColor = vec4(alpha);
+}
+#endif
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -71,17 +71,17 @@ vec4[2] fetch_from_resource_cache_2(int 
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0))
     );
 }
 
 #ifdef WR_VERTEX_SHADER
 
 #define VECS_PER_CLIP_SCROLL_NODE   9
 #define VECS_PER_LOCAL_CLIP_RECT    1
-#define VECS_PER_RENDER_TASK        3
+#define VECS_PER_RENDER_TASK        2
 #define VECS_PER_PRIM_HEADER        2
 #define VECS_PER_TEXT_RUN           3
 #define VECS_PER_GRADIENT_STOP      2
 
 uniform HIGHP_SAMPLER_FLOAT sampler2D sClipScrollNodes;
 uniform HIGHP_SAMPLER_FLOAT sampler2D sLocalClipRects;
 uniform HIGHP_SAMPLER_FLOAT sampler2D sRenderTasks;
 
@@ -198,40 +198,37 @@ RectWithSize fetch_clip_chain_rect(int i
 struct RenderTaskCommonData {
     RectWithSize task_rect;
     float texture_layer_index;
 };
 
 struct RenderTaskData {
     RenderTaskCommonData common_data;
     vec3 data1;
-    vec4 data2;
 };
 
 RenderTaskData fetch_render_task_data(int index) {
     ivec2 uv = get_fetch_uv(index, VECS_PER_RENDER_TASK);
 
     vec4 texel0 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(0, 0));
     vec4 texel1 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(1, 0));
-    vec4 texel2 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(2, 0));
 
     RectWithSize task_rect = RectWithSize(
         texel0.xy,
         texel0.zw
     );
 
     RenderTaskCommonData common_data = RenderTaskCommonData(
         task_rect,
         texel1.x
     );
 
     RenderTaskData data = RenderTaskData(
         common_data,
-        texel1.yzw,
-        texel2
+        texel1.yzw
     );
 
     return data;
 }
 
 RenderTaskCommonData fetch_render_task_common_data(int index) {
     ivec2 uv = get_fetch_uv(index, VECS_PER_RENDER_TASK);
 
@@ -257,28 +254,24 @@ RenderTaskCommonData fetch_render_task_c
 /*
  The dynamic picture that this brush exists on. Right now, it
  contains minimal information. In the future, it will describe
  the transform mode of primitives on this picture, among other things.
  */
 struct PictureTask {
     RenderTaskCommonData common_data;
     vec2 content_origin;
-    float pic_kind_and_raster_mode;
-    vec4 color;
 };
 
 PictureTask fetch_picture_task(int address) {
     RenderTaskData task_data = fetch_render_task_data(address);
 
     PictureTask task = PictureTask(
         task_data.common_data,
-        task_data.data1.xy,
-        task_data.data1.z,
-        task_data.data2
+        task_data.data1.xy
     );
 
     return task;
 }
 
 struct ClipArea {
     RenderTaskCommonData common_data;
     vec2 screen_origin;
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -7,21 +7,21 @@ use api::{DeviceUintRect, DeviceUintPoin
 use api::{DeviceIntPoint, SubpixelDirection, YuvColorSpace, YuvFormat};
 use api::{LayerToWorldTransform, WorldPixel};
 use border::{BorderCornerInstance, BorderCornerSide, BorderEdgeKind};
 use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::{CoordinateSystemId};
 use euclid::{TypedTransform3D, vec3};
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheAddress};
-use gpu_types::{BrushFlags, BrushInstance, ClipChainRectIndex};
+use gpu_types::{BrushFlags, BrushInstance, ClipChainRectIndex, ZBufferId, ZBufferIdGenerator};
 use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex, RasterizationSpace};
 use gpu_types::{CompositePrimitiveInstance, PrimitiveInstance, SimplePrimitiveInstance};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
-use picture::{PictureCompositeMode, PictureKind, PicturePrimitive};
+use picture::{PictureCompositeMode, PicturePrimitive};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{CachedGradient, ImageSource, PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
 use prim_store::{BrushPrimitive, BrushKind, DeferredResolve, EdgeAaSegmentMask, PictureIndex, PrimitiveRun};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
 use std::{usize, f32, i32};
@@ -51,17 +51,16 @@ pub enum BrushImageSourceKind {
     ColorAlphaMask = 2,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BrushBatchKind {
     Solid,
-    Line,
     Image(ImageBufferKind),
     Blend,
     MixBlend {
         task_id: RenderTaskId,
         source_id: RenderTaskId,
         backdrop_id: RenderTaskId,
     },
     YuvImage(ImageBufferKind, YuvFormat, YuvColorSpace),
@@ -452,16 +451,17 @@ impl AlphaBatchBuilder {
     pub fn add_pic_to_batch(
         &mut self,
         pic: &PicturePrimitive,
         task_id: RenderTaskId,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
+        z_generator: &mut ZBufferIdGenerator,
     ) {
         let task_address = render_tasks.get_task_address(task_id);
 
         let task = &render_tasks[task_id];
         let content_origin = match task.kind {
             RenderTaskKind::Picture(ref pic_task) => {
                 pic_task.content_origin
             }
@@ -484,16 +484,17 @@ impl AlphaBatchBuilder {
                 ctx,
                 gpu_cache,
                 render_tasks,
                 task_id,
                 task_address,
                 deferred_resolves,
                 &mut splitter,
                 content_origin,
+                z_generator,
             );
         }
 
         // Flush the accumulated plane splits onto the task tree.
         // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
         for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
             let prim_index = PrimitiveIndex(poly.anchor);
             debug!("process sorted poly {:?} {:?}", prim_index, poly.points);
@@ -519,17 +520,17 @@ impl AlphaBatchBuilder {
             let gpu_address = gpu_handle.as_int(gpu_cache);
 
             let instance = CompositePrimitiveInstance::new(
                 task_address,
                 source_task_address,
                 RenderTaskAddress(0),
                 gpu_address,
                 0,
-                prim_index.0 as i32,
+                z_generator.next(),
                 0,
                 0,
             );
 
             batch.push(PrimitiveInstance::from(instance));
         }
     }
 
@@ -543,16 +544,17 @@ impl AlphaBatchBuilder {
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
         task_id: RenderTaskId,
         task_address: RenderTaskAddress,
         deferred_resolves: &mut Vec<DeferredResolve>,
         splitter: &mut BspSplitter<f64, WorldPixel>,
         content_origin: DeviceIntPoint,
+        z_generator: &mut ZBufferIdGenerator,
     ) {
         for i in 0 .. run.count {
             let prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
             let metadata = &ctx.prim_store.cpu_metadata[prim_index.0];
 
             if metadata.screen_rect.is_some() {
                 self.add_prim_to_batch(
                     metadata.clip_chain_rect_index,
@@ -561,16 +563,17 @@ impl AlphaBatchBuilder {
                     ctx,
                     gpu_cache,
                     render_tasks,
                     task_id,
                     task_address,
                     deferred_resolves,
                     splitter,
                     content_origin,
+                    z_generator,
                 );
             }
         }
     }
 
     // Adds a primitive to a batch.
     // It can recursively call itself in some situations, for
     // example if it encounters a picture where the items
@@ -583,18 +586,19 @@ impl AlphaBatchBuilder {
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
         task_id: RenderTaskId,
         task_address: RenderTaskAddress,
         deferred_resolves: &mut Vec<DeferredResolve>,
         splitter: &mut BspSplitter<f64, WorldPixel>,
         content_origin: DeviceIntPoint,
+        z_generator: &mut ZBufferIdGenerator,
     ) {
-        let z = prim_index.0 as i32;
+        let z = z_generator.next();
         let prim_metadata = ctx.prim_store.get_metadata(prim_index);
         let scroll_node = &ctx.node_data[scroll_id.0 as usize];
         // TODO(gw): Calculating this for every primitive is a bit
         //           wasteful. We should probably cache this in
         //           the scroll node...
         let transform_kind = scroll_node.transform.transform_kind();
 
         let screen_rect = prim_metadata.screen_rect.expect("bug");
@@ -639,18 +643,252 @@ impl AlphaBatchBuilder {
                         let picture =
                             &ctx.prim_store.pictures[pic_index.0];
 
                         match picture.surface {
                             Some(cache_task_id) => {
                                 let cache_task_address = render_tasks.get_task_address(cache_task_id);
                                 let textures = BatchTextures::render_target_cache();
 
-                                match picture.kind {
-                                    PictureKind::TextShadow { .. } => {
+                                // If this picture is participating in a 3D rendering context,
+                                // then don't add it to any batches here. Instead, create a polygon
+                                // for it and add it to the current plane splitter.
+                                if picture.is_in_3d_context {
+                                    // Push into parent plane splitter.
+
+                                    let real_xf = &ctx.clip_scroll_tree
+                                        .nodes[picture.reference_frame_index.0]
+                                        .world_content_transform
+                                        .into();
+                                    let polygon = make_polygon(
+                                        picture.real_local_rect,
+                                        &real_xf,
+                                        prim_index.0,
+                                    );
+
+                                    splitter.add(polygon);
+
+                                    return;
+                                }
+
+                                // Depending on the composite mode of the picture, we generate the
+                                // old style Composite primitive instances. In the future, we'll
+                                // remove these and pass them through the brush batching pipeline.
+                                // This will allow us to unify some of the shaders, apply clip masks
+                                // when compositing pictures, and also correctly apply pixel snapping
+                                // to picture compositing operations.
+                                let source_id = cache_task_id;
+
+                                match picture.composite_mode.expect("bug: only composites here") {
+                                    PictureCompositeMode::Filter(filter) => {
+                                        match filter {
+                                            FilterOp::Blur(..) => {
+                                                let kind = BatchKind::Brush(
+                                                    BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
+                                                );
+                                                let key = BatchKey::new(kind, non_segmented_blend_mode, textures);
+                                                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
+
+                                                let uv_rect_address = render_tasks[cache_task_id]
+                                                    .get_texture_handle()
+                                                    .as_int(gpu_cache);
+
+                                                let instance = BrushInstance {
+                                                    picture_address: task_address,
+                                                    prim_address: prim_cache_address,
+                                                    clip_chain_rect_index,
+                                                    scroll_id,
+                                                    clip_task_address,
+                                                    z,
+                                                    segment_index: 0,
+                                                    edge_flags: EdgeAaSegmentMask::empty(),
+                                                    brush_flags: BrushFlags::empty(),
+                                                    user_data: [
+                                                        uv_rect_address,
+                                                        BrushImageSourceKind::Color as i32,
+                                                        RasterizationSpace::Screen as i32,
+                                                    ],
+                                                };
+                                                batch.push(PrimitiveInstance::from(instance));
+                                            }
+                                            FilterOp::DropShadow(offset, _, _) => {
+                                                let kind = BatchKind::Brush(
+                                                    BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
+                                                );
+                                                let key = BatchKey::new(kind, non_segmented_blend_mode, textures);
+
+                                                let uv_rect_address = render_tasks[cache_task_id]
+                                                    .get_texture_handle()
+                                                    .as_int(gpu_cache);
+
+                                                let instance = BrushInstance {
+                                                    picture_address: task_address,
+                                                    prim_address: prim_cache_address,
+                                                    clip_chain_rect_index,
+                                                    scroll_id,
+                                                    clip_task_address,
+                                                    z,
+                                                    segment_index: 0,
+                                                    edge_flags: EdgeAaSegmentMask::empty(),
+                                                    brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
+                                                    user_data: [
+                                                        uv_rect_address,
+                                                        BrushImageSourceKind::ColorAlphaMask as i32,
+                                                        // TODO(gw): This is totally wrong, but the drop-shadow code itself
+                                                        //           is completely wrong, and doesn't work correctly with
+                                                        //           transformed Picture sources. I'm leaving this as is for
+                                                        //           now, and will fix drop-shadows properly, as a follow up.
+                                                        RasterizationSpace::Local as i32,
+                                                    ],
+                                                };
+
+                                                {
+                                                    let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
+                                                    batch.push(PrimitiveInstance::from(instance));
+                                                }
+
+                                                let secondary_id = picture.secondary_render_task_id.expect("no secondary!?");
+                                                let saved_index = render_tasks[secondary_id].saved_index.expect("no saved index!?");
+                                                debug_assert_ne!(saved_index, SavedTargetIndex::PENDING);
+                                                let secondary_task_address = render_tasks.get_task_address(secondary_id);
+                                                let secondary_textures = BatchTextures {
+                                                    colors: [
+                                                        SourceTexture::RenderTaskCache(saved_index),
+                                                        SourceTexture::Invalid,
+                                                        SourceTexture::Invalid,
+                                                    ],
+                                                };
+                                                let key = BatchKey::new(
+                                                    BatchKind::HardwareComposite,
+                                                    BlendMode::PremultipliedAlpha,
+                                                    secondary_textures,
+                                                );
+                                                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
+                                                let content_rect = prim_metadata.local_rect.translate(&-offset);
+                                                let rect =
+                                                    (content_rect * LayerToWorldScale::new(1.0) * ctx.device_pixel_scale).round()
+                                                                                                                         .to_i32();
+
+                                                let instance = CompositePrimitiveInstance::new(
+                                                    task_address,
+                                                    secondary_task_address,
+                                                    RenderTaskAddress(0),
+                                                    rect.origin.x,
+                                                    rect.origin.y,
+                                                    z,
+                                                    rect.size.width,
+                                                    rect.size.height,
+                                                );
+
+                                                batch.push(PrimitiveInstance::from(instance));
+                                            }
+                                            _ => {
+                                                let key = BatchKey::new(
+                                                    BatchKind::Brush(BrushBatchKind::Blend),
+                                                    BlendMode::PremultipliedAlpha,
+                                                    BatchTextures::render_target_cache(),
+                                                );
+
+                                                let filter_mode = match filter {
+                                                    FilterOp::Blur(..) => 0,
+                                                    FilterOp::Contrast(..) => 1,
+                                                    FilterOp::Grayscale(..) => 2,
+                                                    FilterOp::HueRotate(..) => 3,
+                                                    FilterOp::Invert(..) => 4,
+                                                    FilterOp::Saturate(..) => 5,
+                                                    FilterOp::Sepia(..) => 6,
+                                                    FilterOp::Brightness(..) => 7,
+                                                    FilterOp::Opacity(..) => 8,
+                                                    FilterOp::DropShadow(..) => 9,
+                                                    FilterOp::ColorMatrix(..) => 10,
+                                                };
+
+                                                let user_data = match filter {
+                                                    FilterOp::Contrast(amount) |
+                                                    FilterOp::Grayscale(amount) |
+                                                    FilterOp::Invert(amount) |
+                                                    FilterOp::Saturate(amount) |
+                                                    FilterOp::Sepia(amount) |
+                                                    FilterOp::Brightness(amount) |
+                                                    FilterOp::Opacity(_, amount) => {
+                                                        (amount * 65536.0) as i32
+                                                    }
+                                                    FilterOp::HueRotate(angle) => {
+                                                        (0.01745329251 * angle * 65536.0) as i32
+                                                    }
+                                                    // Go through different paths
+                                                    FilterOp::Blur(..) |
+                                                    FilterOp::DropShadow(..) => {
+                                                        unreachable!();
+                                                    }
+                                                    FilterOp::ColorMatrix(_) => {
+                                                        picture.extra_gpu_data_handle.as_int(gpu_cache)
+                                                    }
+                                                };
+
+                                                let instance = BrushInstance {
+                                                    picture_address: task_address,
+                                                    prim_address: prim_cache_address,
+                                                    clip_chain_rect_index,
+                                                    scroll_id,
+                                                    clip_task_address,
+                                                    z,
+                                                    segment_index: 0,
+                                                    edge_flags: EdgeAaSegmentMask::empty(),
+                                                    brush_flags: BrushFlags::empty(),
+                                                    user_data: [
+                                                        cache_task_address.0 as i32,
+                                                        filter_mode,
+                                                        user_data,
+                                                    ],
+                                                };
+
+                                                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
+                                                batch.push(PrimitiveInstance::from(instance));
+                                            }
+                                        }
+                                    }
+                                    PictureCompositeMode::MixBlend(mode) => {
+                                        let backdrop_id = picture.secondary_render_task_id.expect("no backdrop!?");
+
+                                        let key = BatchKey::new(
+                                            BatchKind::Brush(
+                                                BrushBatchKind::MixBlend {
+                                                    task_id,
+                                                    source_id,
+                                                    backdrop_id,
+                                                },
+                                            ),
+                                            BlendMode::PremultipliedAlpha,
+                                            BatchTextures::no_texture(),
+                                        );
+                                        let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
+                                        let backdrop_task_address = render_tasks.get_task_address(backdrop_id);
+                                        let source_task_address = render_tasks.get_task_address(source_id);
+
+                                        let instance = BrushInstance {
+                                            picture_address: task_address,
+                                            prim_address: prim_cache_address,
+                                            clip_chain_rect_index,
+                                            scroll_id,
+                                            clip_task_address,
+                                            z,
+                                            segment_index: 0,
+                                            edge_flags: EdgeAaSegmentMask::empty(),
+                                            brush_flags: BrushFlags::empty(),
+                                            user_data: [
+                                                mode as u32 as i32,
+                                                backdrop_task_address.0 as i32,
+                                                source_task_address.0 as i32,
+                                            ],
+                                        };
+
+                                        batch.push(PrimitiveInstance::from(instance));
+                                    }
+                                    PictureCompositeMode::Blit => {
                                         let kind = BatchKind::Brush(
                                             BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
                                         );
                                         let key = BatchKey::new(kind, non_segmented_blend_mode, textures);
                                         let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
 
                                         let uv_rect_address = render_tasks[cache_task_id]
                                             .get_texture_handle()
@@ -669,303 +907,29 @@ impl AlphaBatchBuilder {
                                             user_data: [
                                                 uv_rect_address,
                                                 BrushImageSourceKind::Color as i32,
                                                 RasterizationSpace::Screen as i32,
                                             ],
                                         };
                                         batch.push(PrimitiveInstance::from(instance));
                                     }
-                                    PictureKind::Image {
-                                        composite_mode,
-                                        secondary_render_task_id,
-                                        is_in_3d_context,
-                                        reference_frame_index,
-                                        real_local_rect,
-                                        ref extra_gpu_data_handle,
-                                        ..
-                                    } => {
-                                        // If this picture is participating in a 3D rendering context,
-                                        // then don't add it to any batches here. Instead, create a polygon
-                                        // for it and add it to the current plane splitter.
-                                        if is_in_3d_context {
-                                            // Push into parent plane splitter.
-
-                                            let real_xf = &ctx.clip_scroll_tree
-                                                .nodes[reference_frame_index.0]
-                                                .world_content_transform
-                                                .into();
-                                            let polygon = make_polygon(
-                                                real_local_rect,
-                                                &real_xf,
-                                                prim_index.0,
-                                            );
-
-                                            splitter.add(polygon);
-
-                                            return;
-                                        }
-
-                                        // Depending on the composite mode of the picture, we generate the
-                                        // old style Composite primitive instances. In the future, we'll
-                                        // remove these and pass them through the brush batching pipeline.
-                                        // This will allow us to unify some of the shaders, apply clip masks
-                                        // when compositing pictures, and also correctly apply pixel snapping
-                                        // to picture compositing operations.
-                                        let source_id = cache_task_id;
-
-                                        match composite_mode.expect("bug: only composites here") {
-                                            PictureCompositeMode::Filter(filter) => {
-                                                match filter {
-                                                    FilterOp::Blur(..) => {
-                                                        let kind = BatchKind::Brush(
-                                                            BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
-                                                        );
-                                                        let key = BatchKey::new(kind, non_segmented_blend_mode, textures);
-                                                        let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
-
-                                                        let uv_rect_address = render_tasks[cache_task_id]
-                                                            .get_texture_handle()
-                                                            .as_int(gpu_cache);
-
-                                                        let instance = BrushInstance {
-                                                            picture_address: task_address,
-                                                            prim_address: prim_cache_address,
-                                                            clip_chain_rect_index,
-                                                            scroll_id,
-                                                            clip_task_address,
-                                                            z,
-                                                            segment_index: 0,
-                                                            edge_flags: EdgeAaSegmentMask::empty(),
-                                                            brush_flags: BrushFlags::empty(),
-                                                            user_data: [
-                                                                uv_rect_address,
-                                                                BrushImageSourceKind::Color as i32,
-                                                                RasterizationSpace::Screen as i32,
-                                                            ],
-                                                        };
-                                                        batch.push(PrimitiveInstance::from(instance));
-                                                    }
-                                                    FilterOp::DropShadow(offset, _, _) => {
-                                                        let kind = BatchKind::Brush(
-                                                            BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
-                                                        );
-                                                        let key = BatchKey::new(kind, non_segmented_blend_mode, textures);
-
-                                                        let uv_rect_address = render_tasks[cache_task_id]
-                                                            .get_texture_handle()
-                                                            .as_int(gpu_cache);
-
-                                                        let instance = BrushInstance {
-                                                            picture_address: task_address,
-                                                            prim_address: prim_cache_address,
-                                                            clip_chain_rect_index,
-                                                            scroll_id,
-                                                            clip_task_address,
-                                                            z,
-                                                            segment_index: 0,
-                                                            edge_flags: EdgeAaSegmentMask::empty(),
-                                                            brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
-                                                            user_data: [
-                                                                uv_rect_address,
-                                                                BrushImageSourceKind::ColorAlphaMask as i32,
-                                                                // TODO(gw): This is totally wrong, but the drop-shadow code itself
-                                                                //           is completely wrong, and doesn't work correctly with
-                                                                //           transformed Picture sources. I'm leaving this as is for
-                                                                //           now, and will fix drop-shadows properly, as a follow up.
-                                                                RasterizationSpace::Local as i32,
-                                                            ],
-                                                        };
-
-                                                        {
-                                                            let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
-                                                            batch.push(PrimitiveInstance::from(instance));
-                                                        }
-
-                                                        let secondary_id = secondary_render_task_id.expect("no secondary!?");
-                                                        let saved_index = render_tasks[secondary_id].saved_index.expect("no saved index!?");
-                                                        debug_assert_ne!(saved_index, SavedTargetIndex::PENDING);
-                                                        let secondary_task_address = render_tasks.get_task_address(secondary_id);
-                                                        let secondary_textures = BatchTextures {
-                                                            colors: [
-                                                                SourceTexture::RenderTaskCache(saved_index),
-                                                                SourceTexture::Invalid,
-                                                                SourceTexture::Invalid,
-                                                            ],
-                                                        };
-                                                        let key = BatchKey::new(
-                                                            BatchKind::HardwareComposite,
-                                                            BlendMode::PremultipliedAlpha,
-                                                            secondary_textures,
-                                                        );
-                                                        let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
-                                                        let content_rect = prim_metadata.local_rect.translate(&-offset);
-                                                        let rect =
-                                                            (content_rect * LayerToWorldScale::new(1.0) * ctx.device_pixel_scale).round()
-                                                                                                                                 .to_i32();
-
-                                                        let instance = CompositePrimitiveInstance::new(
-                                                            task_address,
-                                                            secondary_task_address,
-                                                            RenderTaskAddress(0),
-                                                            rect.origin.x,
-                                                            rect.origin.y,
-                                                            z,
-                                                            rect.size.width,
-                                                            rect.size.height,
-                                                        );
-
-                                                        batch.push(PrimitiveInstance::from(instance));
-                                                    }
-                                                    _ => {
-                                                        let key = BatchKey::new(
-                                                            BatchKind::Brush(BrushBatchKind::Blend),
-                                                            BlendMode::PremultipliedAlpha,
-                                                            BatchTextures::render_target_cache(),
-                                                        );
-
-                                                        let filter_mode = match filter {
-                                                            FilterOp::Blur(..) => 0,
-                                                            FilterOp::Contrast(..) => 1,
-                                                            FilterOp::Grayscale(..) => 2,
-                                                            FilterOp::HueRotate(..) => 3,
-                                                            FilterOp::Invert(..) => 4,
-                                                            FilterOp::Saturate(..) => 5,
-                                                            FilterOp::Sepia(..) => 6,
-                                                            FilterOp::Brightness(..) => 7,
-                                                            FilterOp::Opacity(..) => 8,
-                                                            FilterOp::DropShadow(..) => 9,
-                                                            FilterOp::ColorMatrix(..) => 10,
-                                                        };
-
-                                                        let user_data = match filter {
-                                                            FilterOp::Contrast(amount) |
-                                                            FilterOp::Grayscale(amount) |
-                                                            FilterOp::Invert(amount) |
-                                                            FilterOp::Saturate(amount) |
-                                                            FilterOp::Sepia(amount) |
-                                                            FilterOp::Brightness(amount) |
-                                                            FilterOp::Opacity(_, amount) => {
-                                                                (amount * 65536.0) as i32
-                                                            }
-                                                            FilterOp::HueRotate(angle) => {
-                                                                (0.01745329251 * angle * 65536.0) as i32
-                                                            }
-                                                            // Go through different paths
-                                                            FilterOp::Blur(..) |
-                                                            FilterOp::DropShadow(..) => {
-                                                                unreachable!();
-                                                            }
-                                                            FilterOp::ColorMatrix(_) => {
-                                                                extra_gpu_data_handle.as_int(gpu_cache)
-                                                            }
-                                                        };
-
-                                                        let instance = BrushInstance {
-                                                            picture_address: task_address,
-                                                            prim_address: prim_cache_address,
-                                                            clip_chain_rect_index,
-                                                            scroll_id,
-                                                            clip_task_address,
-                                                            z,
-                                                            segment_index: 0,
-                                                            edge_flags: EdgeAaSegmentMask::empty(),
-                                                            brush_flags: BrushFlags::empty(),
-                                                            user_data: [
-                                                                cache_task_address.0 as i32,
-                                                                filter_mode,
-                                                                user_data,
-                                                            ],
-                                                        };
-
-                                                        let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
-                                                        batch.push(PrimitiveInstance::from(instance));
-                                                    }
-                                                }
-                                            }
-                                            PictureCompositeMode::MixBlend(mode) => {
-                                                let backdrop_id = secondary_render_task_id.expect("no backdrop!?");
-
-                                                let key = BatchKey::new(
-                                                    BatchKind::Brush(
-                                                        BrushBatchKind::MixBlend {
-                                                            task_id,
-                                                            source_id,
-                                                            backdrop_id,
-                                                        },
-                                                    ),
-                                                    BlendMode::PremultipliedAlpha,
-                                                    BatchTextures::no_texture(),
-                                                );
-                                                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
-                                                let backdrop_task_address = render_tasks.get_task_address(backdrop_id);
-                                                let source_task_address = render_tasks.get_task_address(source_id);
-
-                                                let instance = BrushInstance {
-                                                    picture_address: task_address,
-                                                    prim_address: prim_cache_address,
-                                                    clip_chain_rect_index,
-                                                    scroll_id,
-                                                    clip_task_address,
-                                                    z,
-                                                    segment_index: 0,
-                                                    edge_flags: EdgeAaSegmentMask::empty(),
-                                                    brush_flags: BrushFlags::empty(),
-                                                    user_data: [
-                                                        mode as u32 as i32,
-                                                        backdrop_task_address.0 as i32,
-                                                        source_task_address.0 as i32,
-                                                    ],
-                                                };
-
-                                                batch.push(PrimitiveInstance::from(instance));
-                                            }
-                                            PictureCompositeMode::Blit => {
-                                                let kind = BatchKind::Brush(
-                                                    BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
-                                                );
-                                                let key = BatchKey::new(kind, non_segmented_blend_mode, textures);
-                                                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
-
-                                                let uv_rect_address = render_tasks[cache_task_id]
-                                                    .get_texture_handle()
-                                                    .as_int(gpu_cache);
-
-                                                let instance = BrushInstance {
-                                                    picture_address: task_address,
-                                                    prim_address: prim_cache_address,
-                                                    clip_chain_rect_index,
-                                                    scroll_id,
-                                                    clip_task_address,
-                                                    z,
-                                                    segment_index: 0,
-                                                    edge_flags: EdgeAaSegmentMask::empty(),
-                                                    brush_flags: BrushFlags::empty(),
-                                                    user_data: [
-                                                        uv_rect_address,
-                                                        BrushImageSourceKind::Color as i32,
-                                                        RasterizationSpace::Screen as i32,
-                                                    ],
-                                                };
-                                                batch.push(PrimitiveInstance::from(instance));
-                                            }
-                                        }
-                                    }
                                 }
                             }
                             None => {
                                 // If this picture is being drawn into an existing target (i.e. with
                                 // no composition operation), recurse and add to the current batch list.
                                 self.add_pic_to_batch(
                                     picture,
                                     task_id,
                                     ctx,
                                     gpu_cache,
                                     render_tasks,
                                     deferred_resolves,
+                                    z_generator,
                                 );
                             }
                         }
                     }
                     _ => {
                         if let Some((batch_kind, textures, user_data)) = brush.get_batch_params(
                                 ctx.resource_cache,
                                 gpu_cache,
@@ -1176,17 +1140,17 @@ impl AlphaBatchBuilder {
         textures: BatchTextures,
         clip_chain_rect_index: ClipChainRectIndex,
         clip_task_address: RenderTaskAddress,
         task_relative_bounding_rect: &DeviceIntRect,
         prim_cache_address: GpuCacheAddress,
         scroll_id: ClipScrollNodeIndex,
         task_address: RenderTaskAddress,
         transform_kind: TransformedRectKind,
-        z: i32,
+        z: ZBufferId,
         render_tasks: &RenderTaskTree,
         user_data: [i32; 3],
     ) {
         let base_instance = BrushInstance {
             picture_address: task_address,
             prim_address: prim_cache_address,
             clip_chain_rect_index,
             scroll_id,
@@ -1276,23 +1240,16 @@ impl BrushPrimitive {
     fn get_batch_params(
         &self,
         resource_cache: &ResourceCache,
         gpu_cache: &mut GpuCache,
         deferred_resolves: &mut Vec<DeferredResolve>,
         cached_gradients: &[CachedGradient],
     ) -> Option<(BrushBatchKind, BatchTextures, [i32; 3])> {
         match self.kind {
-            BrushKind::Line { .. } => {
-                Some((
-                    BrushBatchKind::Line,
-                    BatchTextures::no_texture(),
-                    [0; 3],
-                ))
-            }
             BrushKind::Image { request, .. } => {
                 let cache_item = resolve_image(
                     request,
                     resource_cache,
                     gpu_cache,
                     deferred_resolves,
                 );
 
@@ -1435,17 +1392,16 @@ impl AlphaBatchHelpers for PrimitiveStor
                     }
                     BrushKind::Image { alpha_type, .. } => {
                         match alpha_type {
                             AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
                             AlphaType::Alpha => BlendMode::Alpha,
                         }
                     }
                     BrushKind::Solid { .. } |
-                    BrushKind::Line { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::LinearGradient { .. } |
                     BrushKind::Picture { .. } => {
                         BlendMode::PremultipliedAlpha
                     }
                 }
             }
@@ -1547,26 +1503,28 @@ fn make_polygon(
 pub struct ClipBatcher {
     /// Rectangle draws fill up the rectangles with rounded corners.
     pub rectangles: Vec<ClipMaskInstance>,
     /// Image draws apply the image masking.
     pub images: FastHashMap<SourceTexture, Vec<ClipMaskInstance>>,
     pub border_clears: Vec<ClipMaskInstance>,
     pub borders: Vec<ClipMaskInstance>,
     pub box_shadows: FastHashMap<SourceTexture, Vec<ClipMaskInstance>>,
+    pub line_decorations: Vec<ClipMaskInstance>,
 }
 
 impl ClipBatcher {
     pub fn new() -> Self {
         ClipBatcher {
             rectangles: Vec::new(),
             images: FastHashMap::default(),
             border_clears: Vec::new(),
             borders: Vec::new(),
             box_shadows: FastHashMap::default(),
+            line_decorations: Vec::new(),
         }
     }
 
     pub fn add_clip_region(
         &mut self,
         task_address: RenderTaskAddress,
         clip_data_address: GpuCacheAddress,
     ) {
@@ -1624,16 +1582,22 @@ impl ClipBatcher {
                                     ..instance
                                 });
                         } else {
                             warn!("Warnings: skip a image mask");
                             debug!("Key:{:?} Rect::{:?}", mask.image, mask.rect);
                             continue;
                         }
                     }
+                    ClipSource::LineDecoration(..) => {
+                        self.line_decorations.push(ClipMaskInstance {
+                            clip_data_address: gpu_address,
+                            ..instance
+                        });
+                    }
                     ClipSource::BoxShadow(ref info) => {
                         debug_assert_ne!(info.cache_item.texture_id, SourceTexture::Invalid);
 
                         self.box_shadows
                             .entry(info.cache_item.texture_id)
                             .or_insert(Vec::new())
                             .push(ClipMaskInstance {
                                 clip_data_address: gpu_address,
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.rs
@@ -14,17 +14,18 @@ use resource_cache::CacheItem;
 use util::RectHelpers;
 
 #[derive(Debug)]
 pub struct BoxShadowClipSource {
     // Parameters that define the shadow and are constant.
     pub shadow_radius: BorderRadius,
     pub blur_radius: f32,
     pub clip_mode: BoxShadowClipMode,
-    pub stretch_mode: BoxShadowStretchMode,
+    pub stretch_mode_x: BoxShadowStretchMode,
+    pub stretch_mode_y: BoxShadowStretchMode,
 
     // The current cache key (in device-pixels), and handles
     // to the cached clip region and blurred texture.
     pub cache_key: Option<(DeviceIntSize, BoxShadowCacheKey)>,
     pub cache_item: CacheItem,
     pub clip_data_handle: GpuCacheHandle,
 
     // Local-space size of the required render task size.
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,33 +1,41 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
 use api::{ImageRendering, LayerRect, LayerSize, LayoutPoint, LayoutVector2D, LocalClip};
-use api::{BoxShadowClipMode, LayerPoint, LayerToWorldScale};
+use api::{BoxShadowClipMode, LayerPoint, LayerToWorldScale, LineOrientation, LineStyle};
 use border::{BorderCornerClipSource, ensure_no_corner_overlap};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
 use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId};
 use ellipse::Ellipse;
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use gpu_types::{BoxShadowStretchMode, ClipScrollNodeIndex};
 use prim_store::{ClipData, ImageMaskData};
 use render_task::to_cache_size;
 use resource_cache::{CacheItem, ImageRequest, ResourceCache};
 use util::{LayerToWorldFastTransform, MaxRect, calculate_screen_bounding_rect};
-use util::extract_inner_rect_safe;
+use util::{extract_inner_rect_safe, pack_as_float};
 use std::sync::Arc;
 
 pub type ClipStore = FreeList<ClipSources>;
 pub type ClipSourcesHandle = FreeListHandle<ClipSources>;
 pub type ClipSourcesWeakHandle = WeakFreeListHandle<ClipSources>;
 
+#[derive(Debug)]
+pub struct LineDecorationClipSource {
+    rect: LayerRect,
+    style: LineStyle,
+    orientation: LineOrientation,
+    wavy_line_thickness: f32,
+}
+
 #[derive(Clone, Debug)]
 pub struct ClipRegion {
     pub main: LayerRect,
     pub image_mask: Option<ImageMask>,
     pub complex_clips: Vec<ComplexClipRegion>,
 }
 
 impl ClipRegion {
@@ -77,16 +85,17 @@ pub enum ClipSource {
     RoundedRectangle(LayerRect, BorderRadius, ClipMode),
     Image(ImageMask),
     /// TODO(gw): This currently only handles dashed style
     /// clips, where the border style is dashed for both
     /// adjacent border edges. Expand to handle dotted style
     /// and different styles per edge.
     BorderCorner(BorderCornerClipSource),
     BoxShadow(BoxShadowClipSource),
+    LineDecoration(LineDecorationClipSource),
 }
 
 impl From<ClipRegion> for ClipSources {
     fn from(region: ClipRegion) -> ClipSources {
         let mut clips = Vec::new();
 
         if let Some(info) = region.image_mask {
             clips.push(ClipSource::Image(info));
@@ -115,16 +124,32 @@ impl ClipSource {
         ensure_no_corner_overlap(&mut radii, &rect);
         ClipSource::RoundedRectangle(
             rect,
             radii,
             clip_mode,
         )
     }
 
+    pub fn new_line_decoration(
+        rect: LayerRect,
+        style: LineStyle,
+        orientation: LineOrientation,
+        wavy_line_thickness: f32,
+    ) -> ClipSource {
+        ClipSource::LineDecoration(
+            LineDecorationClipSource {
+                rect,
+                style,
+                orientation,
+                wavy_line_thickness,
+            }
+        )
+    }
+
     pub fn new_box_shadow(
         shadow_rect: LayerRect,
         shadow_radius: BorderRadius,
         prim_shadow_rect: LayerRect,
         blur_radius: f32,
         clip_mode: BoxShadowClipMode,
     ) -> ClipSource {
         // Get the fractional offsets required to match the
@@ -173,45 +198,68 @@ impl ClipSource {
             ),
             LayerSize::new(
                 min_shadow_rect_size.width + fract_size.width,
                 min_shadow_rect_size.height + fract_size.height,
             ),
         );
 
         // If the width or height ends up being bigger than the original
-        // primitive shadow rect, just blur the entire rect and draw that
-        // as a simple blit. This is necessary for correctness, since the
-        // blur of one corner may affect the blur in another corner.
-        let mut stretch_mode = BoxShadowStretchMode::Stretch;
-        if shadow_rect.size.width < minimal_shadow_rect.size.width ||
-           shadow_rect.size.height < minimal_shadow_rect.size.height {
-            minimal_shadow_rect.size = shadow_rect.size;
-            stretch_mode = BoxShadowStretchMode::Simple;
+        // primitive shadow rect, just blur the entire rect along that
+        // axis and draw that as a simple blit. This is necessary for
+        // correctness, since the blur of one corner may affect the blur
+        // in another corner.
+        let mut stretch_mode_x = BoxShadowStretchMode::Stretch;
+        if shadow_rect.size.width < minimal_shadow_rect.size.width {
+            minimal_shadow_rect.size.width = shadow_rect.size.width;
+            stretch_mode_x = BoxShadowStretchMode::Simple;
+        }
+
+        let mut stretch_mode_y = BoxShadowStretchMode::Stretch;
+        if shadow_rect.size.height < minimal_shadow_rect.size.height {
+            minimal_shadow_rect.size.height = shadow_rect.size.height;
+            stretch_mode_y = BoxShadowStretchMode::Simple;
         }
 
         // Expand the shadow rect by enough room for the blur to take effect.
         let shadow_rect_alloc_size = LayerSize::new(
             2.0 * blur_region + minimal_shadow_rect.size.width.ceil(),
             2.0 * blur_region + minimal_shadow_rect.size.height.ceil(),
         );
 
         ClipSource::BoxShadow(BoxShadowClipSource {
             shadow_rect_alloc_size,
             shadow_radius,
             prim_shadow_rect,
             blur_radius,
             clip_mode,
-            stretch_mode,
+            stretch_mode_x,
+            stretch_mode_y,
             cache_item: CacheItem::invalid(),
             cache_key: None,
             clip_data_handle: GpuCacheHandle::new(),
             minimal_shadow_rect,
         })
     }
+
+    // Return a modified clip source that is the same as self
+    // but offset in local-space by a specified amount.
+    pub fn offset(&self, offset: &LayoutVector2D) -> ClipSource {
+        match *self {
+            ClipSource::LineDecoration(ref info) => {
+                ClipSource::LineDecoration(LineDecorationClipSource {
+                    rect: info.rect.translate(offset),
+                    ..*info
+                })
+            }
+            _ => {
+                panic!("bug: other clip sources not expected here yet");
+            }
+        }
+    }
 }
 
 #[derive(Debug)]
 pub struct ClipSources {
     pub clips: Vec<(ClipSource, GpuCacheHandle)>,
     pub local_inner_rect: LayerRect,
     pub local_outer_rect: Option<LayerRect>
 }
@@ -274,17 +322,18 @@ impl ClipSources {
                     can_calculate_outer_rect = true;
                     local_outer = local_outer.and_then(|r| r.intersection(rect));
 
                     let inner_rect = extract_inner_rect_safe(rect, radius);
                     local_inner = local_inner
                         .and_then(|r| inner_rect.and_then(|ref inner| r.intersection(inner)));
                 }
                 ClipSource::BoxShadow(..) |
-                ClipSource::BorderCorner { .. } => {
+                ClipSource::BorderCorner { .. } |
+                ClipSource::LineDecoration(..) => {
                     can_calculate_inner_rect = false;
                     break;
                 }
             }
         }
 
         let outer = match can_calculate_outer_rect {
             true => Some(local_outer.unwrap_or_else(LayerRect::zero)),
@@ -312,31 +361,46 @@ impl ClipSources {
                         let data = ImageMaskData { local_rect: mask.rect };
                         data.write_gpu_blocks(request);
                     }
                     ClipSource::BoxShadow(ref info) => {
                         request.push([
                             info.shadow_rect_alloc_size.width,
                             info.shadow_rect_alloc_size.height,
                             info.clip_mode as i32 as f32,
-                            info.stretch_mode as i32 as f32,
+                            0.0,
+                        ]);
+                        request.push([
+                            info.stretch_mode_x as i32 as f32,
+                            info.stretch_mode_y as i32 as f32,
+                            0.0,
+                            0.0,
                         ]);
                         request.push(info.prim_shadow_rect);
                     }
                     ClipSource::Rectangle(rect) => {
                         let data = ClipData::uniform(rect, 0.0, ClipMode::Clip);
                         data.write(&mut request);
                     }
                     ClipSource::RoundedRectangle(ref rect, ref radius, mode) => {
                         let data = ClipData::rounded_rect(rect, radius, mode);
                         data.write(&mut request);
                     }
                     ClipSource::BorderCorner(ref mut source) => {
                         source.write(request);
                     }
+                    ClipSource::LineDecoration(ref info) => {
+                        request.push(info.rect);
+                        request.push([
+                            info.wavy_line_thickness,
+                            pack_as_float(info.style as u32),
+                            pack_as_float(info.orientation as u32),
+                            0.0,
+                        ]);
+                    }
                 }
             }
 
             match *source {
                 ClipSource::Image(ref mask) => {
                     resource_cache.request_image(
                         ImageRequest {
                             key: mask.image,
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -10,27 +10,26 @@ use api::{FilterOp, FontInstanceKey, Fon
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayerPoint, LayerPrimitiveInfo};
 use api::{LayerRect, LayerSize, LayerVector2D, LayoutRect, LayoutSize, LayoutTransform};
 use api::{LayoutVector2D, LineOrientation, LineStyle, LocalClip, PipelineId, PropertyBinding};
 use api::{RepeatMode, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity, Shadow};
 use api::{SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect, TileOffset};
 use api::{TransformStyle, YuvColorSpace, YuvData};
 use app_units::Au;
 use border::ImageBorderSegment;
-use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
 use clip_scroll_node::{ClipScrollNode, NodeType, StickyFrameInfo};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
 use euclid::{SideOffsets2D, vec2};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::{decompose_image, TiledImageInfo};
 use internal_types::{FastHashMap, FastHashSet};
-use picture::{PictureCompositeMode, PictureKind};
+use picture::PictureCompositeMode;
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient};
 use prim_store::{CachedGradientIndex, ImageCacheKey, ImagePrimitiveCpu, ImageSource};
 use prim_store::{PictureIndex, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{ScrollNodeAndClipChain, TextRunPrimitiveCpu};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest, TiledImageMap};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::{BuiltScene, SceneRequest};
@@ -164,34 +163,29 @@ pub struct DisplayListFlattener<'a> {
     /// A list of replacements to make in order to properly handle fixed position
     /// content as well as stacking contexts that create reference frames.
     replacements: Vec<(ClipId, ClipId)>,
 
     /// The data structure that converting between ClipId and the various index
     /// types that the ClipScrollTree uses.
     id_to_index_mapper: ClipIdToIndexMapper,
 
-    /// A stack of the current shadow primitives.  The sub-Vec stores
-    /// a buffer of fast-path primitives to be appended on pop.
-    shadow_prim_stack: Vec<(PrimitiveIndex, Vec<(PrimitiveIndex, ScrollNodeAndClipChain)>)>,
-
-    /// A buffer of "real" content when doing fast-path shadows. This is appended
-    /// when the shadow stack is empty.
-    pending_shadow_contents: Vec<(PrimitiveIndex, ScrollNodeAndClipChain, LayerPrimitiveInfo)>,
-
     /// A stack of scroll nodes used during display list processing to properly
     /// parent new scroll nodes.
     reference_frame_stack: Vec<(ClipId, ClipScrollNodeIndex)>,
 
     /// A stack of stacking context properties.
     sc_stack: Vec<FlattenedStackingContext>,
 
     /// A stack of the current pictures.
     picture_stack: Vec<PictureIndex>,
 
+    /// A stack of the currently active shadows
+    shadow_stack: Vec<(Shadow, PictureIndex)>,
+
     /// A list of scrollbar primitives.
     pub scrollbar_prims: Vec<ScrollbarPrimitive>,
 
     /// The store of primitives.
     pub prim_store: PrimitiveStore,
 
     /// Information about all primitives involved in hit testing.
     pub hit_testing_runs: Vec<HitTestingRun>,
@@ -235,22 +229,21 @@ impl<'a> DisplayListFlattener<'a> {
             font_instances,
             tiled_image_map,
             config: *frame_builder_config,
             pipeline_epochs: Vec::new(),
             replacements: Vec::new(),
             output_pipelines,
             id_to_index_mapper: ClipIdToIndexMapper::default(),
             hit_testing_runs: recycle_vec(old_builder.hit_testing_runs),
-            shadow_prim_stack: Vec::new(),
             cached_gradients: recycle_vec(old_builder.cached_gradients),
-            pending_shadow_contents: Vec::new(),
             scrollbar_prims: recycle_vec(old_builder.scrollbar_prims),
             reference_frame_stack: Vec::new(),
             picture_stack: Vec::new(),
+            shadow_stack: Vec::new(),
             sc_stack: Vec::new(),
             prim_store: old_builder.prim_store.recycle(),
             clip_store: old_builder.clip_store.recycle(),
         };
 
         flattener.id_to_index_mapper.initialize_for_pipeline(root_pipeline);
         flattener.push_root(
             root_pipeline_id,
@@ -925,22 +918,52 @@ impl<'a> DisplayListFlattener<'a> {
     /// Convenience interface that creates a primitive entry and adds it
     /// to the draw list.
     pub fn add_primitive(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
         clip_sources: Vec<ClipSource>,
         container: PrimitiveContainer,
-    ) -> PrimitiveIndex {
-        self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
-        let prim_index = self.create_primitive(info, clip_sources, container);
+    ) {
+        if !self.shadow_stack.is_empty() {
+            // TODO(gw): Restructure this so we don't need to move the shadow
+            //           stack out (borrowck due to create_primitive below).
+            let shadow_stack = mem::replace(&mut self.shadow_stack, Vec::new());
+            for &(ref shadow, shadow_pic_index) in &shadow_stack {
+                // Offset the local rect and clip rect by the shadow offset.
+                let mut info = info.clone();
+                info.rect = info.rect.translate(&shadow.offset);
+                info.clip_rect = info.clip_rect.translate(&shadow.offset);
+
+                // Offset any local clip sources by the shadow offset.
+                let clip_sources: Vec<ClipSource> = clip_sources
+                    .iter()
+                    .map(|cs| cs.offset(&shadow.offset))
+                    .collect();
 
-        self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
-        prim_index
+                // Construct and add a primitive for the given shadow.
+                let shadow_prim_index = self.create_primitive(
+                    &info,
+                    clip_sources,
+                    container.create_shadow(shadow),
+                );
+
+                // Add the new primitive to the shadow picture.
+                let shadow_pic = &mut self.prim_store.pictures[shadow_pic_index.0];
+                shadow_pic.add_primitive(shadow_prim_index, clip_and_scroll);
+            }
+            self.shadow_stack = shadow_stack;
+        }
+
+        if container.is_visible() {
+            let prim_index = self.create_primitive(info, clip_sources, container);
+            self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
+            self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
+        }
     }
 
     pub fn push_stacking_context(
         &mut self,
         pipeline_id: PipelineId,
         composite_ops: CompositeOps,
         transform_style: TransformStyle,
         is_backface_visible: bool,
@@ -968,40 +991,34 @@ impl<'a> DisplayListFlattener<'a> {
             // This picture stores primitive runs for items on the
             // main framebuffer.
             let pic_index = self.prim_store.add_image_picture(
                 None,
                 false,
                 pipeline_id,
                 current_reference_frame_index,
                 None,
+                true,
             );
 
             self.picture_stack.push(pic_index);
         } else if composite_ops.mix_blend_mode.is_some() && self.sc_stack.len() > 2 {
             // If we have a mix-blend-mode, and we aren't the primary framebuffer,
             // the stacking context needs to be isolated to blend correctly as per
             // the CSS spec.
             // TODO(gw): The way we detect not being the primary framebuffer (len > 2)
             //           is hacky and depends on how we create a root stacking context
             //           during flattening.
             let parent_pic_index = self.picture_stack.last().unwrap();
             let parent_pic = &mut self.prim_store.pictures[parent_pic_index.0];
 
-            match parent_pic.kind {
-                PictureKind::Image { ref mut composite_mode, .. } => {
-                    // If not already isolated for some other reason,
-                    // make this picture as isolated.
-                    if composite_mode.is_none() {
-                        *composite_mode = Some(PictureCompositeMode::Blit);
-                    }
-                }
-                PictureKind::TextShadow { .. } => {
-                    panic!("bug: text pictures invalid here");
-                }
+            // If not already isolated for some other reason,
+            // make this picture as isolated.
+            if parent_pic.composite_mode.is_none() {
+                parent_pic.composite_mode = Some(PictureCompositeMode::Blit);
             }
         }
 
         // Get the transform-style of the parent stacking context,
         // which determines if we *might* need to draw this on
         // an intermediate surface for plane splitting purposes.
         let parent_transform_style = match self.sc_stack.last() {
             Some(sc) => sc.transform_style,
@@ -1030,16 +1047,17 @@ impl<'a> DisplayListFlattener<'a> {
             // that will be the container for all the planes and any
             // un-transformed content.
             let container_index = self.prim_store.add_image_picture(
                 None,
                 false,
                 pipeline_id,
                 current_reference_frame_index,
                 None,
+                true,
             );
 
             let prim = BrushPrimitive::new_picture(container_index);
 
             let prim_index = self.prim_store.add_primitive(
                 &LayerRect::zero(),
                 &max_clip,
                 is_backface_visible,
@@ -1082,16 +1100,17 @@ impl<'a> DisplayListFlattener<'a> {
         // For each filter, create a new image with that composite mode.
         for filter in composite_ops.filters.iter().rev() {
             let src_pic_index = self.prim_store.add_image_picture(
                 Some(PictureCompositeMode::Filter(*filter)),
                 false,
                 pipeline_id,
                 current_reference_frame_index,
                 None,
+                true,
             );
 
             let src_prim = BrushPrimitive::new_picture(src_pic_index);
 
             let src_prim_index = self.prim_store.add_primitive(
                 &LayerRect::zero(),
                 &max_clip,
                 is_backface_visible,
@@ -1113,16 +1132,17 @@ impl<'a> DisplayListFlattener<'a> {
         // Same for mix-blend-mode.
         if let Some(mix_blend_mode) = composite_ops.mix_blend_mode {
             let src_pic_index = self.prim_store.add_image_picture(
                 Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
                 false,
                 pipeline_id,
                 current_reference_frame_index,
                 None,
+                true,
             );
 
             let src_prim = BrushPrimitive::new_picture(src_pic_index);
 
             let src_prim_index = self.prim_store.add_primitive(
                 &LayerRect::zero(),
                 &max_clip,
                 is_backface_visible,
@@ -1166,16 +1186,17 @@ impl<'a> DisplayListFlattener<'a> {
 
         // Add picture for this actual stacking context contents to render into.
         let pic_index = self.prim_store.add_image_picture(
             composite_mode,
             participating_in_3d_context,
             pipeline_id,
             current_reference_frame_index,
             frame_output_pipeline_id,
+                true,
         );
 
         // Create a brush primitive that draws this picture.
         let sc_prim = BrushPrimitive::new_picture(pic_index);
 
         // Add the brush to the parent picture.
         let sc_prim_index = self.prim_store.add_primitive(
             &LayerRect::zero(),
@@ -1232,17 +1253,17 @@ impl<'a> DisplayListFlattener<'a> {
         // By the time the stacking context stack is empty, we should
         // also have cleared the picture stack.
         if self.sc_stack.is_empty() {
             self.picture_stack.pop().expect("bug: picture stack invalid");
             debug_assert!(self.picture_stack.is_empty());
         }
 
         assert!(
-            self.shadow_prim_stack.is_empty(),
+            self.shadow_stack.is_empty(),
             "Found unpopped text shadows when popping stacking context!"
         );
     }
 
     pub fn push_reference_frame(
         &mut self,
         reference_frame_id: ClipId,
         parent_id: Option<ClipId>,
@@ -1376,54 +1397,57 @@ impl<'a> DisplayListFlattener<'a> {
 
     pub fn push_shadow(
         &mut self,
         shadow: Shadow,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
     ) {
         let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
-        let pic_index = self.prim_store.add_shadow_picture(shadow, pipeline_id);
+        let current_reference_frame_index = self.current_reference_frame_index();
+        let max_clip = LayerRect::max_rect();
 
-        let prim = BrushPrimitive::new_picture(pic_index);
+        // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
+        // "the image that would be generated by applying to the shadow a
+        // Gaussian blur with a standard deviation equal to half the blur radius."
+        let std_deviation = shadow.blur_radius * 0.5;
 
-        // Create an empty shadow primitive. Insert it into
-        // the draw lists immediately so that it will be drawn
-        // before any visual text elements that are added as
-        // part of this shadow context.
-        let prim_index = self.create_primitive(
-            info,
-            Vec::new(),
-            PrimitiveContainer::Brush(prim),
+        // Create a picture that the shadow primitives will be added to. If the
+        // blur radius is 0, the code in Picture::prepare_for_render will
+        // detect this and mark the picture to be drawn directly into the
+        // parent picture, which avoids an intermediate surface and blur.
+        let shadow_pic_index = self.prim_store.add_image_picture(
+            Some(PictureCompositeMode::Filter(FilterOp::Blur(std_deviation))),
+            false,
+            pipeline_id,
+            current_reference_frame_index,
+            None,
+            false,
         );
 
-        let pending = vec![(prim_index, clip_and_scroll)];
-        self.shadow_prim_stack.push((prim_index, pending));
+        // Create the primitive to draw the shadow picture into the scene.
+        let shadow_prim = BrushPrimitive::new_picture(shadow_pic_index);
+        let shadow_prim_index = self.prim_store.add_primitive(
+            &LayerRect::zero(),
+            &max_clip,
+            info.is_backface_visible,
+            None,
+            None,
+            PrimitiveContainer::Brush(shadow_prim),
+        );
+
+        // Add the shadow primitive. This must be done before pushing this
+        // picture on to the shadow stack, to avoid infinite recursion!
+        self.add_primitive_to_draw_list(shadow_prim_index, clip_and_scroll);
+        self.shadow_stack.push((shadow, shadow_pic_index));
     }
 
     pub fn pop_all_shadows(&mut self) {
-        assert!(self.shadow_prim_stack.len() > 0, "popped shadows, but none were present");
-
-        // Borrowcheck dance
-        let mut shadows = mem::replace(&mut self.shadow_prim_stack, Vec::new());
-        for (_, pending_primitives) in shadows.drain(..) {
-            // Push any fast-path shadows now
-            for (prim_index, clip_and_scroll) in pending_primitives {
-                self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
-            }
-        }
-
-        let mut pending_primitives = mem::replace(&mut self.pending_shadow_contents, Vec::new());
-        for (prim_index, clip_and_scroll, info) in pending_primitives.drain(..) {
-            self.add_primitive_to_hit_testing_list(&info, clip_and_scroll);
-            self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
-        }
-
-        mem::replace(&mut self.pending_shadow_contents, pending_primitives);
-        mem::replace(&mut self.shadow_prim_stack, shadows);
+        assert!(self.shadow_stack.len() > 0, "popped shadows, but none were present");
+        self.shadow_stack.clear();
     }
 
     pub fn add_solid_rectangle(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
         color: ColorF,
         segments: Option<BrushSegmentDescriptor>,
@@ -1481,130 +1505,74 @@ impl<'a> DisplayListFlattener<'a> {
 
         let prim = BrushPrimitive::new(
             BrushKind::Solid {
                 color,
             },
             None,
         );
 
-        let prim_index = self.add_primitive(
-            clip_and_scroll,
+        let prim_index = self.create_primitive(
             info,
             Vec::new(),
             PrimitiveContainer::Brush(prim),
         );
 
+        self.add_primitive_to_draw_list(
+            prim_index,
+            clip_and_scroll,
+        );
+
         self.scrollbar_prims.push(ScrollbarPrimitive {
             prim_index,
             scroll_frame_index: scrollbar_info.0,
             frame_rect: scrollbar_info.1,
         });
     }
 
     pub fn add_line(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
         wavy_line_thickness: f32,
         orientation: LineOrientation,
         line_color: &ColorF,
         style: LineStyle,
     ) {
-        let line = BrushPrimitive::new(
-            BrushKind::Line {
-                wavy_line_thickness,
-                color: line_color.premultiplied(),
-                style,
-                orientation,
+        let prim = BrushPrimitive::new(
+            BrushKind::Solid {
+                color: *line_color,
             },
             None,
         );
 
-        let mut fast_shadow_prims = Vec::new();
-        let mut slow_shadow_prims = Vec::new();
-        for (idx, &(shadow_prim_index, _)) in self.shadow_prim_stack.iter().enumerate() {
-            let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
-            let brush = &self.prim_store.cpu_brushes[shadow_metadata.cpu_prim_index.0];
-            let pic_index = brush.get_picture_index();
-            let picture = &self.prim_store.pictures[pic_index.0];
-            match picture.kind {
-                PictureKind::TextShadow { offset, color, blur_radius, .. } => {
-                    if blur_radius == 0.0 {
-                        fast_shadow_prims.push((idx, offset, color));
-                    } else {
-                        slow_shadow_prims.push((pic_index, offset, color));
-                    }
-                }
-                _ => {}
+        let extra_clips = match style {
+            LineStyle::Solid => {
+                Vec::new()
             }
-        }
-
-        for (idx, shadow_offset, shadow_color) in fast_shadow_prims {
-            let line = BrushPrimitive::new(
-                BrushKind::Line {
-                    wavy_line_thickness,
-                    color: shadow_color.premultiplied(),
-                    style,
-                    orientation,
-                },
-                None,
-            );
-            let mut info = info.clone();
-            info.rect = info.rect.translate(&shadow_offset);
-            info.clip_rect = info.clip_rect.translate(&shadow_offset);
-            let prim_index = self.create_primitive(
-                &info,
-                Vec::new(),
-                PrimitiveContainer::Brush(line),
-            );
-            self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
-        }
+            LineStyle::Wavy |
+            LineStyle::Dotted |
+            LineStyle::Dashed => {
+                vec![
+                    ClipSource::new_line_decoration(
+                        info.rect,
+                        style,
+                        orientation,
+                        wavy_line_thickness,
+                    ),
+                ]
+            }
+        };
 
-        if line_color.a > 0.0 {
-            let prim_index = self.create_primitive(
-                &info,
-                Vec::new(),
-                PrimitiveContainer::Brush(line),
-            );
-
-            if self.shadow_prim_stack.is_empty() {
-                self.add_primitive_to_hit_testing_list(&info, clip_and_scroll);
-                self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
-            } else {
-                self.pending_shadow_contents.push((prim_index, clip_and_scroll, *info));
-            }
-        }
-
-        for (pic_index, shadow_offset, shadow_color) in slow_shadow_prims {
-            let line = BrushPrimitive::new(
-                BrushKind::Line {
-                    wavy_line_thickness,
-                    color: shadow_color.premultiplied(),
-                    style,
-                    orientation,
-                },
-                None,
-            );
-            let mut info = info.clone();
-            info.rect = info.rect.translate(&shadow_offset);
-            info.clip_rect = info.clip_rect.translate(&shadow_offset);
-            let prim_index = self.create_primitive(
-                &info,
-                Vec::new(),
-                PrimitiveContainer::Brush(line),
-            );
-
-            let picture = &mut self.prim_store.pictures[pic_index.0];
-
-            picture.add_primitive(
-                prim_index,
-                clip_and_scroll,
-            );
-        }
+        self.add_primitive(
+            clip_and_scroll,
+            info,
+            extra_clips,
+            PrimitiveContainer::Brush(prim),
+        );
     }
 
     pub fn add_border(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
         border_item: &BorderDisplayItem,
         gradient_stops: ItemRange<GradientStop>,
@@ -2113,112 +2081,22 @@ impl<'a> DisplayListFlattener<'a> {
                 glyph_count,
                 glyph_gpu_blocks: Vec::new(),
                 glyph_keys: Vec::new(),
                 offset: run_offset,
                 shadow: false,
             }
         };
 
-        // Text shadows that have a blur radius of 0 need to be rendered as normal
-        // text elements to get pixel perfect results for reftests. It's also a big
-        // performance win to avoid blurs and render target allocations where
-        // possible. For any text shadows that have zero blur, create a normal text
-        // primitive with the shadow's color and offset. These need to be added
-        // *before* the visual text primitive in order to get the correct paint
-        // order. Store them in a Vec first to work around borrowck issues.
-        // TODO(gw): Refactor to avoid having to store them in a Vec first.
-        let mut fast_shadow_prims = Vec::new();
-        let mut slow_shadow_prims = Vec::new();
-        for (idx, &(shadow_prim_index, _)) in self.shadow_prim_stack.iter().enumerate() {
-            let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
-            let brush = &self.prim_store.cpu_brushes[shadow_metadata.cpu_prim_index.0];
-            let pic_index = brush.get_picture_index();
-            let picture_prim = &self.prim_store.pictures[pic_index.0];
-            match picture_prim.kind {
-                PictureKind::TextShadow { offset, color, blur_radius, .. } => {
-                    let mut text_prim = prim.clone();
-                    text_prim.font.color = color.into();
-                    text_prim.shadow = true;
-                    text_prim.offset += offset;
-
-                    if blur_radius == 0.0 {
-                        fast_shadow_prims.push((idx, text_prim, offset));
-                    } else {
-                        text_prim.font.render_mode = text_prim
-                            .font
-                            .render_mode
-                            .limit_by(FontRenderMode::Alpha);
-
-                        slow_shadow_prims.push((pic_index, text_prim, offset, blur_radius));
-                    }
-                }
-                _ => {}
-            }
-        }
-
-        for (idx, text_prim, offset) in fast_shadow_prims {
-            let rect = prim_info.rect;
-            let mut info = prim_info.clone();
-            info.rect = rect.translate(&offset);
-            info.clip_rect = info.clip_rect.translate(&offset);
-            let prim_index = self.create_primitive(
-                &info,
-                Vec::new(),
-                PrimitiveContainer::TextRun(text_prim),
-            );
-            self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
-        }
-
-        // Only add a visual element if it can contribute to the scene.
-        if text_color.a > 0.0 {
-            // Create (and add to primitive store) the primitive that will be
-            // used for both the visual element and also the shadow(s).
-            let prim_index = self.create_primitive(
-                prim_info,
-                Vec::new(),
-                PrimitiveContainer::TextRun(prim),
-            );
-
-            if self.shadow_prim_stack.is_empty() {
-                self.add_primitive_to_hit_testing_list(prim_info, clip_and_scroll);
-                self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
-            } else {
-                self.pending_shadow_contents.push((prim_index, clip_and_scroll, *prim_info));
-            }
-        }
-
-        // Now add this primitive index to all the currently active text shadow
-        // primitives. Although we're adding the indices *after* the visual
-        // primitive here, they will still draw before the visual text, since
-        // the shadow primitive itself has been added to the draw cmd
-        // list *before* the visual element, during push_shadow. We need
-        // the primitive index of the visual element here before we can add
-        // the indices as sub-primitives to the shadow primitives.
-        for (pic_index, shadow_prim, offset, blur_radius) in slow_shadow_prims {
-            let blur_region = blur_radius * BLUR_SAMPLE_SCALE;
-
-            let rect = prim_info.rect;
-            let mut info = prim_info.clone();
-            info.rect = rect.translate(&offset).inflate(blur_region, blur_region);
-            info.clip_rect = info.clip_rect.translate(&offset);
-
-            let prim_index = self.create_primitive(
-                &info,
-                Vec::new(),
-                PrimitiveContainer::TextRun(shadow_prim),
-            );
-
-            let picture = &mut self.prim_store.pictures[pic_index.0];
-
-            picture.add_primitive(
-                prim_index,
-                clip_and_scroll,
-            );
-        }
+        self.add_primitive(
+            clip_and_scroll,
+            prim_info,
+            Vec::new(),
+            PrimitiveContainer::TextRun(prim),
+        );
     }
 
     pub fn add_image(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
         stretch_size: LayerSize,
         mut tile_spacing: LayerSize,
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -5,17 +5,17 @@
 use api::{BuiltDisplayList, ColorF, DeviceIntPoint, DeviceIntRect, DevicePixelScale};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FontRenderMode};
 use api::{LayerRect, LayerSize, PipelineId, PremultipliedColorF, WorldPoint};
 use clip::{ClipChain, ClipStore};
 use clip_scroll_node::{ClipScrollNode};
 use clip_scroll_tree::{ClipScrollNodeIndex, ClipScrollTree};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
-use gpu_types::{ClipChainRectIndex, ClipScrollNodeData, PictureType};
+use gpu_types::{ClipChainRectIndex, ClipScrollNodeData};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap};
 use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveRun, PrimitiveStore};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_backend::FrameId;
 use render_task::{ClearMode, RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
@@ -69,16 +69,17 @@ pub struct FrameBuildingState<'a> {
 
 pub struct PictureContext<'a> {
     pub pipeline_id: PipelineId,
     pub prim_runs: Vec<PrimitiveRun>,
     pub original_reference_frame_index: Option<ClipScrollNodeIndex>,
     pub display_list: &'a BuiltDisplayList,
     pub inv_world_transform: Option<WorldToLayerFastTransform>,
     pub apply_local_clip_rect: bool,
+    pub inflation_factor: f32,
 }
 
 pub struct PictureState {
     pub tasks: Vec<RenderTaskId>,
 }
 
 impl PictureState {
     pub fn new() -> PictureState {
@@ -198,16 +199,17 @@ impl FrameBuilder {
 
         let pic_context = PictureContext {
             pipeline_id: root_clip_scroll_node.pipeline_id,
             prim_runs: mem::replace(&mut self.prim_store.pictures[0].runs, Vec::new()),
             original_reference_frame_index: None,
             display_list,
             inv_world_transform: None,
             apply_local_clip_rect: true,
+            inflation_factor: 0.0,
         };
 
         let mut pic_state = PictureState::new();
 
         self.prim_store.reset_prim_visibility();
         self.prim_store.prepare_prim_runs(
             &pic_context,
             &mut pic_state,
@@ -221,17 +223,16 @@ impl FrameBuilder {
         let root_render_task = RenderTask::new_picture(
             RenderTaskLocation::Fixed(frame_context.screen_rect),
             PrimitiveIndex(0),
             RenderTargetKind::Color,
             DeviceIntPoint::zero(),
             PremultipliedColorF::TRANSPARENT,
             ClearMode::Transparent,
             pic_state.tasks,
-            PictureType::Image,
         );
 
         let render_task_id = frame_state.render_tasks.add(root_render_task);
         pic.surface = Some(render_task_id);
         Some(render_task_id)
     }
 
     fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache) {
@@ -305,17 +306,17 @@ impl FrameBuilder {
             gpu_cache,
             pan,
             &mut node_data,
             scene_properties,
         );
 
         self.update_scroll_bars(clip_scroll_tree, gpu_cache);
 
-        let mut render_tasks = RenderTaskTree::new();
+        let mut render_tasks = RenderTaskTree::new(frame_id);
 
         let main_render_task_id = self.build_layer_screen_rects_and_cull_layers(
             clip_scroll_tree,
             pipelines,
             resource_cache,
             gpu_cache,
             &mut render_tasks,
             &mut profile_counters,
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -4,16 +4,38 @@
 
 use api::{DevicePoint, LayerToWorldTransform, WorldToLayerTransform};
 use gpu_cache::{GpuCacheAddress, GpuDataRequest};
 use prim_store::EdgeAaSegmentMask;
 use render_task::RenderTaskAddress;
 
 // Contains type that must exactly match the same structures declared in GLSL.
 
+#[derive(Copy, Clone, Debug)]
+#[repr(C)]
+pub struct ZBufferId(i32);
+
+pub struct ZBufferIdGenerator {
+    next: i32,
+}
+
+impl ZBufferIdGenerator {
+    pub fn new() -> ZBufferIdGenerator {
+        ZBufferIdGenerator {
+            next: 0
+        }
+    }
+
+    pub fn next(&mut self) -> ZBufferId {
+        let id = ZBufferId(self.next);
+        self.next += 1;
+        id
+    }
+}
+
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub enum RasterizationSpace {
     Local = 0,
     Screen = 1,
 }
@@ -70,73 +92,73 @@ pub struct PrimitiveInstance {
 }
 
 pub struct SimplePrimitiveInstance {
     pub specific_prim_address: GpuCacheAddress,
     pub task_address: RenderTaskAddress,
     pub clip_task_address: RenderTaskAddress,
     pub clip_chain_rect_index: ClipChainRectIndex,
     pub scroll_id: ClipScrollNodeIndex,
-    pub z_sort_index: i32,
+    pub z: ZBufferId,
 }
 
 impl SimplePrimitiveInstance {
     pub fn new(
         specific_prim_address: GpuCacheAddress,
         task_address: RenderTaskAddress,
         clip_task_address: RenderTaskAddress,
         clip_chain_rect_index: ClipChainRectIndex,
         scroll_id: ClipScrollNodeIndex,
-        z_sort_index: i32,
+        z: ZBufferId,
     ) -> Self {
         SimplePrimitiveInstance {
             specific_prim_address,
             task_address,
             clip_task_address,
             clip_chain_rect_index,
             scroll_id,
-            z_sort_index,
+            z,
         }
     }
 
     pub fn build(&self, data0: i32, data1: i32, data2: i32) -> PrimitiveInstance {
         PrimitiveInstance {
             data: [
                 self.specific_prim_address.as_int(),
                 self.task_address.0 as i32,
                 self.clip_task_address.0 as i32,
                 ((self.clip_chain_rect_index.0 as i32) << 16) | self.scroll_id.0 as i32,
-                self.z_sort_index,
+                self.z.0,
                 data0,
                 data1,
                 data2,
             ],
         }
     }
 }
 
 pub struct CompositePrimitiveInstance {
     pub task_address: RenderTaskAddress,
     pub src_task_address: RenderTaskAddress,
     pub backdrop_task_address: RenderTaskAddress,
     pub data0: i32,
     pub data1: i32,
-    pub z: i32,
+    pub z: ZBufferId,
     pub data2: i32,
     pub data3: i32,
 }
 
 impl CompositePrimitiveInstance {
     pub fn new(
         task_address: RenderTaskAddress,
         src_task_address: RenderTaskAddress,
         backdrop_task_address: RenderTaskAddress,
         data0: i32,
         data1: i32,
-        z: i32,
+        z: ZBufferId,
         data2: i32,
         data3: i32,
     ) -> Self {
         CompositePrimitiveInstance {
             task_address,
             src_task_address,
             backdrop_task_address,
             data0,
@@ -150,17 +172,17 @@ impl CompositePrimitiveInstance {
 
 impl From<CompositePrimitiveInstance> for PrimitiveInstance {
     fn from(instance: CompositePrimitiveInstance) -> Self {
         PrimitiveInstance {
             data: [
                 instance.task_address.0 as i32,
                 instance.src_task_address.0 as i32,
                 instance.backdrop_task_address.0 as i32,
-                instance.z,
+                instance.z.0,
                 instance.data0,
                 instance.data1,
                 instance.data2,
                 instance.data3,
             ],
         }
     }
 }
@@ -182,31 +204,31 @@ bitflags! {
 //           a u16 type.
 #[repr(C)]
 pub struct BrushInstance {
     pub picture_address: RenderTaskAddress,
     pub prim_address: GpuCacheAddress,
     pub clip_chain_rect_index: ClipChainRectIndex,
     pub scroll_id: ClipScrollNodeIndex,
     pub clip_task_address: RenderTaskAddress,
-    pub z: i32,
+    pub z: ZBufferId,
     pub segment_index: i32,
     pub edge_flags: EdgeAaSegmentMask,
     pub brush_flags: BrushFlags,
     pub user_data: [i32; 3],
 }
 
 impl From<BrushInstance> for PrimitiveInstance {
     fn from(instance: BrushInstance) -> Self {
         PrimitiveInstance {
             data: [
                 instance.picture_address.0 as i32 | (instance.clip_task_address.0 as i32) << 16,
                 instance.prim_address.as_int(),
                 ((instance.clip_chain_rect_index.0 as i32) << 16) | instance.scroll_id.0 as i32,
-                instance.z,
+                instance.z.0,
                 instance.segment_index |
                     ((instance.edge_flags.bits() as i32) << 16) |
                     ((instance.brush_flags.bits() as i32) << 24),
                 instance.user_data[0],
                 instance.user_data[1],
                 instance.user_data[2],
             ]
         }
@@ -240,25 +262,16 @@ impl ClipScrollNodeData {
         }
     }
 }
 
 #[derive(Copy, Debug, Clone, PartialEq)]
 #[repr(C)]
 pub struct ClipChainRectIndex(pub usize);
 
-#[derive(Copy, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-#[repr(C)]
-pub enum PictureType {
-    Image = 1,
-    TextShadow = 2,
-}
-
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub struct ImageSource {
     pub p0: DevicePoint,
     pub p1: DevicePoint,
     pub texture_layer: f32,
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -297,18 +297,19 @@ fn get_regions_for_clip_scroll_node(
 
     clips.iter().map(|ref source| {
         match source.0 {
             ClipSource::Rectangle(ref rect) => HitTestRegion::Rectangle(*rect),
             ClipSource::RoundedRectangle(ref rect, ref radii, ref mode) =>
                 HitTestRegion::RoundedRectangle(*rect, *radii, *mode),
             ClipSource::Image(ref mask) => HitTestRegion::Rectangle(mask.rect),
             ClipSource::BorderCorner(_) |
+            ClipSource::LineDecoration(_) |
             ClipSource::BoxShadow(_) => {
-                unreachable!("Didn't expect to hit test against BorderCorner / BoxShadow");
+                unreachable!("Didn't expect to hit test against BorderCorner / BoxShadow / LineDecoration");
             }
         }
     }).collect()
 }
 
 pub struct HitTest {
     pipeline_id: Option<PipelineId>,
     point: WorldPoint,
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -1,20 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ColorF, FilterOp, MixBlendMode, PipelineId};
-use api::{DeviceIntRect, LayerRect, LayerToWorldScale, LayerVector2D};
-use api::{PremultipliedColorF, Shadow};
+use api::{FilterOp, MixBlendMode, PipelineId, PremultipliedColorF};
+use api::{DeviceIntRect, LayerRect, LayerToWorldScale};
 use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip_scroll_tree::ClipScrollNodeIndex;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState};
 use gpu_cache::{GpuCacheHandle, GpuDataRequest};
-use gpu_types::{PictureType};
 use prim_store::{PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
 use prim_store::{PrimitiveMetadata, ScrollNodeAndClipChain};
 use render_task::{ClearMode, RenderTask};
 use render_task::{RenderTaskId, RenderTaskLocation};
 use scene::{FilterOpHelpers, SceneProperties};
 use tiling::RenderTargetKind;
 
 /*
@@ -36,126 +34,98 @@ pub enum PictureCompositeMode {
     /// Apply a CSS filter.
     Filter(FilterOp),
     /// Draw to intermediate surface, copy straight across. This
     /// is used for CSS isolation, and plane splitting.
     Blit,
 }
 
 #[derive(Debug)]
-pub enum PictureKind {
-    TextShadow {
-        offset: LayerVector2D,
-        color: ColorF,
-        blur_radius: f32,
-    },
-    Image {
-        // If a mix-blend-mode, contains the render task for
-        // the readback of the framebuffer that we use to sample
-        // from in the mix-blend-mode shader.
-        // For drop-shadow filter, this will store the original
-        // picture task which would be rendered on screen after
-        // blur pass.
-        secondary_render_task_id: Option<RenderTaskId>,
-        /// How this picture should be composited.
-        /// If None, don't composite - just draw directly on parent surface.
-        composite_mode: Option<PictureCompositeMode>,
-        // If true, this picture is part of a 3D context.
-        is_in_3d_context: bool,
-        // If requested as a frame output (for rendering
-        // pages to a texture), this is the pipeline this
-        // picture is the root of.
-        frame_output_pipeline_id: Option<PipelineId>,
-        // The original reference frame ID for this picture.
-        // It is only different if this is part of a 3D
-        // rendering context.
-        reference_frame_index: ClipScrollNodeIndex,
-        real_local_rect: LayerRect,
-        // An optional cache handle for storing extra data
-        // in the GPU cache, depending on the type of
-        // picture.
-        extra_gpu_data_handle: GpuCacheHandle,
-    },
-}
-
-#[derive(Debug)]
 pub struct PicturePrimitive {
     // If this picture is drawn to an intermediate surface,
     // the associated target information.
     pub surface: Option<RenderTaskId>,
 
-    // Details specific to this type of picture.
-    pub kind: PictureKind,
-
     // List of primitive runs that make up this picture.
     pub runs: Vec<PrimitiveRun>,
 
     // The pipeline that the primitives on this picture belong to.
     pub pipeline_id: PipelineId,
 
+    // If true, apply the local clip rect to primitive drawn
+    // in this picture.
+    pub apply_local_clip_rect: bool,
+
     // The current screen-space rect of the rendered
     // portion of this picture.
     task_rect: DeviceIntRect,
+
+    // If a mix-blend-mode, contains the render task for
+    // the readback of the framebuffer that we use to sample
+    // from in the mix-blend-mode shader.
+    // For drop-shadow filter, this will store the original
+    // picture task which would be rendered on screen after
+    // blur pass.
+    pub secondary_render_task_id: Option<RenderTaskId>,
+    /// How this picture should be composited.
+    /// If None, don't composite - just draw directly on parent surface.
+    pub composite_mode: Option<PictureCompositeMode>,
+    // If true, this picture is part of a 3D context.
+    pub is_in_3d_context: bool,
+    // If requested as a frame output (for rendering
+    // pages to a texture), this is the pipeline this
+    // picture is the root of.
+    pub frame_output_pipeline_id: Option<PipelineId>,
+    // The original reference frame ID for this picture.
+    // It is only different if this is part of a 3D
+    // rendering context.
+    pub reference_frame_index: ClipScrollNodeIndex,
+    pub real_local_rect: LayerRect,
+    // An optional cache handle for storing extra data
+    // in the GPU cache, depending on the type of
+    // picture.
+    pub extra_gpu_data_handle: GpuCacheHandle,
 }
 
 impl PicturePrimitive {
-    pub fn new_text_shadow(shadow: Shadow, pipeline_id: PipelineId) -> Self {
-        PicturePrimitive {
-            runs: Vec::new(),
-            surface: None,
-            kind: PictureKind::TextShadow {
-                offset: shadow.offset,
-                color: shadow.color,
-                blur_radius: shadow.blur_radius,
-            },
-            pipeline_id,
-            task_rect: DeviceIntRect::zero(),
-        }
-    }
+    pub fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool {
+        match self.composite_mode {
+            Some(PictureCompositeMode::Filter(ref mut filter)) => {
+                match filter {
+                    &mut FilterOp::Opacity(ref binding, ref mut value) => {
+                        *value = properties.resolve_float(binding, *value);
+                    }
+                    _ => {}
+                }
 
-    pub fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool {
-        match self.kind {
-            PictureKind::Image { ref mut composite_mode, .. } => {
-                match composite_mode {
-                    &mut Some(PictureCompositeMode::Filter(ref mut filter)) => {
-                        match filter {
-                            &mut FilterOp::Opacity(ref binding, ref mut value) => {
-                                *value = properties.resolve_float(binding, *value);
-                            }
-                            _ => {}
-                        }
-
-                        filter.is_visible()
-                    }
-                    _ => true,
-                }
+                filter.is_visible()
             }
-            _ => true
+            _ => true,
         }
     }
 
     pub fn new_image(
         composite_mode: Option<PictureCompositeMode>,
         is_in_3d_context: bool,
         pipeline_id: PipelineId,
         reference_frame_index: ClipScrollNodeIndex,
         frame_output_pipeline_id: Option<PipelineId>,
+        apply_local_clip_rect: bool,
     ) -> Self {
         PicturePrimitive {
             runs: Vec::new(),
             surface: None,
-            kind: PictureKind::Image {
-                secondary_render_task_id: None,
-                composite_mode,
-                is_in_3d_context,
-                frame_output_pipeline_id,
-                reference_frame_index,
-                real_local_rect: LayerRect::zero(),
-                extra_gpu_data_handle: GpuCacheHandle::new(),
-            },
+            secondary_render_task_id: None,
+            composite_mode,
+            is_in_3d_context,
+            frame_output_pipeline_id,
+            reference_frame_index,
+            real_local_rect: LayerRect::zero(),
+            extra_gpu_data_handle: GpuCacheHandle::new(),
+            apply_local_clip_rect,
             pipeline_id,
             task_rect: DeviceIntRect::zero(),
         }
     }
 
     pub fn add_primitive(
         &mut self,
         prim_index: PrimitiveIndex,
@@ -177,42 +147,30 @@ impl PicturePrimitive {
     }
 
     pub fn update_local_rect(
         &mut self,
         prim_run_rect: PrimitiveRunLocalRect,
     ) -> LayerRect {
         let local_content_rect = prim_run_rect.local_rect_in_actual_parent_space;
 
-        match self.kind {
-            PictureKind::Image { composite_mode, ref mut real_local_rect, .. } => {
-                *real_local_rect = prim_run_rect.local_rect_in_original_parent_space;
+        self.real_local_rect = prim_run_rect.local_rect_in_original_parent_space;
 
-                match composite_mode {
-                    Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
-                        let inflate_size = blur_radius * BLUR_SAMPLE_SCALE;
-                        local_content_rect.inflate(inflate_size, inflate_size)
-                    }
-                    Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, _))) => {
-                        let inflate_size = blur_radius * BLUR_SAMPLE_SCALE;
-                        local_content_rect.inflate(inflate_size, inflate_size)
-                                          .translate(&offset)
-                    }
-                    _ => {
-                        local_content_rect
-                    }
-                }
+        match self.composite_mode {
+            Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
+                let inflate_size = (blur_radius * BLUR_SAMPLE_SCALE).ceil();
+                local_content_rect.inflate(inflate_size, inflate_size)
             }
-            PictureKind::TextShadow { blur_radius, .. } => {
-                let blur_offset = blur_radius * BLUR_SAMPLE_SCALE;
-
-                local_content_rect.inflate(
-                    blur_offset,
-                    blur_offset,
-                )
+            Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, _))) => {
+                let inflate_size = blur_radius * BLUR_SAMPLE_SCALE;
+                local_content_rect.inflate(inflate_size, inflate_size)
+                                  .translate(&offset)
+            }
+            _ => {
+                local_content_rect
             }
         }
     }
 
     pub fn prepare_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_metadata: &mut PrimitiveMetadata,
@@ -221,274 +179,206 @@ impl PicturePrimitive {
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) {
         let content_scale = LayerToWorldScale::new(1.0) * frame_context.device_pixel_scale;
         let prim_screen_rect = prim_metadata
                                 .screen_rect
                                 .as_ref()
                                 .expect("bug: trying to draw an off-screen picture!?");
-        let device_rect;
-
-        match self.kind {
-            PictureKind::Image {
-                ref mut secondary_render_task_id,
-                ref mut extra_gpu_data_handle,
-                composite_mode,
-                ..
-            } => {
-                device_rect = match composite_mode {
-                    Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
-                        // If blur radius is 0, we can skip drawing this an an
-                        // intermediate surface.
-                        if blur_radius == 0.0 {
-                            pic_state.tasks.extend(pic_state_for_children.tasks);
-                            self.surface = None;
-
-                            DeviceIntRect::zero()
-                        } else {
-                            let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
-                            let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
+        let device_rect = match self.composite_mode {
+            Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
+                // If blur radius is 0, we can skip drawing this an an
+                // intermediate surface.
+                if blur_radius == 0.0 {
+                    pic_state.tasks.extend(pic_state_for_children.tasks);
+                    self.surface = None;
 
-                            // The clipped field is the part of the picture that is visible
-                            // on screen. The unclipped field is the screen-space rect of
-                            // the complete picture, if no screen / clip-chain was applied
-                            // (this includes the extra space for blur region). To ensure
-                            // that we draw a large enough part of the picture to get correct
-                            // blur results, inflate that clipped area by the blur range, and
-                            // then intersect with the total screen rect, to minimize the
-                            // allocation size.
-                            let device_rect = prim_screen_rect
-                                .clipped
-                                .inflate(blur_range, blur_range)
-                                .intersection(&prim_screen_rect.unclipped)
-                                .unwrap();
-
-                            let picture_task = RenderTask::new_picture(
-                                RenderTaskLocation::Dynamic(None, device_rect.size),
-                                prim_index,
-                                RenderTargetKind::Color,
-                                device_rect.origin,
-                                PremultipliedColorF::TRANSPARENT,
-                                ClearMode::Transparent,
-                                pic_state_for_children.tasks,
-                                PictureType::Image,
-                            );
+                    DeviceIntRect::zero()
+                } else {
+                    let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
+                    let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
 
-                            let picture_task_id = frame_state.render_tasks.add(picture_task);
-
-                            let blur_render_task = RenderTask::new_blur(
-                                blur_std_deviation,
-                                picture_task_id,
-                                frame_state.render_tasks,
-                                RenderTargetKind::Color,
-                                ClearMode::Transparent,
-                            );
-
-                            let render_task_id = frame_state.render_tasks.add(blur_render_task);
-                            pic_state.tasks.push(render_task_id);
-                            self.surface = Some(render_task_id);
-
-                            device_rect
-                        }
-                    }
-                    Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, _))) => {
-                        // TODO(gw): This is totally wrong and can never work with
-                        //           transformed drop-shadow elements. Fix me!
-                        let rect = (prim_metadata.local_rect.translate(&-offset) * content_scale).round().to_i32();
-                        let mut picture_task = RenderTask::new_picture(
-                            RenderTaskLocation::Dynamic(None, rect.size),
-                            prim_index,
-                            RenderTargetKind::Color,
-                            rect.origin,
-                            PremultipliedColorF::TRANSPARENT,
-                            ClearMode::Transparent,
-                            pic_state_for_children.tasks,
-                            PictureType::Image,
-                        );
-                        picture_task.mark_for_saving();
-
-                        let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
-                        let picture_task_id = frame_state.render_tasks.add(picture_task);
-
-                        let blur_render_task = RenderTask::new_blur(
-                            blur_std_deviation.round(),
-                            picture_task_id,
-                            frame_state.render_tasks,
-                            RenderTargetKind::Color,
-                            ClearMode::Transparent,
-                        );
-
-                        *secondary_render_task_id = Some(picture_task_id);
-
-                        let render_task_id = frame_state.render_tasks.add(blur_render_task);
-                        pic_state.tasks.push(render_task_id);
-                        self.surface = Some(render_task_id);
+                    // The clipped field is the part of the picture that is visible
+                    // on screen. The unclipped field is the screen-space rect of
+                    // the complete picture, if no screen / clip-chain was applied
+                    // (this includes the extra space for blur region). To ensure
+                    // that we draw a large enough part of the picture to get correct
+                    // blur results, inflate that clipped area by the blur range, and
+                    // then intersect with the total screen rect, to minimize the
+                    // allocation size.
+                    let device_rect = prim_screen_rect
+                        .clipped
+                        .inflate(blur_range, blur_range)
+                        .intersection(&prim_screen_rect.unclipped)
+                        .unwrap();
 
-                        rect
-                    }
-                    Some(PictureCompositeMode::MixBlend(..)) => {
-                        let picture_task = RenderTask::new_picture(
-                            RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
-                            prim_index,
-                            RenderTargetKind::Color,
-                            prim_screen_rect.clipped.origin,
-                            PremultipliedColorF::TRANSPARENT,
-                            ClearMode::Transparent,
-                            pic_state_for_children.tasks,
-                            PictureType::Image,
-                        );
-
-                        let readback_task_id = frame_state.render_tasks.add(
-                            RenderTask::new_readback(prim_screen_rect.clipped)
-                        );
-
-                        *secondary_render_task_id = Some(readback_task_id);
-                        pic_state.tasks.push(readback_task_id);
+                    let picture_task = RenderTask::new_picture(
+                        RenderTaskLocation::Dynamic(None, device_rect.size),
+                        prim_index,
+                        RenderTargetKind::Color,
+                        device_rect.origin,
+                        PremultipliedColorF::TRANSPARENT,
+                        ClearMode::Transparent,
+                        pic_state_for_children.tasks,
+                    );
 
-                        let render_task_id = frame_state.render_tasks.add(picture_task);
-                        pic_state.tasks.push(render_task_id);
-                        self.surface = Some(render_task_id);
-
-                        prim_screen_rect.clipped
-                    }
-                    Some(PictureCompositeMode::Filter(filter)) => {
-                        // If this filter is not currently going to affect
-                        // the picture, just collapse this picture into the
-                        // current render task. This most commonly occurs
-                        // when opacity == 1.0, but can also occur on other
-                        // filters and be a significant performance win.
-                        if filter.is_noop() {
-                            pic_state.tasks.extend(pic_state_for_children.tasks);
-                            self.surface = None;
-                        } else {
-
-                            if let FilterOp::ColorMatrix(m) = filter {
-                                if let Some(mut request) = frame_state.gpu_cache.request(extra_gpu_data_handle) {
-                                    for i in 0..5 {
-                                        request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
-                                    }
-                                }
-                            }
+                    let picture_task_id = frame_state.render_tasks.add(picture_task);
 
-                            let picture_task = RenderTask::new_picture(
-                                RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
-                                prim_index,
-                                RenderTargetKind::Color,
-                                prim_screen_rect.clipped.origin,
-                                PremultipliedColorF::TRANSPARENT,
-                                ClearMode::Transparent,
-                                pic_state_for_children.tasks,
-                                PictureType::Image,
-                            );
-
-                            let render_task_id = frame_state.render_tasks.add(picture_task);
-                            pic_state.tasks.push(render_task_id);
-                            self.surface = Some(render_task_id);
-                        }
-
-                        prim_screen_rect.clipped
-                    }
-                    Some(PictureCompositeMode::Blit) => {
-                        let picture_task = RenderTask::new_picture(
-                            RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
-                            prim_index,
-                            RenderTargetKind::Color,
-                            prim_screen_rect.clipped.origin,
-                            PremultipliedColorF::TRANSPARENT,
-                            ClearMode::Transparent,
-                            pic_state_for_children.tasks,
-                            PictureType::Image,
-                        );
+                    let blur_render_task = RenderTask::new_blur(
+                        blur_std_deviation,
+                        picture_task_id,
+                        frame_state.render_tasks,
+                        RenderTargetKind::Color,
+                        ClearMode::Transparent,
+                    );
 
-                        let render_task_id = frame_state.render_tasks.add(picture_task);
-                        pic_state.tasks.push(render_task_id);
-                        self.surface = Some(render_task_id);
+                    let render_task_id = frame_state.render_tasks.add(blur_render_task);
+                    pic_state.tasks.push(render_task_id);
+                    self.surface = Some(render_task_id);
 
-                        prim_screen_rect.clipped
-                    }
-                    None => {
-                        pic_state.tasks.extend(pic_state_for_children.tasks);
-                        self.surface = None;
-
-                        DeviceIntRect::zero()
-                    }
-                };
+                    device_rect
+                }
             }
-            PictureKind::TextShadow { blur_radius, color, .. } => {
-                // This is a shadow element. Create a render task that will
-                // render the text run to a target, and then apply a gaussian
-                // blur to that text run in order to build the actual primitive
-                // which will be blitted to the framebuffer.
-
-                // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
-                // "the image that would be generated by applying to the shadow a
-                // Gaussian blur with a standard deviation equal to half the blur radius."
-                let device_radius = (blur_radius * frame_context.device_pixel_scale.0).round();
-                let blur_std_deviation = device_radius * 0.5;
-
-                let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
-
-                device_rect = prim_screen_rect
-                    .clipped
-                    .inflate(blur_range, blur_range)
-                    .intersection(&prim_screen_rect.unclipped)
-                    .unwrap();
-
-                let picture_task = RenderTask::new_picture(
-                    RenderTaskLocation::Dynamic(None, device_rect.size),
+            Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, _))) => {
+                // TODO(gw): This is totally wrong and can never work with
+                //           transformed drop-shadow elements. Fix me!
+                let rect = (prim_metadata.local_rect.translate(&-offset) * content_scale).round().to_i32();
+                let mut picture_task = RenderTask::new_picture(
+                    RenderTaskLocation::Dynamic(None, rect.size),
                     prim_index,
                     RenderTargetKind::Color,
-                    device_rect.origin,
-                    color.premultiplied(),
+                    rect.origin,
+                    PremultipliedColorF::TRANSPARENT,
                     ClearMode::Transparent,
-                    Vec::new(),
-                    PictureType::TextShadow,
+                    pic_state_for_children.tasks,
                 );
+                picture_task.mark_for_saving();
 
+                let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
                 let picture_task_id = frame_state.render_tasks.add(picture_task);
 
                 let blur_render_task = RenderTask::new_blur(
-                    blur_std_deviation,
+                    blur_std_deviation.round(),
                     picture_task_id,
                     frame_state.render_tasks,
                     RenderTargetKind::Color,
                     ClearMode::Transparent,
                 );
 
+                self.secondary_render_task_id = Some(picture_task_id);
+
                 let render_task_id = frame_state.render_tasks.add(blur_render_task);
                 pic_state.tasks.push(render_task_id);
                 self.surface = Some(render_task_id);
+
+                rect
             }
-        }
+            Some(PictureCompositeMode::MixBlend(..)) => {
+                let picture_task = RenderTask::new_picture(
+                    RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
+                    prim_index,
+                    RenderTargetKind::Color,
+                    prim_screen_rect.clipped.origin,
+                    PremultipliedColorF::TRANSPARENT,
+                    ClearMode::Transparent,
+                    pic_state_for_children.tasks,
+                );
+
+                let readback_task_id = frame_state.render_tasks.add(
+                    RenderTask::new_readback(prim_screen_rect.clipped)
+                );
+
+                self.secondary_render_task_id = Some(readback_task_id);
+                pic_state.tasks.push(readback_task_id);
+
+                let render_task_id = frame_state.render_tasks.add(picture_task);
+                pic_state.tasks.push(render_task_id);
+                self.surface = Some(render_task_id);
+
+                prim_screen_rect.clipped
+            }
+            Some(PictureCompositeMode::Filter(filter)) => {
+                // If this filter is not currently going to affect
+                // the picture, just collapse this picture into the
+                // current render task. This most commonly occurs
+                // when opacity == 1.0, but can also occur on other
+                // filters and be a significant performance win.
+                if filter.is_noop() {
+                    pic_state.tasks.extend(pic_state_for_children.tasks);
+                    self.surface = None;
+                } else {
+
+                    if let FilterOp::ColorMatrix(m) = filter {
+                        if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
+                            for i in 0..5 {
+                                request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
+                            }
+                        }
+                    }
+
+                    let picture_task = RenderTask::new_picture(
+                        RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
+                        prim_index,
+                        RenderTargetKind::Color,
+                        prim_screen_rect.clipped.origin,
+                        PremultipliedColorF::TRANSPARENT,
+                        ClearMode::Transparent,
+                        pic_state_for_children.tasks,
+                    );
+
+                    let render_task_id = frame_state.render_tasks.add(picture_task);
+                    pic_state.tasks.push(render_task_id);
+                    self.surface = Some(render_task_id);
+                }
+
+                prim_screen_rect.clipped
+            }
+            Some(PictureCompositeMode::Blit) => {
+                let picture_task = RenderTask::new_picture(
+                    RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
+                    prim_index,
+                    RenderTargetKind::Color,
+                    prim_screen_rect.clipped.origin,
+                    PremultipliedColorF::TRANSPARENT,
+                    ClearMode::Transparent,
+                    pic_state_for_children.tasks,
+                );
+
+                let render_task_id = frame_state.render_tasks.add(picture_task);
+                pic_state.tasks.push(render_task_id);
+                self.surface = Some(render_task_id);
+
+                prim_screen_rect.clipped
+            }
+            None => {
+                pic_state.tasks.extend(pic_state_for_children.tasks);
+                self.surface = None;
+
+                DeviceIntRect::zero()
+            }
+        };
 
         // If scrolling or property animation has resulted in the task
         // rect being different than last time, invalidate the GPU
         // cache entry for this picture to ensure that the correct
         // task rect is provided to the image shader.
         if self.task_rect != device_rect {
             frame_state.gpu_cache.invalidate(&prim_metadata.gpu_location);
             self.task_rect = device_rect;
         }
     }
 
     pub fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
         request.push(self.task_rect.to_f32());
 
-        match self.kind {
-            PictureKind::TextShadow { .. } => {
-                request.push(PremultipliedColorF::WHITE);
+        let color = match self.composite_mode {
+            Some(PictureCompositeMode::Filter(FilterOp::DropShadow(_, _, color))) => {
+                color.premultiplied()
             }
-            PictureKind::Image { composite_mode, .. } => {
-                let color = match composite_mode {
-                    Some(PictureCompositeMode::Filter(FilterOp::DropShadow(_, _, color))) => {
-                        color.premultiplied()
-                    }
-                    _ => {
-                        PremultipliedColorF::WHITE
-                    }
-                };
+            _ => {
+                PremultipliedColorF::WHITE
+            }
+        };
 
-                request.push(color);
-            }
-        }
+        request.push(color);
     }
 }
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,29 +1,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, BorderRadius, BoxShadowClipMode, BuiltDisplayList, ClipMode, ColorF, ComplexClipRegion};
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch, ExtendMode, FontRenderMode};
-use api::{GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag};
-use api::{LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LayerVector2D, LineOrientation};
-use api::{LineStyle, PipelineId, PremultipliedColorF, Shadow, YuvColorSpace, YuvFormat};
+use api::{FilterOp, GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag};
+use api::{LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LayerVector2D};
+use api::{PipelineId, PremultipliedColorF, Shadow, YuvColorSpace, YuvFormat};
 use border::{BorderCornerInstance, BorderEdgeKind};
+use box_shadow::BLUR_SAMPLE_SCALE;
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
 use clip::{ClipSourcesHandle, ClipWorkItem};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveRunContext;
 use glyph_rasterizer::{FontInstance, FontTransform};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::{ClipChainRectIndex};
-use picture::{PictureCompositeMode, PictureKind, PicturePrimitive};
+use picture::{PictureCompositeMode, PicturePrimitive};
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey, RenderTaskCacheKeyKind};
 use render_task::RenderTaskId;
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{CacheItem, ImageProperties, ImageRequest, ResourceCache};
 use segment::SegmentBuilder;
 use std::{mem, usize};
 use std::sync::Arc;
 use util::{MatrixHelpers, WorldToLayerFastTransform, calculate_screen_bounding_rect};
@@ -193,22 +194,16 @@ pub struct PrimitiveMetadata {
 }
 
 #[derive(Debug)]
 pub enum BrushKind {
     Solid {
         color: ColorF,
     },
     Clear,
-    Line {
-        color: PremultipliedColorF,
-        wavy_line_thickness: f32,
-        style: LineStyle,
-        orientation: LineOrientation,
-    },
     Picture {
         pic_index: PictureIndex,
     },
     Image {
         request: ImageRequest,
         current_epoch: Epoch,
         alpha_type: AlphaType,
     },
@@ -243,18 +238,17 @@ impl BrushKind {
         match *self {
             BrushKind::Solid { .. } |
             BrushKind::Picture { .. } |
             BrushKind::Image { .. } |
             BrushKind::YuvImage { .. } |
             BrushKind::RadialGradient { .. } |
             BrushKind::LinearGradient { .. } => true,
 
-            BrushKind::Clear |
-            BrushKind::Line { .. } => false,
+            BrushKind::Clear => false,
         }
     }
 }
 
 bitflags! {
     /// Each bit of the edge AA mask is:
     /// 0, when the edge of the primitive needs to be considered for AA
     /// 1, when the edge of the segment needs to be considered for AA
@@ -350,25 +344,16 @@ impl BrushPrimitive {
             }
             BrushKind::Solid { color } => {
                 request.push(color.premultiplied());
             }
             BrushKind::Clear => {
                 // Opaque black with operator dest out
                 request.push(PremultipliedColorF::BLACK);
             }
-            BrushKind::Line { color, wavy_line_thickness, style, orientation } => {
-                request.push(color);
-                request.push([
-                    wavy_line_thickness,
-                    pack_as_float(style as u32),
-                    pack_as_float(orientation as u32),
-                    0.0,
-                ]);
-            }
             BrushKind::LinearGradient { start_point, end_point, extend_mode, .. } => {
                 request.push([
                     start_point.x,
                     start_point.y,
                     end_point.x,
                     end_point.y,
                 ]);
                 request.push([
@@ -914,16 +899,105 @@ impl ClipData {
 #[derive(Debug)]
 pub enum PrimitiveContainer {
     TextRun(TextRunPrimitiveCpu),
     Image(ImagePrimitiveCpu),
     Border(BorderPrimitiveCpu),
     Brush(BrushPrimitive),
 }
 
+impl PrimitiveContainer {
+    // Return true if the primary primitive is visible.
+    // Used to trivially reject non-visible primitives.
+    // TODO(gw): Currently, primitives other than those
+    //           listed here are handled before the
+    //           add_primitive() call. In the future
+    //           we should move the logic for all other
+    //           primitive types to use this.
+    pub fn is_visible(&self) -> bool {
+        match *self {
+            PrimitiveContainer::TextRun(ref info) => {
+                info.font.color.a > 0
+            }
+            PrimitiveContainer::Brush(ref brush) => {
+                match brush.kind {
+                    BrushKind::Solid { ref color } => {
+                        color.a > 0.0
+                    }
+                    BrushKind::Clear |
+                    BrushKind::Picture { .. } |
+                    BrushKind::Image { .. } |
+                    BrushKind::YuvImage { .. } |
+                    BrushKind::RadialGradient { .. } |
+                    BrushKind::LinearGradient { .. } => {
+                        true
+                    }
+                }
+            }
+            PrimitiveContainer::Image(..) |
+            PrimitiveContainer::Border(..) => {
+                true
+            }
+        }
+    }
+
+    // Create a clone of this PrimitiveContainer, applying whatever
+    // changes are necessary to the primitive to support rendering
+    // it as part of the supplied shadow.
+    pub fn create_shadow(&self, shadow: &Shadow) -> PrimitiveContainer {
+        match *self {
+            PrimitiveContainer::TextRun(ref info) => {
+                let mut render_mode = info.font.render_mode;
+
+                if shadow.blur_radius > 0.0 {
+                    render_mode = render_mode.limit_by(FontRenderMode::Alpha);
+                }
+
+                PrimitiveContainer::TextRun(TextRunPrimitiveCpu {
+                    font: FontInstance {
+                        color: shadow.color.into(),
+                        render_mode,
+                        ..info.font.clone()
+                    },
+                    offset: info.offset + shadow.offset,
+                    glyph_range: info.glyph_range,
+                    glyph_count: info.glyph_count,
+                    glyph_keys: info.glyph_keys.clone(),
+                    glyph_gpu_blocks: Vec::new(),
+                    shadow: true,
+                })
+            }
+            PrimitiveContainer::Brush(ref brush) => {
+                match brush.kind {
+                    BrushKind::Solid { .. } => {
+                        PrimitiveContainer::Brush(BrushPrimitive::new(
+                            BrushKind::Solid {
+                                color: shadow.color,
+                            },
+                            None,
+                        ))
+                    }
+                    BrushKind::Clear |
+                    BrushKind::Picture { .. } |
+                    BrushKind::Image { .. } |
+                    BrushKind::YuvImage { .. } |
+                    BrushKind::RadialGradient { .. } |
+                    BrushKind::LinearGradient { .. } => {
+                        panic!("bug: other brush kinds not expected here yet");
+                    }
+                }
+            }
+            PrimitiveContainer::Image(..) |
+            PrimitiveContainer::Border(..) => {
+                panic!("bug: other primitive containers not expected here");
+            }
+        }
+    }
+}
+
 pub struct PrimitiveStore {
     /// CPU side information only.
     pub cpu_brushes: Vec<BrushPrimitive>,
     pub cpu_text_runs: Vec<TextRunPrimitiveCpu>,
     pub cpu_images: Vec<ImagePrimitiveCpu>,
     pub cpu_metadata: Vec<PrimitiveMetadata>,
     pub cpu_borders: Vec<BorderPrimitiveCpu>,
 
@@ -957,39 +1031,25 @@ impl PrimitiveStore {
 
     pub fn add_image_picture(
         &mut self,
         composite_mode: Option<PictureCompositeMode>,
         is_in_3d_context: bool,
         pipeline_id: PipelineId,
         reference_frame_index: ClipScrollNodeIndex,
         frame_output_pipeline_id: Option<PipelineId>,
+        apply_local_clip_rect: bool,
     ) -> PictureIndex {
         let pic = PicturePrimitive::new_image(
             composite_mode,
             is_in_3d_context,
             pipeline_id,
             reference_frame_index,
             frame_output_pipeline_id,
-        );
-
-        let pic_index = PictureIndex(self.pictures.len());
-        self.pictures.push(pic);
-
-        pic_index
-    }
-
-    pub fn add_shadow_picture(
-        &mut self,
-        shadow: Shadow,
-        pipeline_id: PipelineId,
-    ) -> PictureIndex {
-        let pic = PicturePrimitive::new_text_shadow(
-            shadow,
-            pipeline_id,
+            apply_local_clip_rect,
         );
 
         let pic_index = PictureIndex(self.pictures.len());
         self.pictures.push(pic);
 
         pic_index
     }
 
@@ -1019,17 +1079,16 @@ impl PrimitiveStore {
             cpu_prim_index: SpecificPrimitiveIndex(0),
         };
 
         let metadata = match container {
             PrimitiveContainer::Brush(brush) => {
                 let opacity = match brush.kind {
                     BrushKind::Clear => PrimitiveOpacity::translucent(),
                     BrushKind::Solid { ref color } => PrimitiveOpacity::from_alpha(color.a),
-                    BrushKind::Line { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Image { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::YuvImage { .. } => PrimitiveOpacity::opaque(),
                     BrushKind::RadialGradient { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::LinearGradient { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Picture { .. } => PrimitiveOpacity::translucent(),
                 };
 
                 let metadata = PrimitiveMetadata {
@@ -1295,18 +1354,17 @@ impl PrimitiveStore {
                                 metadata,
                                 pic_state_for_children,
                                 pic_state,
                                 frame_context,
                                 frame_state,
                             );
                     }
                     BrushKind::Solid { .. } |
-                    BrushKind::Clear |
-                    BrushKind::Line { .. } => {}
+                    BrushKind::Clear => {}
                 }
             }
         }
 
         // Mark this GPU resource as required for this frame.
         if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) {
             // has to match VECS_PER_BRUSH_PRIM
             request.push(metadata.local_rect);
@@ -1422,16 +1480,17 @@ impl PrimitiveStore {
                                 -0.5 * info.shadow_rect_alloc_size.height,
                             ),
                             inner_clip_mode,
                         );
 
                         continue;
                     }
                     ClipSource::BorderCorner(..) |
+                    ClipSource::LineDecoration(..) |
                     ClipSource::Image(..) => {
                         // TODO(gw): We can easily extend the segment builder
                         //           to support these clip sources in the
                         //           future, but they are rarely used.
                         clip_mask_kind = BrushClipMaskKind::Global;
                         continue;
                     }
                 };
@@ -1555,26 +1614,39 @@ impl PrimitiveStore {
 
                 clip_task_id
             })
         }
 
         true
     }
 
+    fn reset_clip_task(&mut self, prim_index: PrimitiveIndex) {
+        let metadata = &mut self.cpu_metadata[prim_index.0];
+        metadata.clip_task_id = None;
+        if metadata.prim_kind == PrimitiveKind::Brush {
+            if let Some(ref mut desc) = self.cpu_brushes[metadata.cpu_prim_index.0].segment_desc {
+                for segment in &mut desc.segments {
+                    segment.clip_task_id = None;
+                }
+            }
+        }
+    }
+
     fn update_clip_task(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_run_context: &PrimitiveRunContext,
         prim_screen_rect: &DeviceIntRect,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) -> bool {
-        self.cpu_metadata[prim_index.0].clip_task_id = None;
+        // Reset clips from previous frames since we may clip differently each frame.
+        self.reset_clip_task(prim_index);
 
         let prim_screen_rect = match prim_screen_rect.intersection(&frame_context.screen_rect) {
             Some(rect) => rect,
             None => {
                 self.cpu_metadata[prim_index.0].screen_rect = None;
                 return false;
             }
         };
@@ -1728,23 +1800,26 @@ impl PrimitiveStore {
             if let BrushKind::Picture { pic_index } = self.cpu_brushes[cpu_prim_index.0].kind {
                 let pic_context_for_children = {
                     let pic = &mut self.pictures[pic_index.0];
 
                     if !pic.resolve_scene_properties(frame_context.scene_properties) {
                         return None;
                     }
 
-                    let (apply_local_clip_rect, original_reference_frame_index) = match pic.kind {
-                        PictureKind::Image { reference_frame_index, composite_mode, .. } => {
-                            may_need_clip_mask = composite_mode.is_some();
-                            (true, Some(reference_frame_index))
+                    may_need_clip_mask = pic.composite_mode.is_some();
+
+                    let inflation_factor = match pic.composite_mode {
+                        Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
+                            // The amount of extra space needed for primitives inside
+                            // this picture to ensure the visibility check is correct.
+                            BLUR_SAMPLE_SCALE * blur_radius
                         }
-                        PictureKind::TextShadow { .. } => {
-                            (false, None)
+                        _ => {
+                            0.0
                         }
                     };
 
                     let display_list = &frame_context
                         .pipelines
                         .get(&pic.pipeline_id)
                         .expect("No display list?")
                         .display_list;
@@ -1752,20 +1827,21 @@ impl PrimitiveStore {
                     let inv_world_transform = prim_run_context
                         .scroll_node
                         .world_content_transform
                         .inverse();
 
                     PictureContext {
                         pipeline_id: pic.pipeline_id,
                         prim_runs: mem::replace(&mut pic.runs, Vec::new()),
-                        original_reference_frame_index,
+                        original_reference_frame_index: Some(pic.reference_frame_index),
                         display_list,
                         inv_world_transform,
-                        apply_local_clip_rect,
+                        apply_local_clip_rect: pic.apply_local_clip_rect,
+                        inflation_factor,
                     }
                 };
 
                 let result = self.prepare_prim_runs(
                     &pic_context_for_children,
                     &mut pic_state_for_children,
                     frame_context,
                     frame_state,
@@ -1783,17 +1859,24 @@ impl PrimitiveStore {
         let (local_rect, unclipped_device_rect) = {
             let metadata = &mut self.cpu_metadata[prim_index.0];
             if metadata.local_rect.size.width <= 0.0 ||
                metadata.local_rect.size.height <= 0.0 {
                 //warn!("invalid primitive rect {:?}", metadata.local_rect);
                 return None;
             }
 
-            let local_rect = metadata.local_clip_rect.intersection(&metadata.local_rect);
+            // Inflate the local rect for this primitive by the inflation factor of
+            // the picture context. This ensures that even if the primitive itself
+            // is not visible, any effects from the blur radius will be correctly
+            // taken into account.
+            let local_rect = metadata
+                .local_rect
+                .inflate(pic_context.inflation_factor, pic_context.inflation_factor)
+                .intersection(&metadata.local_clip_rect);
             let local_rect = match local_rect {
                 Some(local_rect) => local_rect,
                 None => return None,
             };
 
             let screen_bounding_rect = calculate_screen_bounding_rect(
                 &prim_run_context.scroll_node.world_content_transform,
                 &local_rect,
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -68,17 +68,17 @@ impl DocumentView {
     }
 }
 
 struct SceneData {
     scene: Scene,
     removed_pipelines: Vec<PipelineId>,
 }
 
-#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)]
+#[derive(Copy, Clone, Hash, PartialEq, PartialOrd, Debug, Eq, Ord)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameId(pub u32);
 
 struct Document {
     // The latest built scene, usable to build frames.
     // received from the scene builder thread.
     current: SceneData,
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -4,81 +4,86 @@
 
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, ImageDescriptor, ImageFormat};
 use api::{DeviceSize, PremultipliedColorF};
 use box_shadow::{BoxShadowCacheKey};
 use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::CoordinateSystemId;
 use device::TextureFilter;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
-use gpu_types::{ImageSource, PictureType, RasterizationSpace};
+use gpu_types::{ImageSource, RasterizationSpace};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use prim_store::{PrimitiveIndex, ImageCacheKey};
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
+use render_backend::FrameId;
 use resource_cache::{CacheItem, ResourceCache};
 use std::{cmp, ops, usize, f32, i32};
 use texture_cache::{TextureCache, TextureCacheHandle};
 use tiling::{RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind};
 
-const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
+const FLOATS_PER_RENDER_TASK_INFO: usize = 8;
 pub const MAX_BLUR_STD_DEVIATION: f32 = 4.0;
 pub const MIN_DOWNSCALING_RT_SIZE: i32 = 128;
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct RenderTaskId(pub u32); // TODO(gw): Make private when using GPU cache!
+pub struct RenderTaskId(pub u32, FrameId); // TODO(gw): Make private when using GPU cache!
 
 #[derive(Debug, Copy, Clone)]
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskAddress(pub u32);
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskTree {
     pub tasks: Vec<RenderTask>,
     pub task_data: Vec<RenderTaskData>,
     next_saved: SavedTargetIndex,
+    frame_id: FrameId,
 }
 
 impl RenderTaskTree {
-    pub fn new() -> Self {
+    pub fn new(frame_id: FrameId) -> Self {
         RenderTaskTree {
             tasks: Vec::new(),
             task_data: Vec::new(),
             next_saved: SavedTargetIndex(0),
+            frame_id,
         }
     }
 
     pub fn add(&mut self, task: RenderTask) -> RenderTaskId {
-        let id = RenderTaskId(self.tasks.len() as u32);
+        let id = self.tasks.len();
         self.tasks.push(task);
-        id
+        RenderTaskId(id as _, self.frame_id)
     }
 
     pub fn max_depth(&self, id: RenderTaskId, depth: usize, max_depth: &mut usize) {
+        debug_assert_eq!(self.frame_id, id.1);
         let depth = depth + 1;
         *max_depth = cmp::max(*max_depth, depth);
         let task = &self.tasks[id.0 as usize];
         for child in &task.children {
             self.max_depth(*child, depth, max_depth);
         }
     }
 
     pub fn assign_to_passes(
         &self,
         id: RenderTaskId,
         pass_index: usize,
         passes: &mut Vec<RenderPass>,
     ) {
+        debug_assert_eq!(self.frame_id, id.1);
         let task = &self.tasks[id.0 as usize];
 
         for child in &task.children {
             self.assign_to_passes(*child, pass_index - 1, passes);
         }
 
         // Sanity check - can be relaxed if needed
         match task.location {
@@ -101,16 +106,17 @@ impl RenderTaskTree {
             pass_index
         };
 
         let pass = &mut passes[pass_index];
         pass.add_render_task(id, task.get_dynamic_size(), task.target_kind());
     }
 
     pub fn get_task_address(&self, id: RenderTaskId) -> RenderTaskAddress {
+        debug_assert_eq!(self.frame_id, id.1);
         RenderTaskAddress(id.0)
     }
 
     pub fn build(&mut self) {
         for task in &self.tasks {
             self.task_data.push(task.write_task_data());
         }
     }
@@ -120,22 +126,24 @@ impl RenderTaskTree {
         self.next_saved.0 += 1;
         id
     }
 }
 
 impl ops::Index<RenderTaskId> for RenderTaskTree {
     type Output = RenderTask;
     fn index(&self, id: RenderTaskId) -> &RenderTask {
+        debug_assert_eq!(self.frame_id, id.1);
         &self.tasks[id.0 as usize]
     }
 }
 
 impl ops::IndexMut<RenderTaskId> for RenderTaskTree {
     fn index_mut(&mut self, id: RenderTaskId) -> &mut RenderTask {
+        debug_assert_eq!(self.frame_id, id.1);
         &mut self.tasks[id.0 as usize]
     }
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskLocation {
@@ -163,17 +171,16 @@ pub struct ClipRegionTask {
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PictureTask {
     pub prim_index: PrimitiveIndex,
     pub target_kind: RenderTargetKind,
     pub content_origin: DeviceIntPoint,
     pub color: PremultipliedColorF,
-    pub pic_type: PictureType,
     pub uv_rect_handle: GpuCacheHandle,
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BlurTask {
     pub blur_std_deviation: f32,
@@ -257,27 +264,25 @@ impl RenderTask {
     pub fn new_picture(
         location: RenderTaskLocation,
         prim_index: PrimitiveIndex,
         target_kind: RenderTargetKind,
         content_origin: DeviceIntPoint,
         color: PremultipliedColorF,
         clear_mode: ClearMode,
         children: Vec<RenderTaskId>,
-        pic_type: PictureType,
     ) -> Self {
         RenderTask {
             children,
             location,
             kind: RenderTaskKind::Picture(PictureTask {
                 prim_index,
                 target_kind,
                 content_origin,
                 color,
-                pic_type,
                 uv_rect_handle: GpuCacheHandle::new(),
             }),
             clear_mode,
             saved_index: None,
         }
     }
 
     pub fn new_readback(screen_rect: DeviceIntRect) -> Self {
@@ -380,16 +385,17 @@ impl RenderTask {
 
                                 (root_task_id, false)
                             }
                         );
                     }
                     ClipSource::Rectangle(..) |
                     ClipSource::RoundedRectangle(..) |
                     ClipSource::Image(..) |
+                    ClipSource::LineDecoration(..) |
                     ClipSource::BorderCorner(..) => {}
                 }
             }
         }
 
         RenderTask {
             children,
             location: RenderTaskLocation::Dynamic(None, outer_rect.size),
@@ -518,85 +524,66 @@ impl RenderTask {
         // NOTE: The ordering and layout of these structures are
         //       required to match both the GPU structures declared
         //       in prim_shared.glsl, and also the uses in submit_batch()
         //       in renderer.rs.
         // TODO(gw): Maybe there's a way to make this stuff a bit
         //           more type-safe. Although, it will always need
         //           to be kept in sync with the GLSL code anyway.
 
-        let (data1, data2) = match self.kind {
+        let data = match self.kind {
             RenderTaskKind::Picture(ref task) => {
-                (
-                    // Note: has to match `PICTURE_TYPE_*` in shaders
-                    [
-                        task.content_origin.x as f32,
-                        task.content_origin.y as f32,
-                        task.pic_type as u32 as f32,
-                    ],
-                    task.color.to_array()
-                )
+                // Note: has to match `PICTURE_TYPE_*` in shaders
+                [
+                    task.content_origin.x as f32,
+                    task.content_origin.y as f32,
+                    0.0,
+                ]
             }
             RenderTaskKind::CacheMask(ref task) => {
-                (
-                    [
-                        task.actual_rect.origin.x as f32,
-                        task.actual_rect.origin.y as f32,
-                        RasterizationSpace::Screen as i32 as f32,
-                    ],
-                    [0.0; 4],
-                )
+                [
+                    task.actual_rect.origin.x as f32,
+                    task.actual_rect.origin.y as f32,
+                    RasterizationSpace::Screen as i32 as f32,
+                ]
             }
             RenderTaskKind::ClipRegion(..) => {
-                (
-                    [
-                        0.0,
-                        0.0,
-                        RasterizationSpace::Local as i32 as f32,
-                    ],
-                    [0.0; 4],
-                )
+                [
+                    0.0,
+                    0.0,
+                    RasterizationSpace::Local as i32 as f32,
+                ]
             }
             RenderTaskKind::VerticalBlur(ref task) |
             RenderTaskKind::HorizontalBlur(ref task) => {
-                (
-                    [
-                        task.blur_std_deviation,
-                        0.0,
-                        0.0,
-                    ],
-                    [0.0; 4],
-                )
+                [
+                    task.blur_std_deviation,
+                    0.0,
+                    0.0,
+                ]
             }
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) => {
-                (
-                    [0.0; 3],
-                    [0.0; 4],
-                )
+                [0.0; 3]
             }
         };
 
         let (target_rect, target_index) = self.get_target_rect();
 
         RenderTaskData {
             data: [
                 target_rect.origin.x as f32,
                 target_rect.origin.y as f32,
                 target_rect.size.width as f32,
                 target_rect.size.height as f32,
                 target_index.0 as f32,
-                data1[0],
-                data1[1],
-                data1[2],
-                data2[0],
-                data2[1],
-                data2[2],
-                data2[3],
+                data[0],
+                data[1],
+                data[2],
             ]
         }
     }
 
     pub fn get_texture_handle(&self) -> &GpuCacheHandle {
         match self.kind {
             RenderTaskKind::Picture(ref info) => {
                 &info.uv_rect_handle
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -107,20 +107,16 @@ const GPU_TAG_BRUSH_BLEND: GpuProfileTag
 const GPU_TAG_BRUSH_IMAGE: GpuProfileTag = GpuProfileTag {
     label: "B_Image",
     color: debug_colors::SPRINGGREEN,
 };
 const GPU_TAG_BRUSH_SOLID: GpuProfileTag = GpuProfileTag {
     label: "B_Solid",
     color: debug_colors::RED,
 };
-const GPU_TAG_BRUSH_LINE: GpuProfileTag = GpuProfileTag {
-    label: "Line",
-    color: debug_colors::DARKRED,
-};
 const GPU_TAG_CACHE_CLIP: GpuProfileTag = GpuProfileTag {
     label: "C_Clip",
     color: debug_colors::PURPLE,
 };
 const GPU_TAG_SETUP_TARGET: GpuProfileTag = GpuProfileTag {
     label: "target init",
     color: debug_colors::SLATEGREY,
 };
@@ -204,17 +200,16 @@ impl BatchKind {
     #[cfg(feature = "debugger")]
     fn debug_name(&self) -> &'static str {
         match *self {
             BatchKind::HardwareComposite => "HardwareComposite",
             BatchKind::SplitComposite => "SplitComposite",
             BatchKind::Brush(kind) => {
                 match kind {
                     BrushBatchKind::Solid => "Brush (Solid)",
-                    BrushBatchKind::Line => "Brush (Line)",
                     BrushBatchKind::Image(..) => "Brush (Image)",
                     BrushBatchKind::Blend => "Brush (Blend)",
                     BrushBatchKind::MixBlend { .. } => "Brush (Composite)",
                     BrushBatchKind::YuvImage(..) => "Brush (YuvImage)",
                     BrushBatchKind::RadialGradient => "Brush (RadialGradient)",
                     BrushBatchKind::LinearGradient => "Brush (LinearGradient)",
                 }
             }
@@ -224,17 +219,16 @@ impl BatchKind {
 
     fn sampler_tag(&self) -> GpuProfileTag {
         match *self {
             BatchKind::HardwareComposite => GPU_TAG_PRIM_HW_COMPOSITE,
             BatchKind::SplitComposite => GPU_TAG_PRIM_SPLIT_COMPOSITE,
             BatchKind::Brush(kind) => {
                 match kind {
                     BrushBatchKind::Solid => GPU_TAG_BRUSH_SOLID,
-                    BrushBatchKind::Line => GPU_TAG_BRUSH_LINE,
                     BrushBatchKind::Image(..) => GPU_TAG_BRUSH_IMAGE,
                     BrushBatchKind::Blend => GPU_TAG_BRUSH_BLEND,
                     BrushBatchKind::MixBlend { .. } => GPU_TAG_BRUSH_MIXBLEND,
                     BrushBatchKind::YuvImage(..) => GPU_TAG_BRUSH_YUV_IMAGE,
                     BrushBatchKind::RadialGradient => GPU_TAG_BRUSH_RADIAL_GRADIENT,
                     BrushBatchKind::LinearGradient => GPU_TAG_BRUSH_LINEAR_GRADIENT,
                 }
             }
@@ -1758,16 +1752,21 @@ impl Renderer {
             target.clip_batcher.borders.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Clip,
             "BoxShadows",
             target.clip_batcher.box_shadows.len(),
         );
         debug_target.add(
+            debug_server::BatchKind::Clip,
+            "LineDecorations",
+            target.clip_batcher.line_decorations.len(),
+        );
+        debug_target.add(
             debug_server::BatchKind::Cache,
             "Vertical Blur",
             target.vertical_blurs.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Cache,
             "Horizontal Blur",
             target.horizontal_blurs.len(),
@@ -3080,16 +3079,32 @@ impl Renderer {
                 self.draw_instanced_batch(
                     items,
                     VertexArrayKind::Clip,
                     &textures,
                     stats,
                 );
             }
 
+            // draw line decoration clips
+            if !target.clip_batcher.line_decorations.is_empty() {
+                let _gm2 = self.gpu_profile.start_marker("clip lines");
+                self.shaders.cs_clip_line.bind(
+                    &mut self.device,
+                    projection,
+                    &mut self.renderer_errors,
+                );
+                self.draw_instanced_batch(
+                    &target.clip_batcher.line_decorations,
+                    VertexArrayKind::Clip,
+                    &BatchTextures::no_texture(),
+                    stats,
+                );
+            }
+
             // draw image masks
             for (mask_texture_id, items) in target.clip_batcher.images.iter() {
                 let _gm2 = self.gpu_profile.start_marker("clip images");
                 let textures = BatchTextures {
                     colors: [
                         mask_texture_id.clone(),
                         SourceTexture::Invalid,
                         SourceTexture::Invalid,
--- a/gfx/webrender/src/shade.rs
+++ b/gfx/webrender/src/shade.rs
@@ -413,31 +413,31 @@ pub struct Shaders {
     // These are "cache shaders". These shaders are used to
     // draw intermediate results to cache targets. The results
     // of these shaders are then used by the primitive shaders.
     pub cs_blur_a8: LazilyCompiledShader,
     pub cs_blur_rgba8: LazilyCompiledShader,
 
     // Brush shaders
     brush_solid: BrushShader,
-    brush_line: BrushShader,
     brush_image: Vec<Option<BrushShader>>,
     brush_blend: BrushShader,
     brush_mix_blend: BrushShader,
     brush_yuv_image: Vec<Option<BrushShader>>,
     brush_radial_gradient: BrushShader,
     brush_linear_gradient: BrushShader,
 
     /// These are "cache clip shaders". These shaders are used to
     /// draw clip instances into the cached clip mask. The results
     /// of these shaders are also used by the primitive shaders.
     pub cs_clip_rectangle: LazilyCompiledShader,
     pub cs_clip_box_shadow: LazilyCompiledShader,
     pub cs_clip_image: LazilyCompiledShader,
     pub cs_clip_border: LazilyCompiledShader,
+    pub cs_clip_line: LazilyCompiledShader,
 
     // The are "primitive shaders". These shaders draw and blend
     // final results on screen. They are aware of tile boundaries.
     // Most draw directly to the framebuffer, but some use inputs
     // from the cache shaders to draw. Specifically, the box
     // shadow primitive shader stretches the box shadow cache
     // output, and the cache_image shader blits the results of
     // a cache shader (e.g. blur) to the screen.
@@ -459,23 +459,16 @@ impl Shaders {
     ) -> Result<Self, ShaderError> {
         let brush_solid = BrushShader::new(
             "brush_solid",
             device,
             &[],
             options.precache_shaders,
         )?;
 
-        let brush_line = BrushShader::new(
-            "brush_line",
-            device,
-            &[],
-            options.precache_shaders,
-        )?;
-
         let brush_blend = BrushShader::new(
             "brush_blend",
             device,
             &[],
             options.precache_shaders,
         )?;
 
         let brush_mix_blend = BrushShader::new(
@@ -534,16 +527,24 @@ impl Shaders {
         let cs_clip_box_shadow = LazilyCompiledShader::new(
             ShaderKind::ClipCache,
             "cs_clip_box_shadow",
             &[],
             device,
             options.precache_shaders,
         )?;
 
+        let cs_clip_line = LazilyCompiledShader::new(
+            ShaderKind::ClipCache,
+            "cs_clip_line",
+            &[],
+            device,
+            options.precache_shaders,
+        )?;
+
         let cs_clip_image = LazilyCompiledShader::new(
             ShaderKind::ClipCache,
             "cs_clip_image",
             &[],
             device,
             options.precache_shaders,
         )?;
 
@@ -671,27 +672,27 @@ impl Shaders {
             device,
             options.precache_shaders,
         )?;
 
         Ok(Shaders {
             cs_blur_a8,
             cs_blur_rgba8,
             brush_solid,
-            brush_line,
             brush_image,
             brush_blend,
             brush_mix_blend,
             brush_yuv_image,
             brush_radial_gradient,
             brush_linear_gradient,
             cs_clip_rectangle,
             cs_clip_box_shadow,
             cs_clip_border,
             cs_clip_image,
+            cs_clip_line,
             ps_text_run,
             ps_text_run_dual_source,
             ps_image,
             ps_border_corner,
             ps_border_edge,
             ps_hw_composite,
             ps_split_composite,
         })
@@ -719,19 +720,16 @@ impl Shaders {
                     BrushBatchKind::Solid => {
                         &mut self.brush_solid
                     }
                     BrushBatchKind::Image(image_buffer_kind) => {
                         self.brush_image[image_buffer_kind as usize]
                             .as_mut()
                             .expect("Unsupported image shader kind")
                     }
-                    BrushBatchKind::Line => {
-                        &mut self.brush_line
-                    }
                     BrushBatchKind::Blend => {
                         &mut self.brush_blend
                     }
                     BrushBatchKind::MixBlend { .. } => {
                         &mut self.brush_mix_blend
                     }
                     BrushBatchKind::RadialGradient => {
                         &mut self.brush_radial_gradient
@@ -770,25 +768,25 @@ impl Shaders {
             }
         }
     }
 
     pub fn deinit(self, device: &mut Device) {
         self.cs_blur_a8.deinit(device);
         self.cs_blur_rgba8.deinit(device);
         self.brush_solid.deinit(device);
-        self.brush_line.deinit(device);
         self.brush_blend.deinit(device);
         self.brush_mix_blend.deinit(device);
         self.brush_radial_gradient.deinit(device);
         self.brush_linear_gradient.deinit(device);
         self.cs_clip_rectangle.deinit(device);
         self.cs_clip_box_shadow.deinit(device);
         self.cs_clip_image.deinit(device);
         self.cs_clip_border.deinit(device);
+        self.cs_clip_line.deinit(device);
         self.ps_text_run.deinit(device);
         self.ps_text_run_dual_source.deinit(device);
         for shader in self.brush_image {
             if let Some(shader) = shader {
                 shader.deinit(device);
             }
         }
         for shader in self.ps_image {
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -6,19 +6,18 @@ use api::{ColorF, DeviceIntPoint, Device
 use api::{DeviceUintRect, DeviceUintSize, DocumentLayer, FilterOp, ImageFormat, LayerRect};
 use api::{MixBlendMode, PipelineId};
 use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
 use clip::{ClipStore};
 use clip_scroll_tree::{ClipScrollTree, ClipScrollNodeIndex};
 use device::{FrameId, Texture};
 use gpu_cache::{GpuCache};
 use gpu_types::{BlurDirection, BlurInstance};
-use gpu_types::{ClipScrollNodeData};
+use gpu_types::{ClipScrollNodeData, ZBufferIdGenerator};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
-use picture::PictureKind;
 use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveKind, PrimitiveStore};
 use prim_store::{BrushKind, DeferredResolve};
 use profiler::FrameProfileCounters;
 use render_task::{BlitSource, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, RenderTaskLocation, RenderTaskTree};
 use resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32};
 use texture_allocator::GuillotineAllocator;
@@ -309,16 +308,17 @@ impl RenderTarget for ColorRenderTarget 
     fn build(
         &mut self,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
     ) {
         let mut merged_batches = AlphaBatchContainer::new(None);
+        let mut z_generator = ZBufferIdGenerator::new();
 
         for task_id in &self.alpha_tasks {
             let task = &render_tasks[*task_id];
 
             match task.kind {
                 RenderTaskKind::Picture(ref pic_task) => {
                     let brush_index = ctx.prim_store.cpu_metadata[pic_task.prim_index.0].cpu_prim_index;
                     let brush = &ctx.prim_store.cpu_brushes[brush_index.0];
@@ -331,16 +331,17 @@ impl RenderTarget for ColorRenderTarget 
 
                             batch_builder.add_pic_to_batch(
                                 pic,
                                 *task_id,
                                 ctx,
                                 gpu_cache,
                                 render_tasks,
                                 deferred_resolves,
+                                &mut z_generator,
                             );
 
                             if let Some(batch_container) = batch_builder.build(&mut merged_batches) {
                                 self.alpha_batch_containers.push(batch_container);
                             }
                         }
                         _ => {
                             unreachable!();
@@ -390,25 +391,23 @@ impl RenderTarget for ColorRenderTarget 
                 let prim_metadata = ctx.prim_store.get_metadata(task_info.prim_index);
                 match prim_metadata.prim_kind {
                     PrimitiveKind::Brush => {
                         let brush = &ctx.prim_store.cpu_brushes[prim_metadata.cpu_prim_index.0];
                         let pic = &ctx.prim_store.pictures[brush.get_picture_index().0];
 
                         self.alpha_tasks.push(task_id);
 
-                        if let PictureKind::Image { frame_output_pipeline_id, .. } = pic.kind {
-                            // If this pipeline is registered as a frame output
-                            // store the information necessary to do the copy.
-                            if let Some(pipeline_id) = frame_output_pipeline_id {
-                                self.outputs.push(FrameOutput {
-                                    pipeline_id,
-                                    task_id,
-                                });
-                            }
+                        // If this pipeline is registered as a frame output
+                        // store the information necessary to do the copy.
+                        if let Some(pipeline_id) = pic.frame_output_pipeline_id {
+                            self.outputs.push(FrameOutput {
+                                pipeline_id,
+                                task_id,
+                            });
                         }
                     }
                     _ => {
                         // No other primitives make use of primitive caching yet!
                         unreachable!()
                     }
                 }
             }
--- a/gfx/webrender/tests/angle_shader_validation.rs
+++ b/gfx/webrender/tests/angle_shader_validation.rs
@@ -35,16 +35,20 @@ const SHADERS: &[Shader] = &[
     Shader {
         name: "cs_clip_box_shadow",
         features: CLIP_FEATURES,
     },
     Shader {
         name: "cs_clip_border",
         features: CLIP_FEATURES,
     },
+    Shader {
+        name: "cs_clip_line",
+        features: CLIP_FEATURES,
+    },
     // Cache shaders
     Shader {
         name: "cs_blur",
         features: CACHE_FEATURES,
     },
     // Prim shaders
     Shader {
         name: "ps_border_corner",
@@ -87,20 +91,16 @@ const SHADERS: &[Shader] = &[
         name: "brush_blend",
         features: &[],
     },
     Shader {
         name: "brush_composite",
         features: &[],
     },
     Shader {
-        name: "brush_line",
-        features: &[],
-    },
-    Shader {
         name: "brush_radial_gradient",
         features: &[ "DITHERING" ],
     },
     Shader {
         name: "brush_linear_gradient",
         features: &[],
     },
 ];
--- a/gfx/webrender_api/Cargo.toml
+++ b/gfx/webrender_api/Cargo.toml
@@ -13,18 +13,18 @@ deserialize = []
 
 [dependencies]
 app_units = "0.6"
 bincode = "1.0"
 bitflags = "1.0"
 byteorder = "1.2.1"
 ipc-channel = {version = "0.10.0", optional = true}
 euclid = { version = "0.17", features = ["serde"] }
-serde = { version = "=1.0.27", features = ["rc"] }
-serde_derive = { version = "=1.0.27", features = ["deserialize_in_place"] }
+serde = { version = "=1.0.35", features = ["rc"] }
+serde_derive = { version = "=1.0.35", features = ["deserialize_in_place"] }
 time = "0.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.5"
 core-graphics = "0.13"
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-30cfecc343e407ce277d07cf09f27ad9dd1917a1
+22493328352ba432a7cd89491d81bfaa19bc1bce
--- a/gfx/wrench/src/main.rs
+++ b/gfx/wrench/src/main.rs
@@ -344,18 +344,18 @@ impl RenderNotifier for Notifier {
     fn wake_up(&self) {
         self.tx.send(NotifierEvent::WakeUp).unwrap();
     }
 
     fn shut_down(&self) {
         self.tx.send(NotifierEvent::ShutDown).unwrap();
     }
 
-    fn new_document_ready(&self, _: DocumentId, scrolled: bool, _composite_needed: bool) {
-        if !scrolled {
+    fn new_document_ready(&self, _: DocumentId, _scrolled: bool, composite_needed: bool) {
+        if composite_needed {
             self.wake_up();
         }
     }
 }
 
 fn create_notifier() -> (Box<RenderNotifier>, Receiver<NotifierEvent>) {
     let (tx, rx) = channel();
     (Box::new(Notifier { tx: tx }), rx)
@@ -499,16 +499,17 @@ fn main() {
         Box::new(captured) as Box<WrenchThing>
     } else {
         panic!("Should never have gotten here! {:?}", args);
     };
 
     let mut show_help = false;
     let mut do_loop = false;
     let mut cpu_profile_index = 0;
+    let mut cursor_position = WorldPoint::zero();
 
     let dim = window.get_inner_size();
     wrench.update(dim);
     thing.do_frame(&mut wrench);
 
     let mut body = |wrench: &mut Wrench, global_event: glutin::Event| {
         if let Some(window_title) = wrench.take_title() {
             if !cfg!(windows) { //TODO: calling `set_title` from inside the `run_forever` loop is illegal...
@@ -522,23 +523,24 @@ fn main() {
         match global_event {
             glutin::Event::Awakened => {
                 do_render = true;
             }
             glutin::Event::WindowEvent { event, .. } => match event {
                 glutin::WindowEvent::Closed => {
                     return glutin::ControlFlow::Break;
                 }
-
                 glutin::WindowEvent::Refresh |
-                glutin::WindowEvent::Focused(..) |
-                glutin::WindowEvent::CursorMoved { .. } => {
+                glutin::WindowEvent::Focused(..) => {
                     do_render = true;
                 }
-
+                glutin::WindowEvent::CursorMoved { position: (x, y), .. } => {
+                    cursor_position = WorldPoint::new(x as f32, y as f32);
+                    do_render = true;
+                }
                 glutin::WindowEvent::KeyboardInput {
                     input: glutin::KeyboardInput {
                         state: glutin::ElementState::Pressed,
                         virtual_keycode: Some(vk),
                         ..
                     },
                     ..
                 } => match vk {
@@ -609,16 +611,30 @@ fn main() {
                     }
                     VirtualKeyCode::Down => {
                         let current_zoom = wrench.get_page_zoom();
                         let new_zoom_factor = ZoomFactor::new((current_zoom.get() - 0.1).max(0.1));
 
                         wrench.set_page_zoom(new_zoom_factor);
                         do_frame = true;
                     }
+                    VirtualKeyCode::X => {
+                        let results = wrench.api.hit_test(
+                            wrench.document_id,
+                            None,
+                            cursor_position,
+                            HitTestFlags::FIND_ALL
+                        );
+
+                        println!("Hit test results:");
+                        for item in &results.items {
+                            println!("  • {:?}", item);
+                        }
+                        println!("");
+                    }
                     _ => {}
                 }
                 _ => {}
             },
             _ => return glutin::ControlFlow::Continue,
         };
 
         let dim = window.get_inner_size();
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -510,37 +510,21 @@ impl Wrench {
             txn.set_display_list(
                 Epoch(frame_number),
                 root_background_color,
                 self.window_size_f32(),
                 display_list,
                 false,
             );
         }
-        // TODO(nical) - Need to separate the set_display_list from the scrolling
-        // operations into separate transactions for mysterious -but probably related
-        // to the other comment below- reasons.
-        self.api.send_transaction(self.document_id, txn);
 
-        let mut txn = Transaction::new();
         for (id, offset) in scroll_offsets {
             txn.scroll_node_with_id(*offset, *id, ScrollClamping::NoClamping);
         }
-        // TODO(nical) - Wrench does not notify frames when there was scrolling
-        // in the transaction (See RenderNotifier implementations). If we don't
-        // generate a frame after scrolling, wrench just stops and some tests
-        // will time out.
-        // I suppose this was to avoid taking the snapshot after scrolling if
-        // there was other updates coming in a subsequent messages but it's very
-        // error-prone with transactions.
-        // For now just send two transactions to avoid the deadlock, but we should
-        // figure this out.
-        self.api.send_transaction(self.document_id, txn);
 
-        let mut txn = Transaction::new();
         txn.generate_frame();
         self.api.send_transaction(self.document_id, txn);
     }
 
     pub fn get_frame_profiles(
         &mut self,
     ) -> (Vec<webrender::CpuProfile>, Vec<webrender::GpuProfile>) {
         self.renderer.get_frame_profiles()
@@ -570,16 +554,17 @@ impl Wrench {
             "O - Toggle showing intermediate targets",
             "I - Toggle showing texture caches",
             "B - Toggle showing alpha primitive rects",
             "S - Toggle compact profiler",
             "Q - Toggle GPU queries for time and samples",
             "M - Trigger memory pressure event",
             "T - Save CPU profile to a file",
             "C - Save a capture to captures/wrench/",
+            "X - Do a hit test at the current cursor position",
         ];
 
         let color_and_offset = [(*BLACK_COLOR, 2.0), (*WHITE_COLOR, 0.0)];
         let dr = self.renderer.debug_renderer();
 
         for ref co in &color_and_offset {
             let x = self.device_pixel_ratio * (15.0 + co.1);
             let mut y = self.device_pixel_ratio * (15.0 + co.1 + dr.line_height());
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -368,58 +368,71 @@ impl YamlFrameReader {
                 } else {
                     None
                 }
             }
             _ => None,
         }
     }
 
+    fn to_hit_testing_tag(&self, item: &Yaml) -> Option<ItemTag> {
+        match *item {
+            Yaml::Array(ref array) if array.len() == 2 => {
+                match (array[0].as_i64(), array[1].as_i64()) {
+                    (Some(first), Some(second)) => Some((first as u64, second as u16)),
+                    _ => None,
+                }
+            }
+            _ => None,
+        }
+
+    }
+
     pub fn add_or_get_image(
             &mut self,
             file: &Path,
             tiling: Option<i64>,
             wrench: &mut Wrench,
     ) -> (ImageKey, LayoutSize) {
         let key = (file.to_owned(), tiling);
         if let Some(k) = self.image_map.get(&key) {
             return *k;
         }
 
         if self.list_resources { println!("{}", file.to_string_lossy()); }
         let (descriptor, image_data) = match image::open(file) {
             Ok(image) => {
-                let image_dims = image.dimensions();
+                let (image_width, image_height) = image.dimensions();
                 let (format, bytes) = match image {
                     image::ImageLuma8(_) => {
                         (ImageFormat::R8, image.raw_pixels())
                     }
                     image::ImageRgba8(_) => {
                         let mut pixels = image.raw_pixels();
                         premultiply(pixels.as_mut_slice());
                         (ImageFormat::BGRA8, pixels)
                     }
                     image::ImageRgb8(_) => {
                         let bytes = image.raw_pixels();
-                        let mut pixels = Vec::new();
+                        let mut pixels = Vec::with_capacity(image_width as usize * image_height as usize * 4);
                         for bgr in bytes.chunks(3) {
                             pixels.extend_from_slice(&[
                                 bgr[2],
                                 bgr[1],
                                 bgr[0],
                                 0xff
                             ]);
                         }
                         (ImageFormat::BGRA8, pixels)
                     }
                     _ => panic!("We don't support whatever your crazy image type is, come on"),
                 };
                 let descriptor = ImageDescriptor::new(
-                    image_dims.0,
-                    image_dims.1,
+                    image_width,
+                    image_height,
                     format,
                     is_image_opaque(format, &bytes[..]),
                     self.allow_mipmaps,
                 );
                 let data = ImageData::new(bytes);
                 (descriptor, data)
             }
             _ => {
@@ -1285,16 +1298,18 @@ impl YamlFrameReader {
                         dl.push_clip_id(id);
                         pushed_clip = true;
                     }
                 }
             }
 
             let mut info = LayoutPrimitiveInfo::with_clip_rect(LayoutRect::zero(), clip_rect);
             info.is_backface_visible = item["backface-visible"].as_bool().unwrap_or(true);;
+            info.tag = self.to_hit_testing_tag(&item["hit-testing-tag"]);
+
             match item_type {
                 "rect" => self.handle_rect(dl, item, &mut info),
                 "clear-rect" => self.handle_clear_rect(dl, item, &mut info),
                 "line" => self.handle_line(dl, item, &mut info),
                 "image" => self.handle_image(dl, wrench, item, &mut info),
                 "yuv-image" => self.handle_yuv_image(dl, wrench, item, &mut info),
                 "text" | "glyphs" => self.handle_text(dl, wrench, item, &mut info),
                 "scroll-frame" => self.handle_scroll_frame(dl, wrench, item),
--- a/gfx/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wrench/src/yaml_frame_writer.rs
@@ -682,19 +682,27 @@ impl YamlFrameWriter {
                 *list_iterator = traversal;
             }
             let base = match list_iterator.next() {
                 Some(base) => base,
                 None => break,
             };
 
             let mut v = new_table();
-            rect_node(&mut v, "bounds", &base.rect());
+            let info = base.get_layer_primitive_info(&LayoutVector2D::zero());
+            rect_node(&mut v, "bounds", &info.rect);
+            rect_node(&mut v, "clip-rect", &info.clip_rect);
 
-            rect_node(&mut v, "clip-rect", base.clip_rect());
+            if let Some(tag) = info.tag {
+                yaml_node(
+                    &mut v,
+                    "hit-testing-tag",
+                     Yaml::Array(vec![Yaml::Integer(tag.0 as i64), Yaml::Integer(tag.1 as i64)])
+                );
+            }
 
             let clip_and_scroll_yaml = match clip_id_mapper.map_info(&base.clip_and_scroll()) {
                 (scroll_id, Some(clip_id)) => {
                     Yaml::Array(vec![Yaml::Integer(scroll_id), Yaml::Integer(clip_id)])
                 }
                 (scroll_id, None) => Yaml::Integer(scroll_id),
             };
             yaml_node(&mut v, "clip-and-scroll", clip_and_scroll_yaml);
@@ -995,17 +1003,16 @@ impl YamlFrameWriter {
 
                     let mut sub_iter = base.sub_iter();
                     self.write_display_list(&mut v, display_list, scene, &mut sub_iter, clip_id_mapper);
                     continue_traversal = Some(sub_iter);
                 }
                 Clip(item) => {
                     str_node(&mut v, "type", "clip");
                     usize_node(&mut v, "id", clip_id_mapper.add_id(item.id));
-                    size_node(&mut v, "content-size", &base.rect().size);
 
                     let (complex_clips, complex_clip_count) = base.complex_clip();
                     if let Some(complex) = self.make_complex_clips_node(
                         complex_clip_count,
                         complex_clips,
                         display_list,
                     ) {
                         yaml_node(&mut v, "complex", complex);