Bug 1443807 - Update webrender to commit 5cb71f0f23719795e7c89417d91a7abad8ac20e9. r=jrmuizel
authorKartikaya Gupta <kgupta@mozilla.com>
Fri, 09 Mar 2018 08:39:35 -0500
changeset 407315 1bc8b8422379c407563e8187deba6ebe39f48748
parent 407314 5110a382f6a073a9fa220936442125c961bc57af
child 407316 8a988d1a6f23f900b90c7843e2c61db14a57e56c
push id60898
push userkgupta@mozilla.com
push dateFri, 09 Mar 2018 13:57:03 +0000
treeherderautoland@8a988d1a6f23 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1443807
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1443807 - Update webrender to commit 5cb71f0f23719795e7c89417d91a7abad8ac20e9. r=jrmuizel MozReview-Commit-ID: 9JzKooI2sJ2
gfx/webrender/res/brush_image.glsl
gfx/webrender/res/brush_picture.glsl
gfx/webrender/res/clip_shared.glsl
gfx/webrender/res/cs_blur.glsl
gfx/webrender/res/cs_clip_box_shadow.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/clip_scroll_node.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_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/segment.rs
gfx/webrender/src/tiling.rs
gfx/webrender/tests/angle_shader_validation.rs
gfx/webrender_bindings/revision.txt
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -8,22 +8,28 @@
 
 #ifdef WR_FEATURE_ALPHA_PASS
 varying vec2 vLocalPos;
 #endif
 
 varying vec3 vUv;
 flat varying vec4 vUvBounds;
 flat varying vec4 vColor;
+
+#ifdef WR_FEATURE_ALPHA_PASS
 flat varying vec2 vSelect;
+#endif
+
 #ifdef WR_VERTEX_SHADER
 
-#define IMAGE_SOURCE_COLOR              0
-#define IMAGE_SOURCE_ALPHA              1
-#define IMAGE_SOURCE_MASK_FROM_COLOR    2
+#ifdef WR_FEATURE_ALPHA_PASS
+    #define IMAGE_SOURCE_COLOR              0
+    #define IMAGE_SOURCE_ALPHA              1
+    #define IMAGE_SOURCE_MASK_FROM_COLOR    2
+#endif
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     ivec3 user_data,
     PictureTask pic_task
 ) {
@@ -48,41 +54,42 @@ void brush_vs(
 
     // Handle case where the UV coords are inverted (e.g. from an
     // external image).
     vUvBounds = vec4(
         min(uv0, uv1) + vec2(0.5),
         max(uv0, uv1) - vec2(0.5)
     ) / texture_size.xyxy;
 
+#ifdef WR_FEATURE_ALPHA_PASS
     switch (user_data.y) {
         case IMAGE_SOURCE_COLOR:
             vSelect = vec2(0.0, 0.0);
             break;
         case IMAGE_SOURCE_ALPHA:
             vSelect = vec2(0.0, 1.0);
             break;
         case IMAGE_SOURCE_MASK_FROM_COLOR:
             vSelect = vec2(1.0, 1.0);
             break;
     }
 
-#ifdef WR_FEATURE_ALPHA_PASS
     vLocalPos = vi.local_pos;
 #endif
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 vec4 brush_fs() {
     vec2 uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
 
     vec4 texel = TEX_SAMPLE(sColor0, vec3(uv, vUv.z));
-    vec4 mask = mix(texel.rrrr, texel.aaaa, vSelect.x);
-    vec4 color = mix(texel, vColor * mask, vSelect.y);
 
 #ifdef WR_FEATURE_ALPHA_PASS
-    color *= init_transform_fs(vLocalPos);
+    vec4 mask = mix(texel.rrrr, texel.aaaa, vSelect.x);
+    vec4 color = mix(texel, vColor * mask, vSelect.y) * init_transform_fs(vLocalPos);
+#else
+    vec4 color = texel;
 #endif
 
     return color;
 }
 #endif
deleted file mode 100644
--- a/gfx/webrender/res/brush_picture.glsl
+++ /dev/null
@@ -1,114 +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 1
-
-#include shared,prim_shared,brush
-
-#ifdef WR_FEATURE_ALPHA_PASS
-varying vec2 vLocalPos;
-#endif
-
-varying vec3 vUv;
-flat varying int vImageKind;
-flat varying vec4 vUvBounds;
-flat varying vec4 vUvBounds_NoClamp;
-flat varying vec4 vParams;
-flat varying vec4 vColor;
-
-#define BRUSH_PICTURE_SIMPLE      0
-#define BRUSH_PICTURE_NINEPATCH   1
-
-#ifdef WR_VERTEX_SHADER
-
-struct Picture {
-    vec4 color;
-};
-
-Picture fetch_picture(int address) {
-    vec4 data = fetch_from_resource_cache_1(address);
-    return Picture(data);
-}
-
-void brush_vs(
-    VertexInfo vi,
-    int prim_address,
-    RectWithSize local_rect,
-    ivec3 user_data,
-    PictureTask pic_task
-) {
-    vImageKind = user_data.y;
-
-    Picture pic = fetch_picture(prim_address);
-    ImageResource res = fetch_image_resource(user_data.x);
-    vec2 texture_size = vec2(textureSize(sColor1, 0).xy);
-    vColor = pic.color;
-    vec2 uv0 = res.uv_rect.p0;
-    vec2 uv1 = res.uv_rect.p1;
-    vec2 src_size = (uv1 - uv0) * res.user_data.x;
-    vUv.z = res.layer;
-
-    // TODO(gw): In the future we'll probably draw these as segments
-    //           with the brush shader. When that occurs, we can
-    //           modify the UVs for each segment in the VS, and the
-    //           FS can become a simple shader that doesn't need
-    //           to adjust the UVs.
-
-    switch (vImageKind) {
-        case BRUSH_PICTURE_SIMPLE: {
-            vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
-            vUv.xy = mix(uv0, uv1, f);
-            vUv.xy /= texture_size;
-            break;
-        }
-        case BRUSH_PICTURE_NINEPATCH: {
-            vec2 local_src_size = src_size / uDevicePixelRatio;
-            vUv.xy = (vi.local_pos - local_rect.p0) / local_src_size;
-            vParams.xy = vec2(0.5);
-            vParams.zw = (local_rect.size / local_src_size - 0.5);
-            break;
-        }
-        default:
-            vUv.xy = vec2(0.0);
-            vParams = vec4(0.0);
-    }
-
-    vUvBounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy;
-    vUvBounds_NoClamp = vec4(uv0, uv1) / texture_size.xyxy;
-
-#ifdef WR_FEATURE_ALPHA_PASS
-    vLocalPos = vi.local_pos;
-#endif
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-vec4 brush_fs() {
-    vec2 uv;
-
-    switch (vImageKind) {
-        case BRUSH_PICTURE_SIMPLE: {
-            uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
-            break;
-        }
-        case BRUSH_PICTURE_NINEPATCH: {
-            uv = clamp(vUv.xy, vec2(0.0), vParams.xy);
-            uv += max(vec2(0.0), vUv.xy - vParams.zw);
-            uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
-            uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
-            break;
-        }
-        default:
-            uv = vec2(0.0);
-    }
-
-    vec4 color = vColor * texture(sColor1, vec3(uv, vUv.z)).r;
-
-#ifdef WR_FEATURE_ALPHA_PASS
-    color *= init_transform_fs(vLocalPos);
-#endif
-
-    return color;
-}
-#endif
--- a/gfx/webrender/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -48,17 +48,25 @@ RectWithSize intersect_rect(RectWithSize
 
 // The transformed vertex function that always covers the whole clip area,
 // which is the intersection of all clip instances of a given primitive
 ClipVertexInfo write_clip_tile_vertex(RectWithSize local_clip_rect,
                                       ClipScrollNode scroll_node,
                                       ClipArea area) {
     vec2 actual_pos = area.screen_origin + aPosition.xy * area.common_data.task_rect.size;
 
-    vec4 node_pos = get_node_pos(actual_pos / uDevicePixelRatio, scroll_node);
+    vec4 node_pos;
+
+    // Select the local position, based on whether we are rasterizing this
+    // clip mask in local- or sccreen-space.
+    if (area.local_space) {
+        node_pos = vec4(actual_pos / uDevicePixelRatio, 0.0, 1.0);
+    } else {
+        node_pos = get_node_pos(actual_pos / uDevicePixelRatio, scroll_node);
+    }
 
     // compute the point position inside the scroll node, in CSS space
     vec2 vertex_pos = actual_pos +
                       area.common_data.task_rect.p0 -
                       area.screen_origin;
 
     gl_Position = uTransform * vec4(vertex_pos, 0.0, 1);
 
--- a/gfx/webrender/res/cs_blur.glsl
+++ b/gfx/webrender/res/cs_blur.glsl
@@ -19,27 +19,25 @@ flat varying int vBlurRadius;
 
 in int aBlurRenderTaskAddress;
 in int aBlurSourceTaskAddress;
 in int aBlurDirection;
 
 struct BlurTask {
     RenderTaskCommonData common_data;
     float blur_radius;
-    float scale_factor;
     vec4 color;
 };
 
 BlurTask fetch_blur_task(int address) {
     RenderTaskData task_data = fetch_render_task_data(address);
 
     BlurTask task = BlurTask(
         task_data.common_data,
         task_data.data1.x,
-        task_data.data1.y,
         task_data.data2
     );
 
     return task;
 }
 
 void main(void) {
     BlurTask blur_task = fetch_blur_task(aBlurRenderTaskAddress);
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/cs_clip_box_shadow.glsl
@@ -0,0 +1,82 @@
+/* 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 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;
+
+#ifdef WR_VERTEX_SHADER
+
+struct BoxShadowData {
+    vec2 src_rect_size;
+    float clip_mode;
+    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, 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);
+    ImageResource res = fetch_image_resource_direct(cmi.resource_address);
+
+    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;
+
+    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;
+
+    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;
+
+    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 = clamp(vUv.xy, vec2(0.0), vEdge.xy);
+    uv += max(vec2(0.0), vUv.xy - vEdge.zw);
+
+    uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
+    uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
+
+    float in_shadow_rect = point_inside_rect(
+        local_pos,
+        vLocalBounds.xy,
+        vLocalBounds.zw
+    );
+
+    float texel = TEX_SAMPLE(sColor0, vec3(uv, vLayer)).r;
+
+    float alpha = mix(texel, 1.0 - texel, vClipMode);
+
+    oFragColor = vec4(mix(vClipMode, alpha, in_shadow_rect));
+}
+#endif
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -81,31 +81,22 @@ vec4[2] fetch_from_resource_cache_2(int 
 uniform HIGHP_SAMPLER_FLOAT sampler2D sClipScrollNodes;
 uniform HIGHP_SAMPLER_FLOAT sampler2D sLocalClipRects;
 uniform HIGHP_SAMPLER_FLOAT sampler2D sRenderTasks;
 
 // Instanced attributes
 in ivec4 aData0;
 in ivec4 aData1;
 
-// Work around Angle bug that forgets to update sampler metadata,
-// by making the use of those samplers uniform across programs.
-// https://github.com/servo/webrender/wiki/Driver-issues#texturesize-in-vertex-shaders
-void markCacheTexturesUsed() {
-    vec2 size = vec2(textureSize(sCacheA8, 0)) + vec2(textureSize(sCacheRGBA8, 0));
-    if (size.x > 1000000.0) {
-        gl_Position = vec4(0.0);
-    }
-}
-
 // get_fetch_uv is a macro to work around a macOS Intel driver parsing bug.
 // TODO: convert back to a function once the driver issues are resolved, if ever.
 // https://github.com/servo/webrender/pull/623
 // https://github.com/servo/servo/issues/13953
-#define get_fetch_uv(i, vpi)  ivec2(vpi * (i % (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi)), i / (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi))
+// Do the division with unsigned ints because that's more efficient with D3D
+#define get_fetch_uv(i, vpi)  ivec2(int(uint(vpi) * (uint(i) % uint(WR_MAX_VERTEX_TEXTURE_WIDTH/vpi))), int(uint(i) / uint(WR_MAX_VERTEX_TEXTURE_WIDTH/vpi)))
 
 
 vec4[8] fetch_from_resource_cache_8(int address) {
     ivec2 uv = get_resource_cache_uv(address);
     return vec4[8](
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0)),
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(2, 0)),
@@ -254,17 +245,16 @@ RenderTaskCommonData fetch_render_task_c
         texel1.x
     );
 
     return data;
 }
 
 #define PIC_TYPE_IMAGE          1
 #define PIC_TYPE_TEXT_SHADOW    2
-#define PIC_TYPE_BOX_SHADOW     3
 
 /*
  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;
@@ -284,32 +274,35 @@ PictureTask fetch_picture_task(int addre
     );
 
     return task;
 }
 
 struct ClipArea {
     RenderTaskCommonData common_data;
     vec2 screen_origin;
+    bool local_space;
 };
 
 ClipArea fetch_clip_area(int index) {
     ClipArea area;
 
     if (index == 0x7FFF) { //special sentinel task index
         area.common_data = RenderTaskCommonData(
             RectWithSize(vec2(0.0), vec2(0.0)),
             0.0
         );
         area.screen_origin = vec2(0.0);
+        area.local_space = false;
     } else {
         RenderTaskData task_data = fetch_render_task_data(index);
 
         area.common_data = task_data.common_data;
         area.screen_origin = task_data.data1.xy;
+        area.local_space = task_data.data1.z == 0.0;
     }
 
     return area;
 }
 
 struct Glyph {
     vec2 offset;
 };
@@ -370,18 +363,16 @@ PrimitiveInstance fetch_prim_instance() 
     pi.clip_task_index = aData0.z;
     pi.clip_chain_rect_index = aData0.w / 65536;
     pi.scroll_node_id = aData0.w % 65536;
     pi.z = aData1.x;
     pi.user_data0 = aData1.y;
     pi.user_data1 = aData1.z;
     pi.user_data2 = aData1.w;
 
-    markCacheTexturesUsed();
-
     return pi;
 }
 
 struct CompositeInstance {
     int render_task_index;
     int src_task_index;
     int backdrop_task_index;
     int user_data0;
@@ -399,18 +390,16 @@ CompositeInstance fetch_composite_instan
     ci.backdrop_task_index = aData0.z;
     ci.z = float(aData0.w);
 
     ci.user_data0 = aData1.x;
     ci.user_data1 = aData1.y;
     ci.user_data2 = aData1.z;
     ci.user_data3 = aData1.w;
 
-    markCacheTexturesUsed();
-
     return ci;
 }
 
 struct Primitive {
     ClipScrollNode scroll_node;
     ClipArea clip_area;
     PictureTask task;
     RectWithSize local_rect;
@@ -817,17 +806,17 @@ float init_transform_fs(vec2 local_pos) 
 
 float do_clip() {
     // anything outside of the mask is considered transparent
     bvec4 inside = lessThanEqual(
         vec4(vClipMaskUvBounds.xy, vClipMaskUv.xy),
         vec4(vClipMaskUv.xy, vClipMaskUvBounds.zw));
     // check for the dummy bounds, which are given to the opaque objects
     return vClipMaskUvBounds.xy == vClipMaskUvBounds.zw ? 1.0:
-        all(inside) ? texelFetch(sSharedCacheA8, ivec3(vClipMaskUv), 0).r : 0.0;
+        all(inside) ? texelFetch(sCacheA8, ivec3(vClipMaskUv), 0).r : 0.0;
 }
 
 #ifdef WR_FEATURE_DITHERING
 vec4 dither(vec4 color) {
     const int matrix_mask = 7;
 
     ivec2 pos = ivec2(gl_FragCoord.xy) & ivec2(matrix_mask);
     float noise_normalized = (texelFetch(sDither, pos, 0).r * 255.0 + 0.5) / 64.0;
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -11,17 +11,17 @@ use clip::{ClipSource, ClipStore, ClipWo
 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::{ClipMaskInstance, ClipScrollNodeIndex};
 use gpu_types::{CompositePrimitiveInstance, PrimitiveInstance, SimplePrimitiveInstance};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
-use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive, PictureSurface};
+use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{CachedGradient, ImageSource, PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
 use prim_store::{BrushPrimitive, BrushKind, DeferredResolve, EdgeAaSegmentMask, 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};
@@ -50,17 +50,16 @@ pub enum BrushImageSourceKind {
     //Alpha = 1,            // Unused for now, but left here as shaders need to match.
     ColorAlphaMask = 2,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BrushBatchKind {
-    Picture,
     Solid,
     Line,
     Image(ImageBufferKind),
     Blend,
     MixBlend {
         task_id: RenderTaskId,
         source_id: RenderTaskId,
         backdrop_id: RenderTaskId,
@@ -517,20 +516,17 @@ impl AlphaBatchBuilder {
                 BatchKind::SplitComposite,
                 BlendMode::PremultipliedAlpha,
                 BatchTextures::no_texture(),
             );
             let pic_metadata = &ctx.prim_store.cpu_metadata[prim_index.0];
             let pic = &ctx.prim_store.cpu_pictures[pic_metadata.cpu_prim_index.0];
             let batch = self.batch_list.get_suitable_batch(key, &pic_metadata.screen_rect.as_ref().expect("bug").clipped);
 
-            let render_task_id = match pic.surface {
-                Some(PictureSurface::RenderTask(render_task_id)) => render_task_id,
-                Some(PictureSurface::TextureCache(..)) | None => panic!("BUG: unexpected surface in splitting"),
-            };
+            let render_task_id = pic.surface.expect("BUG: unexpected surface in splitting");
             let source_task_address = render_tasks.get_task_address(render_task_id);
             let gpu_address = gpu_handle.as_int(gpu_cache);
 
             let instance = CompositePrimitiveInstance::new(
                 task_address,
                 source_task_address,
                 RenderTaskAddress(0),
                 gpu_address,
@@ -569,17 +565,17 @@ impl AlphaBatchBuilder {
             // Now that we walk the primitive runs in order to add
             // items to batches, we need to check if they are
             // visible here.
             // We currently only support culling on normal (Image)
             // picture types.
             // TODO(gw): Support culling on shadow image types.
             let is_image = match pic.kind {
                 PictureKind::Image { .. } => true,
-                PictureKind::BoxShadow { .. } | PictureKind::TextShadow { .. } => false,
+                PictureKind::TextShadow { .. } => false,
             };
 
             if !is_image || metadata.screen_rect.is_some() {
                 self.add_prim_to_batch(
                     metadata.clip_chain_rect_index,
                     scroll_id,
                     prim_index,
                     ctx,
@@ -796,17 +792,17 @@ impl AlphaBatchBuilder {
                 let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                 batch.push(base_instance.build(cache_item.uv_rect_handle.as_int(gpu_cache), 0, 0));
             }
             PrimitiveKind::TextRun => {
                 let text_cpu =
                     &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
                 let is_shadow = match pic.kind {
                     PictureKind::TextShadow { .. } => true,
-                    PictureKind::BoxShadow { .. } | PictureKind::Image { .. } => false,
+                    PictureKind::Image { .. } => false,
                 };
 
                 // TODO(gw): It probably makes sense to base this decision on the content
                 //           origin field in the future (once that's configurable).
                 let font_transform = if is_shadow {
                     None
                 } else {
                     Some(scroll_node.transform)
@@ -889,48 +885,17 @@ impl AlphaBatchBuilder {
                     },
                 );
             }
             PrimitiveKind::Picture => {
                 let picture =
                     &ctx.prim_store.cpu_pictures[prim_metadata.cpu_prim_index.0];
 
                 match picture.surface {
-                    Some(PictureSurface::TextureCache(ref cache_item)) => {
-                        match picture.kind {
-                            PictureKind::TextShadow { .. } |
-                            PictureKind::Image { .. } => {
-                                panic!("BUG: only supported as render tasks for now");
-                            }
-                            PictureKind::BoxShadow { image_kind, .. } => {
-                                let textures = BatchTextures::color(cache_item.texture_id);
-                                let kind = BrushBatchKind::Picture;
-
-                                self.add_brush_to_batch(
-                                    &picture.brush,
-                                    prim_metadata,
-                                    kind,
-                                    specified_blend_mode,
-                                    non_segmented_blend_mode,
-                                    textures,
-                                    clip_chain_rect_index,
-                                    clip_task_address,
-                                    &task_relative_bounding_rect,
-                                    prim_cache_address,
-                                    scroll_id,
-                                    task_address,
-                                    transform_kind,
-                                    z,
-                                    render_tasks,
-                                    [cache_item.uv_rect_handle.as_int(gpu_cache), image_kind as i32, 0],
-                                );
-                            }
-                        }
-                    }
-                    Some(PictureSurface::RenderTask(cache_task_id)) => {
+                    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 { .. } => {
                                 let kind = BatchKind::Brush(
                                     BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
                                 );
@@ -954,19 +919,16 @@ impl AlphaBatchBuilder {
                                     user_data: [
                                         uv_rect_address,
                                         BrushImageSourceKind::Color as i32,
                                         0,
                                     ],
                                 };
                                 batch.push(PrimitiveInstance::from(instance));
                             }
-                            PictureKind::BoxShadow { .. } => {
-                                panic!("BUG: should be handled as a texture cache surface");
-                            }
                             PictureKind::Image {
                                 composite_mode,
                                 secondary_render_task_id,
                                 is_in_3d_context,
                                 reference_frame_index,
                                 real_local_rect,
                                 ref extra_gpu_data_handle,
                                 ..
@@ -1442,19 +1404,16 @@ impl BrushPrimitive {
                     textures,
                     [
                         uv_rect_addresses[0],
                         uv_rect_addresses[1],
                         uv_rect_addresses[2],
                     ],
                 ))
             }
-            BrushKind::Mask { .. } => {
-                unreachable!("bug: mask brushes not expected in normal alpha pass");
-            }
         }
     }
 }
 
 trait AlphaBatchHelpers {
     fn get_blend_mode(
         &self,
         metadata: &PrimitiveMetadata,
@@ -1482,17 +1441,16 @@ impl AlphaBatchHelpers for PrimitiveStor
                     }
                     BrushKind::Image { alpha_type, .. } => {
                         match alpha_type {
                             AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
                             AlphaType::Alpha => BlendMode::Alpha,
                         }
                     }
                     BrushKind::Solid { .. } |
-                    BrushKind::Mask { .. } |
                     BrushKind::Line { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::LinearGradient { .. } |
                     BrushKind::Picture => {
                         BlendMode::PremultipliedAlpha
                     }
                 }
@@ -1594,28 +1552,46 @@ fn make_polygon(
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 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>>,
 }
 
 impl ClipBatcher {
     pub fn new() -> Self {
         ClipBatcher {
             rectangles: Vec::new(),
             images: FastHashMap::default(),
             border_clears: Vec::new(),
             borders: Vec::new(),
+            box_shadows: FastHashMap::default(),
         }
     }
 
+    pub fn add_clip_region(
+        &mut self,
+        task_address: RenderTaskAddress,
+        clip_data_address: GpuCacheAddress,
+    ) {
+        let instance = ClipMaskInstance {
+            render_task_address: task_address,
+            scroll_node_data_index: ClipScrollNodeIndex(0),
+            segment: 0,
+            clip_data_address,
+            resource_address: GpuCacheAddress::invalid(),
+        };
+
+        self.rectangles.push(instance);
+    }
+
     pub fn add(
         &mut self,
         task_address: RenderTaskAddress,
         clips: &[ClipWorkItem],
         coordinate_system_id: CoordinateSystemId,
         resource_cache: &ResourceCache,
         gpu_cache: &GpuCache,
         clip_store: &ClipStore,
@@ -1654,16 +1630,28 @@ impl ClipBatcher {
                                     ..instance
                                 });
                         } else {
                             warn!("Warnings: skip a image mask");
                             debug!("Key:{:?} Rect::{:?}", mask.image, mask.rect);
                             continue;
                         }
                     }
+                    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,
+                                resource_address: gpu_cache.get_address(&info.cache_item.uv_rect_handle),
+                                ..instance
+                            });
+                    }
                     ClipSource::Rectangle(..) => {
                         if work_item.coordinate_system_id != coordinate_system_id {
                             self.rectangles.push(ClipMaskInstance {
                                 clip_data_address: gpu_address,
                                 ..instance
                             });
                             coordinate_system_id = work_item.coordinate_system_id;
                         }
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.rs
@@ -1,97 +1,131 @@
 /* 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, BoxShadowClipMode, ClipMode, ColorF, ComplexClipRegion, LayerPoint};
+use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, ComplexClipRegion};
 use api::{LayerPrimitiveInfo, LayerRect, LayerSize, LayerVector2D, LayoutSize, LocalClip};
-use api::PipelineId;
-use app_units::Au;
+use api::{DeviceIntSize};
 use clip::ClipSource;
 use display_list_flattener::DisplayListFlattener;
-use gpu_types::BrushImageKind;
+use gpu_cache::GpuCacheHandle;
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveContainer};
 use prim_store::ScrollNodeAndClipChain;
-use picture::PicturePrimitive;
-use render_task::MAX_BLUR_STD_DEVIATION;
+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,
+
+    // 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.
+    pub shadow_rect_alloc_size: LayerSize,
+
+    // The minimal shadow rect for the parameters above,
+    // used when drawing the shadow rect to be blurred.
+    pub minimal_shadow_rect: LayerRect,
+
+    // Local space rect for the shadow to be drawn or
+    // stretched in the shadow primitive.
+    pub prim_shadow_rect: LayerRect,
+}
+
 // The blur shader samples BLUR_SAMPLE_SCALE * blur_radius surrounding texels.
 pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
 
 // Maximum blur radius.
 // Taken from https://searchfox.org/mozilla-central/rev/c633ffa4c4611f202ca11270dcddb7b29edddff8/layout/painting/nsCSSRendering.cpp#4412
 pub const MAX_BLUR_RADIUS : f32 = 300.;
 
-#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
+// A cache key that uniquely identifies a minimally sized
+// and blurred box-shadow rect that can be stored in the
+// texture cache and applied to clip-masks.
+#[derive(Debug, Clone, Eq, Hash, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BoxShadowCacheKey {
-    pub width: Au,
-    pub height: Au,
-    pub blur_radius: Au,
-    pub spread_radius: Au,
-    pub offset_x: Au,
-    pub offset_y: Au,
-    pub br_top_left_w: Au,
-    pub br_top_left_h: Au,
-    pub br_top_right_w: Au,
-    pub br_top_right_h: Au,
-    pub br_bottom_left_w: Au,
-    pub br_bottom_left_h: Au,
-    pub br_bottom_right_w: Au,
-    pub br_bottom_right_h: Au,
+    pub blur_radius_dp: i32,
     pub clip_mode: BoxShadowClipMode,
+    pub rect_size: DeviceIntSize,
+    pub br_top_left: DeviceIntSize,
+    pub br_top_right: DeviceIntSize,
+    pub br_bottom_right: DeviceIntSize,
+    pub br_bottom_left: DeviceIntSize,
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn add_box_shadow(
         &mut self,
-        pipeline_id: PipelineId,
         clip_and_scroll: ScrollNodeAndClipChain,
         prim_info: &LayerPrimitiveInfo,
         box_offset: &LayerVector2D,
         color: &ColorF,
         mut blur_radius: f32,
         spread_radius: f32,
         border_radius: BorderRadius,
         clip_mode: BoxShadowClipMode,
     ) {
         if color.a == 0.0 {
             return;
         }
 
-        let (spread_amount, brush_clip_mode) = match clip_mode {
+        // Inset shadows get smaller as spread radius increases.
+        let (spread_amount, prim_clip_mode) = match clip_mode {
             BoxShadowClipMode::Outset => {
-                (spread_radius, ClipMode::Clip)
+                (spread_radius, ClipMode::ClipOut)
             }
             BoxShadowClipMode::Inset => {
-                (-spread_radius, ClipMode::ClipOut)
+                (-spread_radius, ClipMode::Clip)
             }
         };
 
+        // Ensure the blur radius is somewhat sensible.
         blur_radius = f32::min(blur_radius, MAX_BLUR_RADIUS);
+
+        // Adjust the border radius of the box shadow per CSS-spec.
         let shadow_radius = adjust_border_radius_for_box_shadow(
             border_radius,
             spread_amount,
         );
+
+        // Apply parameters that affect where the shadow rect
+        // exists in the local space of the primitive.
         let shadow_rect = prim_info.rect
             .translate(box_offset)
             .inflate(spread_amount, spread_amount);
 
+        // If blur radius is zero, we can use a fast path with
+        // no blur applied.
         if blur_radius == 0.0 {
-            if box_offset.x == 0.0 && box_offset.y == 0.0 && spread_amount == 0.0 {
+            // Trivial reject of box-shadows that are not visible.
+            if box_offset.x == 0.0 &&
+               box_offset.y == 0.0 &&
+               spread_amount == 0.0 {
                 return;
             }
+
             let mut clips = Vec::with_capacity(2);
             clips.push(ClipSource::Rectangle(*prim_info.local_clip.clip_rect()));
 
             let fast_info = match clip_mode {
                 BoxShadowClipMode::Outset => {
+                    if !shadow_rect.is_well_formed_and_nonempty() {
+                        return;
+                    }
+
                     // TODO(gw): Add a fast path for ClipOut + zero border radius!
                     clips.push(ClipSource::new_rounded_rect(
                         prim_info.rect,
                         border_radius,
                         ClipMode::ClipOut
                     ));
 
                     LayerPrimitiveInfo::with_clip(
@@ -102,21 +136,23 @@ impl<'a> DisplayListFlattener<'a> {
                                 shadow_rect,
                                 shadow_radius,
                                 ClipMode::Clip,
                             ),
                         ),
                     )
                 }
                 BoxShadowClipMode::Inset => {
-                    clips.push(ClipSource::new_rounded_rect(
-                        shadow_rect,
-                        shadow_radius,
-                        ClipMode::ClipOut
-                    ));
+                    if shadow_rect.is_well_formed_and_nonempty() {
+                        clips.push(ClipSource::new_rounded_rect(
+                            shadow_rect,
+                            shadow_radius,
+                            ClipMode::ClipOut
+                        ));
+                    }
 
                     LayerPrimitiveInfo::with_clip(
                         prim_info.rect,
                         LocalClip::RoundedRect(
                             prim_info.rect,
                             ComplexClipRegion::new(
                                 prim_info.rect,
                                 border_radius,
@@ -135,223 +171,93 @@ impl<'a> DisplayListFlattener<'a> {
                     BrushPrimitive::new(BrushKind::Solid {
                             color: *color,
                         },
                         None,
                     )
                 ),
             );
         } else {
+            // Normal path for box-shadows with a valid blur radius.
             let blur_offset = BLUR_SAMPLE_SCALE * blur_radius;
             let mut extra_clips = vec![];
 
-            let cache_key = BoxShadowCacheKey {
-                width: Au::from_f32_px(shadow_rect.size.width),
-                height: Au::from_f32_px(shadow_rect.size.height),
-                blur_radius: Au::from_f32_px(blur_radius),
-                spread_radius: Au::from_f32_px(spread_radius),
-                offset_x: Au::from_f32_px(box_offset.x),
-                offset_y: Au::from_f32_px(box_offset.y),
-                br_top_left_w: Au::from_f32_px(border_radius.top_left.width),
-                br_top_left_h: Au::from_f32_px(border_radius.top_left.height),
-                br_top_right_w: Au::from_f32_px(border_radius.top_right.width),
-                br_top_right_h: Au::from_f32_px(border_radius.top_right.height),
-                br_bottom_left_w: Au::from_f32_px(border_radius.bottom_left.width),
-                br_bottom_left_h: Au::from_f32_px(border_radius.bottom_left.height),
-                br_bottom_right_w: Au::from_f32_px(border_radius.bottom_right.width),
-                br_bottom_right_h: Au::from_f32_px(border_radius.bottom_right.height),
+            // Add a normal clip mask to clip out the contents
+            // of the surrounding primitive.
+            extra_clips.push(ClipSource::new_rounded_rect(
+                prim_info.rect,
+                border_radius,
+                prim_clip_mode,
+            ));
+
+            // Get the local rect of where the shadow will be drawn,
+            // expanded to include room for the blurred region.
+            let dest_rect = shadow_rect.inflate(blur_offset, blur_offset);
+
+            // Draw the box-shadow as a solid rect, using a box-shadow
+            // clip mask source.
+            let prim = BrushPrimitive::new(
+                BrushKind::Solid {
+                    color: *color,
+                },
+                None,
+            );
+
+            // Create the box-shadow clip source.
+            let shadow_clip_source = ClipSource::new_box_shadow(
+                shadow_rect,
+                shadow_radius,
+                dest_rect,
+                blur_radius,
                 clip_mode,
-            };
+            );
 
-            match clip_mode {
+            let prim_info = match clip_mode {
                 BoxShadowClipMode::Outset => {
-                    let mut width;
-                    let mut height;
-                    let brush_prim;
-                    let mut image_kind = BrushImageKind::NinePatch;
-
+                    // Certain spread-radii make the shadow invalid.
                     if !shadow_rect.is_well_formed_and_nonempty() {
                         return;
                     }
 
-                    // Create a minimal size primitive mask to blur. In this
-                    // case, we ensure the size of each corner is the same,
-                    // to simplify the shader logic that stretches the blurred
-                    // result across the primitive.
-                    let max_width = shadow_radius.top_left.width
-                                        .max(shadow_radius.bottom_left.width)
-                                        .max(shadow_radius.top_right.width)
-                                        .max(shadow_radius.bottom_right.width);
-                    let max_height = shadow_radius.top_left.height
-                                        .max(shadow_radius.bottom_left.height)
-                                        .max(shadow_radius.top_right.height)
-                                        .max(shadow_radius.bottom_right.height);
-
-                    width = 2.0 * max_width + BLUR_SAMPLE_SCALE * blur_radius;
-                    height = 2.0 * max_height + BLUR_SAMPLE_SCALE * blur_radius;
-
-                    // 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.
-                    if width > prim_info.rect.size.width || height > prim_info.rect.size.height {
-                        image_kind = BrushImageKind::Simple;
-                        width = prim_info.rect.size.width + spread_amount * 2.0;
-                        height = prim_info.rect.size.height + spread_amount * 2.0;
-                    }
-
-                    let clip_rect = LayerRect::new(
-                        LayerPoint::zero(),
-                        LayerSize::new(width, height)
-                    );
-
-                    brush_prim = BrushPrimitive::new(
-                        BrushKind::Mask {
-                            clip_mode: brush_clip_mode,
-                            rect: clip_rect,
-                            radii: shadow_radius,
-                        },
-                        None,
-                    );
+                    // Add the box-shadow clip source.
+                    extra_clips.push(shadow_clip_source);
 
-                    // Construct a mask primitive to add to the picture.
-                    let brush_rect = LayerRect::new(LayerPoint::zero(),
-                                                    LayerSize::new(width, height));
-                    let brush_info = LayerPrimitiveInfo::new(brush_rect);
-                    let brush_prim_index = self.create_primitive(
-                        &brush_info,
-                        Vec::new(),
-                        PrimitiveContainer::Brush(brush_prim),
-                    );
-
-                    // Create a box shadow picture and add the mask primitive to it.
-                    let pic_rect = shadow_rect.inflate(blur_offset, blur_offset);
-                    let mut pic_prim = PicturePrimitive::new_box_shadow(
-                        blur_radius,
-                        *color,
-                        clip_mode,
-                        image_kind,
-                        cache_key,
-                        pipeline_id,
-                    );
-                    pic_prim.add_primitive(
-                        brush_prim_index,
-                        clip_and_scroll
-                    );
-
-                    extra_clips.push(ClipSource::new_rounded_rect(
-                        prim_info.rect,
-                        border_radius,
-                        ClipMode::ClipOut,
-                    ));
-                    let pic_info = LayerPrimitiveInfo::with_clip_rect(
-                        pic_rect,
+                    // Outset shadows are expanded by the shadow
+                    // region from the original primitive.
+                    LayerPrimitiveInfo::with_clip_rect(
+                        dest_rect,
                         *prim_info.local_clip.clip_rect()
-                    );
-                    self.add_primitive(
-                        clip_and_scroll,
-                        &pic_info,
-                        extra_clips,
-                        PrimitiveContainer::Picture(pic_prim),
-                    );
+                    )
                 }
                 BoxShadowClipMode::Inset => {
-                    // TODO(gw): Inset shadows still need an optimization pass.
-                    //           We draw and blur way more pixels than needed.
-
-                    // Draw a picture that covers the area of the primitive rect.
-                    let brush_rect = LayerRect::new(
-                        LayerPoint::zero(),
-                        prim_info.rect.size
-                    );
-
-                    // Define where the inset box shadow rect is, local
-                    // to the brush rect above.
-                    let clip_rect = brush_rect.translate(box_offset)
-                                              .inflate(spread_amount, spread_amount);
-
-                    // Ensure there are more than one pixel around the edges, so that there
-                    // is non-zero data to blur, in the case of an inset shadow
-                    // with zero spread and zero offset.
-                    // The size of inflation edge is determined by std deviation because large
-                    // std deviation blur would be downscaled first. Thus, we need more thick
-                    // edge to prevent edge get blurred after downscled.
-                    let mut adjusted_blur_std_deviation = blur_radius * 0.5;
-                    let mut inflate_size = 1.0;
-                    while adjusted_blur_std_deviation > MAX_BLUR_STD_DEVIATION {
-                        adjusted_blur_std_deviation *= 0.5;
-                        inflate_size *= 2.0;
+                    // If the inner shadow rect contains the prim
+                    // rect, no pixels will be shadowed.
+                    if border_radius.is_zero() &&
+                       shadow_rect.inflate(-blur_radius, -blur_radius).contains_rect(&prim_info.rect) {
+                        return;
                     }
 
-                    let brush_rect = brush_rect.inflate(inflate_size + box_offset.x.abs(), inflate_size + box_offset.y.abs());
-                    let brush_prim = BrushPrimitive::new(
-                        BrushKind::Mask {
-                            clip_mode: brush_clip_mode,
-                            rect: clip_rect,
-                            radii: shadow_radius,
-                        },
-                        None,
-                    );
-                    let brush_info = LayerPrimitiveInfo::new(brush_rect);
-                    let brush_prim_index = self.create_primitive(
-                        &brush_info,
-                        Vec::new(),
-                        PrimitiveContainer::Brush(brush_prim),
-                    );
-
-                    // Create a box shadow picture primitive and add
-                    // the brush primitive to it.
-                    let mut pic_prim = PicturePrimitive::new_box_shadow(
-                        blur_radius,
-                        *color,
-                        BoxShadowClipMode::Inset,
-                        // TODO(gw): Make use of optimization for inset.
-                        BrushImageKind::NinePatch,
-                        cache_key,
-                        pipeline_id,
-                    );
-                    pic_prim.add_primitive(
-                        brush_prim_index,
-                        clip_and_scroll
-                    );
-
-                    let clip_rect = prim_info.local_clip.clip_rect();
-                    let clip_rect = match prim_info.rect.intersection(clip_rect) {
-                        Some(clip_rect) => clip_rect,
-                        None => return,
-                    };
-
-                    // Draw the picture one pixel outside the original
-                    // rect to account for the inflate above. This
-                    // extra edge will be clipped by the local clip
-                    // rect set below.
-                    let pic_rect = prim_info.rect.inflate(inflate_size + box_offset.x.abs(), inflate_size + box_offset.y.abs());
-                    let pic_info = LayerPrimitiveInfo::with_clip_rect(
-                        pic_rect,
-                        clip_rect
-                    );
-
-                    // Add a normal clip to ensure nothing gets drawn
-                    // outside the primitive rect.
-                    if !border_radius.is_zero() {
-                        extra_clips.push(ClipSource::new_rounded_rect(
-                            prim_info.rect,
-                            border_radius,
-                            ClipMode::Clip,
-                        ));
+                    // Inset shadows are still visible, even if the
+                    // inset shadow rect becomes invalid (they will
+                    // just look like a solid rectangle).
+                    if shadow_rect.is_well_formed_and_nonempty() {
+                        extra_clips.push(shadow_clip_source);
                     }
 
-                    // Add the picture primitive to the frame.
-                    self.add_primitive(
-                        clip_and_scroll,
-                        &pic_info,
-                        extra_clips,
-                        PrimitiveContainer::Picture(pic_prim),
-                    );
+                    // Inset shadows draw inside the original primitive.
+                    prim_info.clone()
                 }
-            }
+            };
+
+            self.add_primitive(
+                clip_and_scroll,
+                &prim_info,
+                extra_clips,
+                PrimitiveContainer::Brush(prim),
+            );
         }
     }
 }
 
 fn adjust_border_radius_for_box_shadow(
     radius: BorderRadius,
     spread_amount: f32,
 ) -> BorderRadius {
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,22 +1,25 @@
 /* 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, LayoutPoint, LayoutVector2D, LocalClip};
+use api::{ImageRendering, LayerRect, LayerSize, LayoutPoint, LayoutVector2D, LocalClip};
+use api::{BoxShadowClipMode, LayerPoint, LayerToWorldScale};
 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::ClipScrollNodeIndex;
 use prim_store::{ClipData, ImageMaskData};
-use resource_cache::{ImageRequest, ResourceCache};
+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 std::sync::Arc;
 
 pub type ClipStore = FreeList<ClipSources>;
 pub type ClipSourcesHandle = FreeListHandle<ClipSources>;
 pub type ClipSourcesWeakHandle = WeakFreeListHandle<ClipSources>;
 
@@ -73,16 +76,17 @@ pub enum ClipSource {
     Rectangle(LayerRect),
     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),
 }
 
 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));
@@ -110,16 +114,99 @@ impl ClipSource {
     ) -> ClipSource {
         ensure_no_corner_overlap(&mut radii, &rect);
         ClipSource::RoundedRectangle(
             rect,
             radii,
             clip_mode,
         )
     }
+
+    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
+        // source rect with a minimal rect.
+        let fract_offset = LayerPoint::new(
+            shadow_rect.origin.x.fract().abs(),
+            shadow_rect.origin.y.fract().abs(),
+        );
+        let fract_size = LayerSize::new(
+            shadow_rect.size.width.fract().abs(),
+            shadow_rect.size.height.fract().abs(),
+        );
+
+        // Create a minimal size primitive mask to blur. In this
+        // case, we ensure the size of each corner is the same,
+        // to simplify the shader logic that stretches the blurred
+        // result across the primitive.
+        let max_corner_width = shadow_radius.top_left.width
+                                    .max(shadow_radius.bottom_left.width)
+                                    .max(shadow_radius.top_right.width)
+                                    .max(shadow_radius.bottom_right.width);
+        let max_corner_height = shadow_radius.top_left.height
+                                    .max(shadow_radius.bottom_left.height)
+                                    .max(shadow_radius.top_right.height)
+                                    .max(shadow_radius.bottom_right.height);
+
+        // Get maximum distance that can be affected by given blur radius.
+        let blur_region = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
+
+        // If the largest corner is smaller than the blur radius, we need to ensure
+        // that it's big enough that the corners don't affect the middle segments.
+        let used_corner_width = max_corner_width.max(blur_region);
+        let used_corner_height = max_corner_height.max(blur_region);
+
+        // Minimal nine-patch size, corner + internal + corner.
+        let min_shadow_rect_size = LayerSize::new(
+            2.0 * used_corner_width + blur_region,
+            2.0 * used_corner_height + blur_region,
+        );
+
+        // The minimal rect to blur.
+        let mut minimal_shadow_rect = LayerRect::new(
+            LayerPoint::new(
+                blur_region + fract_offset.x,
+                blur_region + fract_offset.y,
+            ),
+            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.
+        minimal_shadow_rect.size.width = minimal_shadow_rect.size.width.min(shadow_rect.size.width);
+        minimal_shadow_rect.size.height = minimal_shadow_rect.size.height.min(shadow_rect.size.height);
+
+        // 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,
+            cache_item: CacheItem::invalid(),
+            cache_key: None,
+            clip_data_handle: GpuCacheHandle::new(),
+            minimal_shadow_rect,
+        })
+    }
 }
 
 #[derive(Debug)]
 pub struct ClipSources {
     pub clips: Vec<(ClipSource, GpuCacheHandle)>,
     pub local_inner_rect: LayerRect,
     pub local_outer_rect: Option<LayerRect>
 }
@@ -181,16 +268,17 @@ 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 { .. } => {
                     can_calculate_inner_rect = false;
                     break;
                 }
             }
         }
 
         let outer = match can_calculate_outer_rect {
@@ -205,47 +293,91 @@ impl ClipSources {
 
         (inner, outer)
     }
 
     pub fn update(
         &mut self,
         gpu_cache: &mut GpuCache,
         resource_cache: &mut ResourceCache,
+        device_pixel_scale: DevicePixelScale,
     ) {
         for &mut (ref mut source, ref mut handle) in &mut self.clips {
             if let Some(mut request) = gpu_cache.request(handle) {
                 match *source {
                     ClipSource::Image(ref mask) => {
                         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,
+                            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);
                     }
                 }
             }
 
-            if let ClipSource::Image(ref mask) = *source {
-                resource_cache.request_image(
-                    ImageRequest {
-                        key: mask.image,
-                        rendering: ImageRendering::Auto,
-                        tile: None,
-                    },
-                    gpu_cache,
-                );
+            match *source {
+                ClipSource::Image(ref mask) => {
+                    resource_cache.request_image(
+                        ImageRequest {
+                            key: mask.image,
+                            rendering: ImageRendering::Auto,
+                            tile: None,
+                        },
+                        gpu_cache,
+                    );
+                }
+                ClipSource::BoxShadow(ref mut info) => {
+                    // 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 blur_radius_dp = (info.blur_radius * 0.5 * device_pixel_scale.0).round();
+
+                    // Create the cache key for this box-shadow render task.
+                    let content_scale = LayerToWorldScale::new(1.0) * device_pixel_scale;
+                    let cache_size = to_cache_size(info.shadow_rect_alloc_size * content_scale);
+                    let bs_cache_key = BoxShadowCacheKey {
+                        blur_radius_dp: blur_radius_dp as i32,
+                        clip_mode: info.clip_mode,
+                        rect_size: (info.shadow_rect_alloc_size * content_scale).round().to_i32(),
+                        br_top_left: (info.shadow_radius.top_left * content_scale).round().to_i32(),
+                        br_top_right: (info.shadow_radius.top_right * content_scale).round().to_i32(),
+                        br_bottom_right: (info.shadow_radius.bottom_right * content_scale).round().to_i32(),
+                        br_bottom_left: (info.shadow_radius.bottom_left * content_scale).round().to_i32(),
+                    };
+
+                    info.cache_key = Some((cache_size, bs_cache_key));
+
+                    if let Some(mut request) = gpu_cache.request(&mut info.clip_data_handle) {
+                        let data = ClipData::rounded_rect(
+                            &info.minimal_shadow_rect,
+                            &info.shadow_radius,
+                            ClipMode::Clip,
+                        );
+
+                        data.write(&mut request);
+                    }
+                }
+                _ => {}
             }
         }
     }
 
     pub fn get_screen_bounds(
         &self,
         transform: &LayerToWorldFastTransform,
         device_pixel_scale: DevicePixelScale,
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -365,17 +365,21 @@ impl ClipScrollNode {
                 (handle, clip_chain_index, clip_chain_node),
             _ => {
                 self.invertible = true;
                 return;
             }
         };
 
         let clip_sources = clip_store.get_mut(clip_sources_handle);
-        clip_sources.update(gpu_cache, resource_cache);
+        clip_sources.update(
+            gpu_cache,
+            resource_cache,
+            device_pixel_scale,
+        );
         let (screen_inner_rect, screen_outer_rect) =
             clip_sources.get_screen_bounds(&self.world_viewport_transform, device_pixel_scale);
 
         // All clipping ClipScrollNodes should have outer rectangles, because they never
         // use the BorderCorner clip type and they always have at last one non-ClipOut
         // Rectangle ClipSource.
         let screen_outer_rect = screen_outer_rect.expect("Clipping node didn't have outer rect.");
         let local_outer_rect = clip_sources.local_outer_rect.expect(
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -736,17 +736,16 @@ impl<'a> DisplayListFlattener<'a> {
             }
             SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
                 let bounds = box_shadow_info
                     .box_bounds
                     .translate(&reference_frame_relative_offset);
                 let mut prim_info = prim_info.clone();
                 prim_info.rect = bounds;
                 self.add_box_shadow(
-                    pipeline_id,
                     clip_and_scroll,
                     &prim_info,
                     &box_shadow_info.offset,
                     &box_shadow_info.color,
                     box_shadow_info.blur_radius,
                     box_shadow_info.spread_radius,
                     box_shadow_info.border_radius,
                     box_shadow_info.clip_mode,
@@ -1307,19 +1306,18 @@ impl<'a> DisplayListFlattener<'a> {
             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 { .. } |
-                PictureKind::BoxShadow { .. } => {
-                    panic!("bug: text/box pictures invalid here");
+                PictureKind::TextShadow { .. } => {
+                    panic!("bug: text pictures invalid here");
                 }
             }
         }
 
         // 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() {
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -8,17 +8,17 @@ use api::{LayerRect, LayerSize, Pipeline
 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 hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap};
-use picture::{ContentOrigin, PictureSurface};
+use picture::{ContentOrigin};
 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};
 use std::{mem, f32};
 use std::sync::Arc;
@@ -230,17 +230,17 @@ impl FrameBuilder {
             ContentOrigin::Screen(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(PictureSurface::RenderTask(render_task_id));
+        pic.surface = Some(render_task_id);
         Some(render_task_id)
     }
 
     fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache) {
         static SCROLLBAR_PADDING: f32 = 8.0;
 
         for scrollbar_prim in &self.scrollbar_prims {
             let metadata = &mut self.prim_store.cpu_metadata[scrollbar_prim.prim_index.0];
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -4,16 +4,25 @@
 
 use api::{DevicePoint, LayerToWorldTransform, PremultipliedColorF, 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(Debug, Copy, Clone)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[repr(C)]
+pub enum RasterizationSpace {
+    Local = 0,
+    Screen = 1,
+}
+
 #[repr(i32)]
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BlurDirection {
     Horizontal = 0,
     Vertical,
 }
@@ -190,26 +199,16 @@ impl From<BrushInstance> for PrimitiveIn
                 instance.user_data[0],
                 instance.user_data[1],
                 instance.user_data[2],
             ]
         }
     }
 }
 
-// Defines how a brush image is stretched onto the primitive.
-// In the future, we may draw with segments for each portion
-// of the primitive, in which case this will be redundant.
-#[repr(C)]
-#[derive(Debug, Copy, Clone)]
-pub enum BrushImageKind {
-    Simple = 0,     // A normal rect
-    NinePatch = 1,  // A nine-patch image (stretch inside segments)
-}
-
 #[derive(Copy, Debug, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub struct ClipScrollNodeIndex(pub u32);
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -239,17 +238,16 @@ 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,
-    BoxShadow = 3,
 }
 
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub struct ImageSource {
     pub p0: DevicePoint,
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -295,18 +295,20 @@ 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(_) =>
-                unreachable!("Didn't expect to hit test against BorderCorner"),
+            ClipSource::BorderCorner(_) |
+            ClipSource::BoxShadow(_) => {
+                unreachable!("Didn't expect to hit test against BorderCorner / BoxShadow");
+            }
         }
     }).collect()
 }
 
 pub struct HitTest {
     pipeline_id: Option<PipelineId>,
     point: WorldPoint,
     flags: HitTestFlags,
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -1,44 +1,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceSize};
+use api::{DeviceIntPoint, DeviceIntRect};
 use api::{LayerPoint, LayerRect, LayerToWorldScale, LayerVector2D};
-use api::{BoxShadowClipMode, ColorF, FilterOp, MixBlendMode, PipelineId};
+use api::{ColorF, FilterOp, MixBlendMode, PipelineId};
 use api::{PremultipliedColorF, Shadow};
-use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowCacheKey};
+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::{BrushImageKind, PictureType};
+use gpu_types::{PictureType};
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
 use prim_store::ScrollNodeAndClipChain;
-use render_task::{ClearMode, RenderTask, RenderTaskCacheKey};
-use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
-use resource_cache::CacheItem;
+use render_task::{ClearMode, RenderTask};
+use render_task::{RenderTaskId, RenderTaskLocation, to_cache_size};
 use scene::{FilterOpHelpers, SceneProperties};
 use tiling::RenderTargetKind;
 
-
-// TODO(gw): Rounding the content rect here to device pixels is not
-// technically correct. Ideally we should ceil() here, and ensure that
-// the extra part pixel in the case of fractional sizes is correctly
-// handled. For now, just use rounding which passes the existing
-// Gecko tests.
-// Note: zero-square tasks are prohibited in WR task tree, so
-// we ensure each dimension to be at least the length of 1 after rounding.
-fn to_cache_size(size: DeviceSize) -> DeviceIntSize {
-    DeviceIntSize::new(
-        1.max(size.width.round() as i32),
-        1.max(size.height.round() as i32),
-    )
-}
-
 /*
  A picture represents a dynamically rendered image. It consists of:
 
  * A number of primitives that are drawn onto the picture.
  * A composite operation describing how to composite this
    picture into its parent.
  * A configuration describing how to draw the primitives on
    this picture (e.g. in screen space or local space).
@@ -70,24 +54,16 @@ pub enum ContentOrigin {
 #[derive(Debug)]
 pub enum PictureKind {
     TextShadow {
         offset: LayerVector2D,
         color: ColorF,
         blur_radius: f32,
         content_rect: LayerRect,
     },
-    BoxShadow {
-        blur_radius: f32,
-        color: ColorF,
-        clip_mode: BoxShadowClipMode,
-        image_kind: BrushImageKind,
-        content_rect: LayerRect,
-        cache_key: BoxShadowCacheKey,
-    },
     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>,
@@ -107,31 +83,21 @@ pub enum PictureKind {
         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,
     },
 }
 
-// The type of surface that a picture can be drawn to.
-// RenderTask surfaces are not retained across frames.
-// TextureCache surfaces are stored across frames, and
-// also shared between display lists.
-#[derive(Debug)]
-pub enum PictureSurface {
-    RenderTask(RenderTaskId),
-    TextureCache(CacheItem),
-}
-
 #[derive(Debug)]
 pub struct PicturePrimitive {
     // If this picture is drawn to an intermediate surface,
     // the associated target information.
-    pub surface: Option<PictureSurface>,
+    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.
@@ -188,44 +154,16 @@ impl PicturePrimitive {
                     }
                     _ => true,
                 }
             }
             _ => true
         }
     }
 
-    pub fn new_box_shadow(
-        blur_radius: f32,
-        color: ColorF,
-        clip_mode: BoxShadowClipMode,
-        image_kind: BrushImageKind,
-        cache_key: BoxShadowCacheKey,
-        pipeline_id: PipelineId,
-    ) -> Self {
-        PicturePrimitive {
-            runs: Vec::new(),
-            surface: None,
-            kind: PictureKind::BoxShadow {
-                blur_radius,
-                color,
-                clip_mode,
-                image_kind,
-                content_rect: LayerRect::zero(),
-                cache_key,
-            },
-            pipeline_id,
-            cull_children: false,
-            brush: BrushPrimitive::new(
-                BrushKind::Picture,
-                None,
-            ),
-        }
-    }
-
     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>,
     ) -> Self {
         PicturePrimitive {
@@ -264,18 +202,18 @@ impl PicturePrimitive {
 
         self.runs.push(PrimitiveRun {
             base_prim_index: prim_index,
             count: 1,
             clip_and_scroll,
         });
     }
 
-    pub fn update_local_rect(&mut self,
-        prim_local_rect: LayerRect,
+    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;
 
@@ -299,35 +237,16 @@ impl PicturePrimitive {
 
                 *content_rect = local_content_rect.inflate(
                     blur_offset,
                     blur_offset,
                 );
 
                 content_rect.translate(&offset)
             }
-            PictureKind::BoxShadow { blur_radius, clip_mode, ref mut content_rect, .. } => {
-                // We need to inflate the content rect if outset.
-                *content_rect = match clip_mode {
-                    BoxShadowClipMode::Outset => {
-                        let full_offset = blur_radius * BLUR_SAMPLE_SCALE;
-                        // For a non-uniform radii, we need to expand
-                        // the content rect on all sides for the blur.
-                        local_content_rect.inflate(
-                            full_offset,
-                            full_offset,
-                        )
-                    }
-                    BoxShadowClipMode::Inset => {
-                        local_content_rect
-                    }
-                };
-
-                prim_local_rect
-            }
         }
     }
 
     pub fn prepare_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_screen_rect: &DeviceIntRect,
         prim_local_rect: &LayerRect,
@@ -357,28 +276,28 @@ impl PicturePrimitive {
                             ClearMode::Transparent,
                             pic_state_for_children.tasks,
                             PictureType::Image,
                         );
 
                         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(
+                        let blur_render_task = RenderTask::new_blur(
                             blur_std_deviation,
                             picture_task_id,
                             frame_state.render_tasks,
                             RenderTargetKind::Color,
                             ClearMode::Transparent,
                             PremultipliedColorF::TRANSPARENT,
                         );
 
                         let render_task_id = frame_state.render_tasks.add(blur_render_task);
                         pic_state.tasks.push(render_task_id);
-                        self.surface = Some(PictureSurface::RenderTask(render_task_id));
+                        self.surface = Some(render_task_id);
                     }
                     Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, color))) => {
                         let rect = (prim_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,
                             ContentOrigin::Screen(rect.origin),
@@ -387,30 +306,30 @@ impl PicturePrimitive {
                             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(
+                        let blur_render_task = RenderTask::new_blur(
                             blur_std_deviation.round(),
                             picture_task_id,
                             frame_state.render_tasks,
                             RenderTargetKind::Color,
                             ClearMode::Transparent,
                             color.premultiplied(),
                         );
 
                         *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(PictureSurface::RenderTask(render_task_id));
+                        self.surface = Some(render_task_id);
                     }
                     Some(PictureCompositeMode::MixBlend(..)) => {
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, prim_screen_rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             content_origin,
                             PremultipliedColorF::TRANSPARENT,
@@ -421,17 +340,17 @@ impl PicturePrimitive {
 
                         let readback_task_id = frame_state.render_tasks.add(RenderTask::new_readback(*prim_screen_rect));
 
                         *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(PictureSurface::RenderTask(render_task_id));
+                        self.surface = Some(render_task_id);
                     }
                     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() {
@@ -455,34 +374,34 @@ impl PicturePrimitive {
                                 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(PictureSurface::RenderTask(render_task_id));
+                            self.surface = Some(render_task_id);
                         }
                     }
                     Some(PictureCompositeMode::Blit) => {
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, prim_screen_rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             content_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(PictureSurface::RenderTask(render_task_id));
+                        self.surface = Some(render_task_id);
                     }
                     None => {
                         pic_state.tasks.extend(pic_state_for_children.tasks);
                         self.surface = None;
                     }
                 }
             }
             PictureKind::TextShadow { blur_radius, color, content_rect, .. } => {
@@ -506,99 +425,28 @@ impl PicturePrimitive {
                     color.premultiplied(),
                     ClearMode::Transparent,
                     Vec::new(),
                     PictureType::TextShadow,
                 );
 
                 let picture_task_id = frame_state.render_tasks.add(picture_task);
 
-                let (blur_render_task, _) = RenderTask::new_blur(
+                let blur_render_task = RenderTask::new_blur(
                     blur_std_deviation,
                     picture_task_id,
                     frame_state.render_tasks,
                     RenderTargetKind::Color,
                     ClearMode::Transparent,
                     color.premultiplied(),
                 );
 
                 let render_task_id = frame_state.render_tasks.add(blur_render_task);
                 pic_state.tasks.push(render_task_id);
-                self.surface = Some(PictureSurface::RenderTask(render_task_id));
-            }
-            PictureKind::BoxShadow { blur_radius, clip_mode, color, content_rect, cache_key, .. } => {
-                // TODO(gw): Rounding the content rect here to device pixels is not
-                // technically correct. Ideally we should ceil() here, and ensure that
-                // the extra part pixel in the case of fractional sizes is correctly
-                // handled. For now, just use rounding which passes the existing
-                // Gecko tests.
-                let cache_size = to_cache_size(content_rect.size * content_scale);
-
-                // Request the texture cache item for this box-shadow key. If it
-                // doesn't exist in the cache, the closure is invoked to build
-                // a render task chain to draw the cacheable result.
-                let cache_item = frame_state.resource_cache.request_render_task(
-                    RenderTaskCacheKey {
-                        size: cache_size,
-                        kind: RenderTaskCacheKeyKind::BoxShadow(cache_key),
-                    },
-                    frame_state.gpu_cache,
-                    frame_state.render_tasks,
-                    |render_tasks| {
-                        // 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_clear_mode = match clip_mode {
-                            BoxShadowClipMode::Outset => {
-                                ClearMode::One
-                            }
-                            BoxShadowClipMode::Inset => {
-                                ClearMode::Zero
-                            }
-                        };
-
-                        let picture_task = RenderTask::new_picture(
-                            RenderTaskLocation::Dynamic(None, cache_size),
-                            prim_index,
-                            RenderTargetKind::Alpha,
-                            ContentOrigin::Local(content_rect.origin),
-                            color.premultiplied(),
-                            ClearMode::Zero,
-                            Vec::new(),
-                            PictureType::BoxShadow,
-                        );
-
-                        let picture_task_id = render_tasks.add(picture_task);
-
-                        let (blur_render_task, scale_factor) = RenderTask::new_blur(
-                            blur_std_deviation,
-                            picture_task_id,
-                            render_tasks,
-                            RenderTargetKind::Alpha,
-                            blur_clear_mode,
-                            color.premultiplied(),
-                        );
-
-                        let root_task_id = render_tasks.add(blur_render_task);
-                        pic_state.tasks.push(root_task_id);
-
-                        // TODO(gw): Remove the nastiness with having to pass
-                        //           the scale factor through the texture cache
-                        //           item user data. This will disappear once
-                        //           the brush_picture shader is updated to draw
-                        //           segments, since the scale factor will not
-                        //           be used at all then during drawing.
-                        (root_task_id, [scale_factor, 0.0, 0.0], false)
-                    }
-                );
-
-                self.surface = Some(PictureSurface::TextureCache(cache_item));
+                self.surface = Some(render_task_id);
             }
         }
     }
 
     pub fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
         // TODO(gw): It's unfortunate that we pay a fixed cost
         //           of 5 GPU blocks / picture, just due to the size
         //           of the color matrix. There aren't typically very
@@ -629,14 +477,11 @@ impl PicturePrimitive {
 
                         request.push([amount, 1.0 - amount, 0.0, 0.0]);
                     }
                     _ => {
                         request.push([0.0; 4]);
                     }
                 }
             }
-            PictureKind::BoxShadow { color, .. } => {
-                request.push(color.premultiplied());
-            }
         }
     }
 }
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipMode, ColorF, ComplexClipRegion};
+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, PremultipliedColorF, YuvColorSpace, YuvFormat};
 use border::{BorderCornerInstance, BorderEdgeKind};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
@@ -185,21 +185,16 @@ pub struct PrimitiveMetadata {
 
     /// A tag used to identify this primitive outside of WebRender. This is
     /// used for returning useful data during hit testing.
     pub tag: Option<ItemTag>,
 }
 
 #[derive(Debug)]
 pub enum BrushKind {
-    Mask {
-        clip_mode: ClipMode,
-        rect: LayerRect,
-        radii: BorderRadius,
-    },
     Solid {
         color: ColorF,
     },
     Clear,
     Line {
         color: PremultipliedColorF,
         wavy_line_thickness: f32,
         style: LineStyle,
@@ -243,17 +238,16 @@ impl BrushKind {
         match *self {
             BrushKind::Solid { .. } |
             BrushKind::Picture |
             BrushKind::Image { .. } |
             BrushKind::YuvImage { .. } |
             BrushKind::RadialGradient { .. } |
             BrushKind::LinearGradient { .. } => true,
 
-            BrushKind::Mask { .. } |
             BrushKind::Clear |
             BrushKind::Line { .. } => false,
         }
     }
 }
 
 bitflags! {
     /// Each bit of the edge AA mask is:
@@ -335,37 +329,16 @@ impl BrushPrimitive {
             }
             BrushKind::Solid { color } => {
                 request.push(color.premultiplied());
             }
             BrushKind::Clear => {
                 // Opaque black with operator dest out
                 request.push(PremultipliedColorF::BLACK);
             }
-            BrushKind::Mask { clip_mode, rect, radii } => {
-                request.push([
-                    clip_mode as u32 as f32,
-                    0.0,
-                    0.0,
-                    0.0
-                ]);
-                request.push(rect);
-                request.push([
-                    radii.top_left.width,
-                    radii.top_left.height,
-                    radii.top_right.width,
-                    radii.top_right.height,
-                ]);
-                request.push([
-                    radii.bottom_right.width,
-                    radii.bottom_right.height,
-                    radii.bottom_left.width,
-                    radii.bottom_left.height,
-                ]);
-            }
             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,
                 ]);
@@ -985,17 +958,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::Mask { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Line { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Image { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::YuvImage { .. } => PrimitiveOpacity::opaque(),
                     BrushKind::RadialGradient { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::LinearGradient { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Picture => {
                         // TODO(gw): This is not currently used. In the future
                         //           we should detect opaque pictures.
@@ -1201,17 +1173,17 @@ impl PrimitiveStore {
                                     );
                                     let target_to_cache_task_id = render_tasks.add(target_to_cache_task);
 
                                     // Hook this into the render task tree at the right spot.
                                     pic_state.tasks.push(target_to_cache_task_id);
 
                                     // Pass the image opacity, so that the cached render task
                                     // item inherits the same opacity properties.
-                                    (target_to_cache_task_id, [0.0; 3], image_properties.descriptor.is_opaque)
+                                    (target_to_cache_task_id, image_properties.descriptor.is_opaque)
                                 }
                             );
                         }
                         ImageSource::Default => {
                             // Normal images just reference the source texture each frame.
                             request_source_image = true;
                         }
                     }
@@ -1283,17 +1255,16 @@ impl PrimitiveStore {
                                 pic_context.display_list,
                             );
                             gradient_builder.build(
                                 reverse_stops,
                                 &mut request,
                             );
                         }
                     }
-                    BrushKind::Mask { .. } |
                     BrushKind::Solid { .. } |
                     BrushKind::Clear |
                     BrushKind::Line { .. } |
                     BrushKind::Picture { .. } => {}
                 }
             }
         }
 
@@ -1405,16 +1376,41 @@ impl PrimitiveStore {
             for &(ref clip, _) in &local_clips.clips {
                 let (local_clip_rect, radius, mode) = match *clip {
                     ClipSource::RoundedRectangle(rect, radii, clip_mode) => {
                         (rect, Some(radii), clip_mode)
                     }
                     ClipSource::Rectangle(rect) => {
                         (rect, None, ClipMode::Clip)
                     }
+                    ClipSource::BoxShadow(ref info) => {
+                        // For inset box shadows, we can clip out any
+                        // pixels that are inside the shadow region
+                        // and are beyond the inner rect, as they can't
+                        // be affected by the blur radius.
+                        let inner_clip_mode = match info.clip_mode {
+                            BoxShadowClipMode::Outset => None,
+                            BoxShadowClipMode::Inset => Some(ClipMode::ClipOut),
+                        };
+
+                        // Push a region into the segment builder where the
+                        // box-shadow can have an effect on the result. This
+                        // ensures clip-mask tasks get allocated for these
+                        // pixel regions, even if no other clips affect them.
+                        segment_builder.push_mask_region(
+                            info.prim_shadow_rect,
+                            info.prim_shadow_rect.inflate(
+                                -0.5 * info.shadow_rect_alloc_size.width,
+                                -0.5 * info.shadow_rect_alloc_size.height,
+                            ),
+                            inner_clip_mode,
+                        );
+
+                        continue;
+                    }
                     ClipSource::BorderCorner(..) |
                     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;
                     }
@@ -1434,17 +1430,17 @@ impl PrimitiveStore {
                     let relative_transform = prim_transform
                         .inverse()
                         .unwrap_or(WorldToLayerFastTransform::identity())
                         .pre_mul(&clip_transform.into());
 
                     relative_transform.transform_rect(&local_clip_rect)
                 };
 
-                segment_builder.push_rect(local_clip_rect, radius, mode);
+                segment_builder.push_clip_rect(local_clip_rect, radius, mode);
             }
         }
 
         match brush.segment_desc {
             Some(ref mut segment_desc) => {
                 segment_desc.clip_mask_kind = clip_mask_kind;
             }
             None => {
@@ -1526,16 +1522,20 @@ impl PrimitiveStore {
             );
 
             let intersected_rect = combined_outer_rect.intersection(&segment_screen_rect);
             segment.clip_task_id = intersected_rect.map(|bounds| {
                 let clip_task = RenderTask::new_mask(
                     bounds,
                     clips.clone(),
                     prim_run_context.scroll_node.coordinate_system_id,
+                    frame_state.clip_store,
+                    frame_state.gpu_cache,
+                    frame_state.resource_cache,
+                    frame_state.render_tasks,
                 );
 
                 let clip_task_id = frame_state.render_tasks.add(clip_task);
                 pic_state.tasks.push(clip_task_id);
 
                 clip_task_id
             })
         }
@@ -1570,16 +1570,17 @@ impl PrimitiveStore {
         let transform = &prim_run_context.scroll_node.world_content_transform;
         let extra_clip =  {
             let metadata = &self.cpu_metadata[prim_index.0];
             metadata.clip_sources.as_ref().map(|ref clip_sources| {
                 let prim_clips = frame_state.clip_store.get_mut(clip_sources);
                 prim_clips.update(
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
+                    frame_context.device_pixel_scale,
                 );
                 let (screen_inner_rect, screen_outer_rect) =
                     prim_clips.get_screen_bounds(transform, frame_context.device_pixel_scale);
 
                 if let Some(outer) = screen_outer_rect {
                     combined_outer_rect = combined_outer_rect.and_then(|r| r.intersection(&outer));
                 }
 
@@ -1658,16 +1659,20 @@ impl PrimitiveStore {
         ) {
             return true;
         }
 
         let clip_task = RenderTask::new_mask(
             combined_outer_rect,
             clips,
             prim_coordinate_system_id,
+            frame_state.clip_store,
+            frame_state.gpu_cache,
+            frame_state.resource_cache,
+            frame_state.render_tasks,
         );
 
         let clip_task_id = frame_state.render_tasks.add(clip_task);
         self.cpu_metadata[prim_index.0].clip_task_id = Some(clip_task_id);
         pic_state.tasks.push(clip_task_id);
 
         true
     }
@@ -1711,17 +1716,16 @@ impl PrimitiveStore {
                     return None;
                 }
 
                 let (draw_text_transformed, 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))
                     }
-                    PictureKind::BoxShadow { .. } |
                     PictureKind::TextShadow { .. } => {
                         (false, None)
                     }
                 };
 
                 let display_list = &frame_context
                     .pipelines
                     .get(&pic.pipeline_id)
@@ -1751,20 +1755,17 @@ impl PrimitiveStore {
                 frame_state,
             );
 
             // Restore the dependencies (borrow check dance)
             let pic = &mut self.cpu_pictures[cpu_prim_index.0];
             pic.runs = pic_context_for_children.prim_runs;
 
             let metadata = &mut self.cpu_metadata[prim_index.0];
-            metadata.local_rect = pic.update_local_rect(
-                metadata.local_rect,
-                result,
-            );
+            metadata.local_rect = pic.update_local_rect(result);
         }
 
         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;
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,26 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, ImageDescriptor, ImageFormat};
-use api::PremultipliedColorF;
-use box_shadow::BoxShadowCacheKey;
-use clip::ClipWorkItem;
+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, GpuCacheHandle};
-use gpu_types::{ImageSource, PictureType};
+use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
+use gpu_types::{ImageSource, PictureType, RasterizationSpace};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::ContentOrigin;
 use prim_store::{PrimitiveIndex, ImageCacheKey};
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
-use resource_cache::CacheItem;
+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;
 pub const MAX_BLUR_STD_DEVIATION: f32 = 4.0;
 pub const MIN_DOWNSCALING_RT_SIZE: i32 = 128;
@@ -152,42 +152,47 @@ pub struct CacheMaskTask {
     actual_rect: DeviceIntRect,
     pub clips: Vec<ClipWorkItem>,
     pub coordinate_system_id: CoordinateSystemId,
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ClipRegionTask {
+    pub clip_data_address: GpuCacheAddress,
+}
+
+#[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: ContentOrigin,
     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,
     pub target_kind: RenderTargetKind,
     pub color: PremultipliedColorF,
-    pub scale_factor: f32,
     pub uv_rect_handle: GpuCacheHandle,
 }
 
 impl BlurTask {
     #[cfg(feature = "debugger")]
     fn print_with<T: PrintTreePrinter>(&self, pt: &mut T) {
         pt.add_item(format!("std deviation: {}", self.blur_std_deviation));
         pt.add_item(format!("target: {:?}", self.target_kind));
-        pt.add_item(format!("scale: {}", self.scale_factor));
     }
 }
 
 // Where the source data for a blit task can be found.
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BlitSource {
@@ -214,16 +219,17 @@ pub struct RenderTaskData {
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskKind {
     Picture(PictureTask),
     CacheMask(CacheMaskTask),
+    ClipRegion(ClipRegionTask),
     VerticalBlur(BlurTask),
     HorizontalBlur(BlurTask),
     Readback(DeviceIntRect),
     Scaling(RenderTargetKind),
     Blit(BlitTask),
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
@@ -311,30 +317,115 @@ impl RenderTask {
             saved_index: None,
         }
     }
 
     pub fn new_mask(
         outer_rect: DeviceIntRect,
         clips: Vec<ClipWorkItem>,
         prim_coordinate_system_id: CoordinateSystemId,
+        clip_store: &mut ClipStore,
+        gpu_cache: &mut GpuCache,
+        resource_cache: &mut ResourceCache,
+        render_tasks: &mut RenderTaskTree,
     ) -> Self {
+        let mut children = Vec::new();
+
+        // Step through the clip sources that make up this mask. If we find
+        // any box-shadow clip sources, request that image from the render
+        // task cache. This allows the blurred box-shadow rect to be cached
+        // in the texture cache across frames.
+        // TODO(gw): Consider moving this logic outside this function, especially
+        //           as we add more clip sources that depend on render tasks.
+        // TODO(gw): If this ever shows up in a profile, we could pre-calculate
+        //           whether a ClipSources contains any box-shadows and skip
+        //           this iteration for the majority of cases.
+        for clip_item in &clips {
+            let clip_sources = clip_store.get_opt_mut(&clip_item.clip_sources).expect("bug");
+            for &mut (ref mut clip, _) in &mut clip_sources.clips {
+                match *clip {
+                    ClipSource::BoxShadow(ref mut info) => {
+                        let (cache_size, cache_key) = info.cache_key
+                            .as_ref()
+                            .expect("bug: no cache key set")
+                            .clone();
+                        let blur_radius_dp = cache_key.blur_radius_dp as f32;
+                        let clip_data_address = gpu_cache.get_address(&info.clip_data_handle);
+
+                        // Request a cacheable render task with a blurred, minimal
+                        // sized box-shadow rect.
+                        info.cache_item = resource_cache.request_render_task(
+                            RenderTaskCacheKey {
+                                size: cache_size,
+                                kind: RenderTaskCacheKeyKind::BoxShadow(cache_key),
+                            },
+                            gpu_cache,
+                            render_tasks,
+                            |render_tasks| {
+                                // Draw the rounded rect.
+                                let mask_task = RenderTask::new_rounded_rect_mask(
+                                    cache_size,
+                                    clip_data_address,
+                                );
+
+                                let mask_task_id = render_tasks.add(mask_task);
+
+                                // Blur it
+                                let blur_render_task = RenderTask::new_blur(
+                                    blur_radius_dp,
+                                    mask_task_id,
+                                    render_tasks,
+                                    RenderTargetKind::Alpha,
+                                    ClearMode::Zero,
+                                    PremultipliedColorF::TRANSPARENT,
+                                );
+
+                                let root_task_id = render_tasks.add(blur_render_task);
+                                children.push(root_task_id);
+
+                                (root_task_id, false)
+                            }
+                        );
+                    }
+                    ClipSource::Rectangle(..) |
+                    ClipSource::RoundedRectangle(..) |
+                    ClipSource::Image(..) |
+                    ClipSource::BorderCorner(..) => {}
+                }
+            }
+        }
+
         RenderTask {
-            children: Vec::new(),
+            children,
             location: RenderTaskLocation::Dynamic(None, outer_rect.size),
             kind: RenderTaskKind::CacheMask(CacheMaskTask {
                 actual_rect: outer_rect,
                 clips,
                 coordinate_system_id: prim_coordinate_system_id,
             }),
             clear_mode: ClearMode::One,
             saved_index: None,
         }
     }
 
+    pub fn new_rounded_rect_mask(
+        size: DeviceIntSize,
+        clip_data_address: GpuCacheAddress,
+    ) -> Self {
+        RenderTask {
+            children: Vec::new(),
+            location: RenderTaskLocation::Dynamic(None, size),
+            kind: RenderTaskKind::ClipRegion(ClipRegionTask {
+                clip_data_address,
+            }),
+            clear_mode: ClearMode::One,
+            saved_index: None,
+        }
+    }
+
     // Construct a render task to apply a blur to a primitive.
     // The render task chain that is constructed looks like:
     //
     //    PrimitiveCacheTask: Draw the primitives.
     //           ^
     //           |
     //    DownscalingTask(s): Each downscaling task reduces the size of render target to
     //           ^            half. Also reduce the std deviation to half until the std
@@ -350,17 +441,17 @@ impl RenderTask {
     //
     pub fn new_blur(
         blur_std_deviation: f32,
         src_task_id: RenderTaskId,
         render_tasks: &mut RenderTaskTree,
         target_kind: RenderTargetKind,
         clear_mode: ClearMode,
         color: PremultipliedColorF,
-    ) -> (Self, f32) {
+    ) -> Self {
         // Adjust large std deviation value.
         let mut adjusted_blur_std_deviation = blur_std_deviation;
         let blur_target_size = render_tasks[src_task_id].get_dynamic_size();
         let mut adjusted_blur_target_size = blur_target_size;
         let mut downscaling_src_task_id = src_task_id;
         let mut scale_factor = 1.0;
         while adjusted_blur_std_deviation > MAX_BLUR_STD_DEVIATION {
             if adjusted_blur_target_size.width < MIN_DOWNSCALING_RT_SIZE ||
@@ -372,49 +463,46 @@ impl RenderTask {
             adjusted_blur_target_size = (blur_target_size.to_f32() / scale_factor).to_i32();
             let downscaling_task = RenderTask::new_scaling(
                 target_kind,
                 downscaling_src_task_id,
                 adjusted_blur_target_size,
             );
             downscaling_src_task_id = render_tasks.add(downscaling_task);
         }
-        scale_factor = blur_target_size.width as f32 / adjusted_blur_target_size.width as f32;
 
         let blur_task_v = RenderTask {
             children: vec![downscaling_src_task_id],
             location: RenderTaskLocation::Dynamic(None, adjusted_blur_target_size),
             kind: RenderTaskKind::VerticalBlur(BlurTask {
                 blur_std_deviation: adjusted_blur_std_deviation,
                 target_kind,
                 color,
-                scale_factor,
                 uv_rect_handle: GpuCacheHandle::new(),
             }),
             clear_mode,
             saved_index: None,
         };
 
         let blur_task_v_id = render_tasks.add(blur_task_v);
 
         let blur_task_h = RenderTask {
             children: vec![blur_task_v_id],
             location: RenderTaskLocation::Dynamic(None, adjusted_blur_target_size),
             kind: RenderTaskKind::HorizontalBlur(BlurTask {
                 blur_std_deviation: adjusted_blur_std_deviation,
                 target_kind,
                 color,
-                scale_factor,
                 uv_rect_handle: GpuCacheHandle::new(),
             }),
             clear_mode,
             saved_index: None,
         };
 
-        (blur_task_h, scale_factor)
+        blur_task_h
     }
 
     pub fn new_scaling(
         target_kind: RenderTargetKind,
         src_task_id: RenderTaskId,
         target_size: DeviceIntSize,
     ) -> Self {
         RenderTask {
@@ -461,27 +549,37 @@ impl RenderTask {
                     task.color.to_array()
                 )
             }
             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],
+                )
+            }
+            RenderTaskKind::ClipRegion(..) => {
+                (
+                    [
                         0.0,
+                        0.0,
+                        RasterizationSpace::Local as i32 as f32,
                     ],
                     [0.0; 4],
                 )
             }
             RenderTaskKind::VerticalBlur(ref task) |
             RenderTaskKind::HorizontalBlur(ref task) => {
                 (
                     [
                         task.blur_std_deviation,
-                        task.scale_factor,
+                        0.0,
                         0.0,
                     ],
                     task.color.to_array()
                 )
             }
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) => {
@@ -516,16 +614,17 @@ impl RenderTask {
         match self.kind {
             RenderTaskKind::Picture(ref info) => {
                 &info.uv_rect_handle
             }
             RenderTaskKind::VerticalBlur(ref info) |
             RenderTaskKind::HorizontalBlur(ref info) => {
                 &info.uv_rect_handle
             }
+            RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::CacheMask(..) => {
                 panic!("texture handle not supported for this task kind");
             }
         }
     }
@@ -568,16 +667,17 @@ impl RenderTask {
             }
         }
     }
 
     pub fn target_kind(&self) -> RenderTargetKind {
         match self.kind {
             RenderTaskKind::Readback(..) => RenderTargetKind::Color,
 
+            RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) => {
                 RenderTargetKind::Alpha
             }
 
             RenderTaskKind::VerticalBlur(ref task_info) |
             RenderTaskKind::HorizontalBlur(ref task_info) => {
                 task_info.target_kind
             }
@@ -604,18 +704,25 @@ impl RenderTask {
     // if we decide that is useful.
     pub fn is_shared(&self) -> bool {
         match self.kind {
             RenderTaskKind::Picture(..) |
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::HorizontalBlur(..) |
             RenderTaskKind::Scaling(..) |
+            RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Blit(..) => false,
-            RenderTaskKind::CacheMask(..) => true,
+
+            // TODO(gw): For now, we've disabled the shared clip mask
+            //           optimization. It's of dubious value in the
+            //           future once we start to cache clip tasks anyway.
+            //           I have left shared texture support here though,
+            //           just in case we want it in the future.
+            RenderTaskKind::CacheMask(..) => false,
         }
     }
 
     pub fn prepare_for_render(
         &mut self,
         gpu_cache: &mut GpuCache,
     ) {
         let (target_rect, target_index) = self.get_target_rect();
@@ -626,16 +733,17 @@ impl RenderTask {
                 (&mut info.uv_rect_handle, info.color)
             }
             RenderTaskKind::Picture(ref mut info) => {
                 (&mut info.uv_rect_handle, info.color)
             }
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
+            RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) => {
                 return;
             }
         };
 
         if let Some(mut request) = gpu_cache.request(cache_handle) {
             let image_source = ImageSource {
                 p0: target_rect.origin.to_f32(),
@@ -654,16 +762,19 @@ impl RenderTask {
             RenderTaskKind::Picture(ref task) => {
                 pt.new_level(format!("Picture of {:?}", task.prim_index));
                 pt.add_item(format!("kind: {:?}", task.target_kind));
             }
             RenderTaskKind::CacheMask(ref task) => {
                 pt.new_level(format!("CacheMask with {} clips", task.clips.len()));
                 pt.add_item(format!("rect: {:?}", task.actual_rect));
             }
+            RenderTaskKind::ClipRegion(..) => {
+                pt.new_level("ClipRegion".to_owned());
+            }
             RenderTaskKind::VerticalBlur(ref task) => {
                 pt.new_level("VerticalBlur".to_owned());
                 task.print_with(pt);
             }
             RenderTaskKind::HorizontalBlur(ref task) => {
                 pt.new_level("HorizontalBlur".to_owned());
                 task.print_with(pt);
             }
@@ -772,30 +883,30 @@ impl RenderTaskCache {
 
     pub fn request_render_task<F>(
         &mut self,
         key: RenderTaskCacheKey,
         texture_cache: &mut TextureCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         mut f: F,
-    ) -> CacheItem where F: FnMut(&mut RenderTaskTree) -> (RenderTaskId, [f32; 3], bool) {
+    ) -> CacheItem where F: FnMut(&mut RenderTaskTree) -> (RenderTaskId, bool) {
         // Get the texture cache handle for this cache key,
         // or create one.
         let cache_entry = self.entries
                               .entry(key)
                               .or_insert(RenderTaskCacheEntry {
                                   handle: TextureCacheHandle::new(),
                               });
 
         // Check if this texture cache handle is valie.
         if texture_cache.request(&mut cache_entry.handle, gpu_cache) {
             // Invoke user closure to get render task chain
             // to draw this into the texture cache.
-            let (render_task_id, user_data, is_opaque) = f(render_tasks);
+            let (render_task_id, is_opaque) = f(render_tasks);
             let render_task = &mut render_tasks[render_task_id];
 
             // Select the right texture page to allocate from.
             let image_format = match render_task.target_kind() {
                 RenderTargetKind::Color => ImageFormat::BGRA8,
                 RenderTargetKind::Alpha => ImageFormat::R8,
             };
 
@@ -820,17 +931,17 @@ impl RenderTaskCache {
 
             // Allocate space in the texture cache, but don't supply
             // and CPU-side data to be uploaded.
             texture_cache.update(
                 &mut cache_entry.handle,
                 descriptor,
                 TextureFilter::Linear,
                 None,
-                user_data,
+                [0.0; 3],
                 None,
                 gpu_cache,
             );
 
             // Get the allocation details in the texture cache, and store
             // this in the render task. The renderer will draw this
             // task into the appropriate layer and rect of the texture
             // cache on this frame.
@@ -844,8 +955,22 @@ impl RenderTaskCache {
             );
         }
 
         // Finally, return the texture cache handle that we know
         // is now up to date.
         texture_cache.get(&cache_entry.handle)
     }
 }
+
+// TODO(gw): Rounding the content rect here to device pixels is not
+// technically correct. Ideally we should ceil() here, and ensure that
+// the extra part pixel in the case of fractional sizes is correctly
+// handled. For now, just use rounding which passes the existing
+// Gecko tests.
+// Note: zero-square tasks are prohibited in WR task tree, so
+// we ensure each dimension to be at least the length of 1 after rounding.
+pub fn to_cache_size(size: DeviceSize) -> DeviceIntSize {
+    DeviceIntSize::new(
+        1.max(size.width.round() as i32),
+        1.max(size.height.round() as i32),
+    )
+}
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -108,24 +108,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_MASK: GpuProfileTag = GpuProfileTag {
-    label: "B_Mask",
-    color: debug_colors::BLACK,
-};
-const GPU_TAG_BRUSH_PICTURE: GpuProfileTag = GpuProfileTag {
-    label: "B_Picture",
-    color: debug_colors::SILVER,
-};
 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,
 };
@@ -216,17 +208,16 @@ impl TransformBatchKind {
 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::Picture => "Brush (Picture)",
                     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)",
@@ -237,17 +228,16 @@ impl BatchKind {
     }
 
     fn gpu_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::Picture => GPU_TAG_BRUSH_PICTURE,
                     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,
@@ -1597,31 +1587,30 @@ pub struct Renderer {
     // 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.
     cs_text_run: LazilyCompiledShader,
     cs_blur_a8: LazilyCompiledShader,
     cs_blur_rgba8: LazilyCompiledShader,
 
     // Brush shaders
-    brush_mask_rounded_rect: LazilyCompiledShader,
-    brush_picture: BrushShader,
     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.
     cs_clip_rectangle: LazilyCompiledShader,
+    cs_clip_box_shadow: LazilyCompiledShader,
     cs_clip_image: LazilyCompiledShader,
     cs_clip_border: 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
@@ -1796,24 +1785,16 @@ impl Renderer {
         let cs_text_run = try!{
             LazilyCompiledShader::new(ShaderKind::Cache(VertexArrayKind::Primitive),
                                       "cs_text_run",
                                       &[],
                                       &mut device,
                                       options.precache_shaders)
         };
 
-        let brush_mask_rounded_rect = try!{
-            LazilyCompiledShader::new(ShaderKind::Brush,
-                                      "brush_mask_rounded_rect",
-                                      &[],
-                                      &mut device,
-                                      options.precache_shaders)
-        };
-
         let brush_solid = try!{
             BrushShader::new("brush_solid",
                              &mut device,
                              &[],
                              options.precache_shaders)
         };
 
         let brush_line = try!{
@@ -1832,23 +1813,16 @@ impl Renderer {
 
         let brush_mix_blend = try!{
             BrushShader::new("brush_mix_blend",
                              &mut device,
                              &[],
                              options.precache_shaders)
         };
 
-        let brush_picture = try!{
-            BrushShader::new("brush_picture",
-                             &mut device,
-                             &[],
-                             options.precache_shaders)
-        };
-
         let brush_radial_gradient = try!{
             BrushShader::new("brush_radial_gradient",
                              &mut device,
                              if options.enable_dithering {
                                 &dithering_feature
                              } else {
                                 &[]
                              },
@@ -1885,16 +1859,24 @@ impl Renderer {
         let cs_clip_rectangle = try!{
             LazilyCompiledShader::new(ShaderKind::ClipCache,
                                       "cs_clip_rectangle",
                                       &[],
                                       &mut device,
                                       options.precache_shaders)
         };
 
+        let cs_clip_box_shadow = try!{
+            LazilyCompiledShader::new(ShaderKind::ClipCache,
+                                      "cs_clip_box_shadow",
+                                      &[],
+                                      &mut device,
+                                      options.precache_shaders)
+        };
+
         let cs_clip_image = try!{
             LazilyCompiledShader::new(ShaderKind::ClipCache,
                                       "cs_clip_image",
                                       &[],
                                       &mut device,
                                       options.precache_shaders)
         };
 
@@ -2267,27 +2249,26 @@ impl Renderer {
             device,
             active_documents: Vec::new(),
             pending_texture_updates: Vec::new(),
             pending_gpu_cache_updates: Vec::new(),
             pending_shader_updates: Vec::new(),
             cs_text_run,
             cs_blur_a8,
             cs_blur_rgba8,
-            brush_mask_rounded_rect,
-            brush_picture,
             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,
             ps_text_run,
             ps_text_run_dual_source,
             ps_image,
             ps_border_corner,
             ps_border_edge,
             ps_hw_composite,
@@ -2510,35 +2491,35 @@ impl Renderer {
             target.clip_batcher.border_clears.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Clip,
             "Borders",
             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::Cache,
             "Vertical Blur",
             target.vertical_blurs.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Cache,
             "Horizontal Blur",
             target.horizontal_blurs.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Clip,
             "Rectangles",
             target.clip_batcher.rectangles.len(),
         );
-        debug_target.add(
-            debug_server::BatchKind::Cache,
-            "Rectangle Brush (Rounded Rect)",
-            target.brush_mask_rounded_rects.len(),
-        );
         for (_, items) in target.clip_batcher.images.iter() {
             debug_target.add(debug_server::BatchKind::Clip, "Image mask", items.len());
         }
 
         debug_target
     }
 
     #[cfg(feature = "debugger")]
@@ -3197,25 +3178,16 @@ impl Renderer {
                             .bind(
                                 &mut self.device,
                                 key.blend_mode,
                                 projection,
                                 0,
                                 &mut self.renderer_errors,
                             );
                     }
-                    BrushBatchKind::Picture => {
-                        self.brush_picture.bind(
-                            &mut self.device,
-                            key.blend_mode,
-                            projection,
-                            0,
-                            &mut self.renderer_errors,
-                        );
-                    }
                     BrushBatchKind::Line => {
                         self.brush_line.bind(
                             &mut self.device,
                             key.blend_mode,
                             projection,
                             0,
                             &mut self.renderer_errors,
                         );
@@ -3972,30 +3944,16 @@ impl Renderer {
                     &BatchTextures::no_texture(),
                     stats,
                 );
             }
         }
 
         self.handle_scaling(render_tasks, &target.scalings, SourceTexture::CacheA8);
 
-        if !target.brush_mask_rounded_rects.is_empty() {
-            self.device.set_blend(false);
-
-            let _timer = self.gpu_profile.start_timer(GPU_TAG_BRUSH_MASK);
-            self.brush_mask_rounded_rect
-                .bind(&mut self.device, projection, 0, &mut self.renderer_errors);
-            self.draw_instanced_batch(
-                &target.brush_mask_rounded_rects,
-                VertexArrayKind::Primitive,
-                &BatchTextures::no_texture(),
-                stats,
-            );
-        }
-
         // Draw the clip items into the tiled alpha mask.
         {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_CLIP);
 
             // If we have border corner clips, the first step is to clear out the
             // area in the clip mask. This allows drawing multiple invididual clip
             // in regions below.
             if !target.clip_batcher.border_clears.is_empty() {
@@ -4045,16 +4003,36 @@ impl Renderer {
                 );
                 self.draw_instanced_batch(
                     &target.clip_batcher.rectangles,
                     VertexArrayKind::Clip,
                     &BatchTextures::no_texture(),
                     stats,
                 );
             }
+            // draw box-shadow clips
+            for (mask_texture_id, items) in target.clip_batcher.box_shadows.iter() {
+                let _gm2 = self.gpu_profile.start_marker("box-shadows");
+                let textures = BatchTextures {
+                    colors: [
+                        mask_texture_id.clone(),
+                        SourceTexture::Invalid,
+                        SourceTexture::Invalid,
+                    ],
+                };
+                self.cs_clip_box_shadow
+                    .bind(&mut self.device, projection, 0, &mut self.renderer_errors);
+                self.draw_instanced_batch(
+                    items,
+                    VertexArrayKind::Clip,
+                    &textures,
+                    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,
@@ -4678,25 +4656,24 @@ impl Renderer {
         self.texture_resolver.deinit(&mut self.device);
         self.device.delete_vao(self.prim_vao);
         self.device.delete_vao(self.clip_vao);
         self.device.delete_vao(self.blur_vao);
         self.debug.deinit(&mut self.device);
         self.cs_text_run.deinit(&mut self.device);
         self.cs_blur_a8.deinit(&mut self.device);
         self.cs_blur_rgba8.deinit(&mut self.device);
-        self.brush_mask_rounded_rect.deinit(&mut self.device);
-        self.brush_picture.deinit(&mut self.device);
         self.brush_solid.deinit(&mut self.device);
         self.brush_line.deinit(&mut self.device);
         self.brush_blend.deinit(&mut self.device);
         self.brush_mix_blend.deinit(&mut self.device);
         self.brush_radial_gradient.deinit(&mut self.device);
         self.brush_linear_gradient.deinit(&mut self.device);
         self.cs_clip_rectangle.deinit(&mut self.device);
+        self.cs_clip_box_shadow.deinit(&mut self.device);
         self.cs_clip_image.deinit(&mut self.device);
         self.cs_clip_border.deinit(&mut self.device);
         self.ps_text_run.deinit(&mut self.device);
         self.ps_text_run_dual_source.deinit(&mut self.device);
         for shader in self.brush_image {
             if let Some(shader) = shader {
                 shader.deinit(&mut self.device);
             }
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -315,17 +315,17 @@ impl ResourceCache {
     // closure will be invoked to generate the render task
     // chain that is required to draw this task.
     pub fn request_render_task<F>(
         &mut self,
         key: RenderTaskCacheKey,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         f: F,
-    ) -> CacheItem where F: FnMut(&mut RenderTaskTree) -> (RenderTaskId, [f32; 3], bool) {
+    ) -> CacheItem where F: FnMut(&mut RenderTaskTree) -> (RenderTaskId, bool) {
         self.cached_render_tasks.request_render_task(
             key,
             &mut self.texture_cache,
             gpu_cache,
             render_tasks,
             f
         )
     }
--- a/gfx/webrender/src/segment.rs
+++ b/gfx/webrender/src/segment.rs
@@ -139,24 +139,24 @@ impl Event {
     }
 }
 
 // An item that provides some kind of clip region (either
 // a clip in/out rect, or a mask region).
 #[derive(Debug)]
 struct Item {
     rect: LayerRect,
-    mode: ClipMode,
+    mode: Option<ClipMode>,
     flags: ItemFlags,
 }
 
 impl Item {
     fn new(
         rect: LayerRect,
-        mode: ClipMode,
+        mode: Option<ClipMode>,
         has_mask: bool,
     ) -> Item {
         let flags = if has_mask {
             ItemFlags::HAS_MASK
         } else {
             ItemFlags::empty()
         };
 
@@ -187,37 +187,109 @@ impl SegmentBuilder {
         local_clip_rect: LayerRect,
     ) -> SegmentBuilder {
         let mut builder = SegmentBuilder {
             items: Vec::new(),
             bounding_rect: Some(local_rect),
             inner_rect,
         };
 
-        builder.push_rect(local_rect, None, ClipMode::Clip);
-        builder.push_rect(local_clip_rect, None, ClipMode::Clip);
+        builder.push_clip_rect(local_rect, None, ClipMode::Clip);
+        builder.push_clip_rect(local_clip_rect, None, ClipMode::Clip);
 
         builder
     }
 
+    // Push a region defined by an inner and outer rect where there
+    // is a mask required. This ensures that segments which intersect
+    // with these areas will get a clip mask task allocated. This
+    // is currently used to mark where a box-shadow region can affect
+    // the pixels of a clip-mask. It might be useful for other types
+    // such as dashed and dotted borders in the future.
+    pub fn push_mask_region(
+        &mut self,
+        outer_rect: LayerRect,
+        inner_rect: LayerRect,
+        inner_clip_mode: Option<ClipMode>,
+    ) {
+        debug_assert!(outer_rect.contains_rect(&inner_rect));
+
+        let p0 = outer_rect.origin;
+        let p1 = inner_rect.origin;
+        let p2 = inner_rect.bottom_right();
+        let p3 = outer_rect.bottom_right();
+
+        let segments = &[
+            LayerRect::new(
+                LayerPoint::new(p0.x, p0.y),
+                LayerSize::new(p1.x - p0.x, p1.y - p0.y),
+            ),
+            LayerRect::new(
+                LayerPoint::new(p2.x, p0.y),
+                LayerSize::new(p3.x - p2.x, p1.y - p0.y),
+            ),
+            LayerRect::new(
+                LayerPoint::new(p2.x, p2.y),
+                LayerSize::new(p3.x - p2.x, p3.y - p2.y),
+            ),
+            LayerRect::new(
+                LayerPoint::new(p0.x, p2.y),
+                LayerSize::new(p1.x - p0.x, p3.y - p2.y),
+            ),
+            LayerRect::new(
+                LayerPoint::new(p1.x, p0.y),
+                LayerSize::new(p2.x - p1.x, p1.y - p0.y),
+            ),
+            LayerRect::new(
+                LayerPoint::new(p2.x, p1.y),
+                LayerSize::new(p3.x - p2.x, p2.y - p1.y),
+            ),
+            LayerRect::new(
+                LayerPoint::new(p1.x, p2.y),
+                LayerSize::new(p2.x - p1.x, p3.y - p2.y),
+            ),
+            LayerRect::new(
+                LayerPoint::new(p0.x, p1.y),
+                LayerSize::new(p1.x - p0.x, p2.y - p1.y),
+            ),
+        ];
+
+        for segment in segments {
+            self.items.push(Item::new(
+                *segment,
+                None,
+                true
+            ));
+        }
+
+        if inner_clip_mode.is_some() {
+            self.items.push(Item::new(
+                inner_rect,
+                inner_clip_mode,
+                false,
+            ));
+        }
+    }
+
     // Push some kind of clipping region into the segment builder.
     // If radius is None, it's a simple rect.
-    pub fn push_rect(
+    pub fn push_clip_rect(
         &mut self,
         rect: LayerRect,
         radius: Option<BorderRadius>,
         mode: ClipMode,
     ) {
         // Keep track of a minimal bounding rect for the set of
         // segments that will be generated.
         if mode == ClipMode::Clip {
             self.bounding_rect = self.bounding_rect.and_then(|bounding_rect| {
                 bounding_rect.intersection(&rect)
             });
         }
+        let mode = Some(mode);
 
         match radius {
             Some(radius) => {
                 // For a rounded rect, try to create a nine-patch where there
                 // is a clip item for each corner, inner and edge region.
                 match extract_inner_rect_safe(&rect, &radius) {
                     Some(inner) => {
                         let p0 = rect.origin;
@@ -475,17 +547,17 @@ fn emit_segment_if_needed(
     //           in a hash set or similar if this ever
     //           shows up in a profile.
     let mut has_clip_mask = false;
 
     for item in items {
         if item.flags.contains(ItemFlags::X_ACTIVE | ItemFlags::Y_ACTIVE) {
             has_clip_mask |= item.flags.contains(ItemFlags::HAS_MASK);
 
-            if item.mode == ClipMode::ClipOut && !item.flags.contains(ItemFlags::HAS_MASK) {
+            if item.mode == Some(ClipMode::ClipOut) && !item.flags.contains(ItemFlags::HAS_MASK) {
                 return None;
             }
         }
     }
 
     let segment_rect = LayerRect::new(
         LayerPoint::new(
             x0.to_f32_px(),
@@ -573,17 +645,17 @@ mod test {
     ) {
         let mut sb = SegmentBuilder::new(
             local_rect,
             inner_rect,
             local_clip_rect,
         );
         let mut segments = Vec::new();
         for &(rect, radius, mode) in clips {
-            sb.push_rect(rect, radius, mode);
+            sb.push_clip_rect(rect, radius, mode);
         }
         sb.build(|segment| {
             segments.push(Segment {
                 ..*segment
             });
         });
         segments.sort_by(segment_sorter);
         expected_segments.sort_by(segment_sorter);
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -5,25 +5,24 @@
 use api::{ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale, DeviceUintPoint};
 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, BrushFlags, BrushInstance, ClipChainRectIndex};
-use gpu_types::{ClipScrollNodeData, ClipScrollNodeIndex as GPUClipScrollNodeIndex};
-use gpu_types::{PrimitiveInstance};
+use gpu_types::{BlurDirection, BlurInstance};
+use gpu_types::{ClipScrollNodeData};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::{PictureKind};
 use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveKind, PrimitiveStore};
-use prim_store::{BrushKind, DeferredResolve, EdgeAaSegmentMask};
+use prim_store::{DeferredResolve};
 use profiler::FrameProfileCounters;
-use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
+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;
 
 const MIN_TARGET_SIZE: u32 = 2048;
 
 #[derive(Debug)]
@@ -399,16 +398,17 @@ impl RenderTarget for ColorRenderTarget 
                         }
                     }
                     _ => {
                         // No other primitives make use of primitive caching yet!
                         unreachable!()
                     }
                 }
             }
+            RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) => {
                 panic!("Should not be added to color target!");
             }
             RenderTaskKind::Readback(device_rect) => {
                 self.readbacks.push(device_rect);
             }
             RenderTaskKind::Scaling(..) => {
                 self.scalings.push(ScalingInfo {
@@ -475,17 +475,16 @@ impl RenderTarget for ColorRenderTarget 
         })
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct AlphaRenderTarget {
     pub clip_batcher: ClipBatcher,
-    pub brush_mask_rounded_rects: Vec<PrimitiveInstance>,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurInstance>,
     pub horizontal_blurs: Vec<BlurInstance>,
     pub scalings: Vec<ScalingInfo>,
     pub zero_clears: Vec<RenderTaskId>,
     allocator: TextureAllocator,
 }
 
@@ -495,17 +494,16 @@ impl RenderTarget for AlphaRenderTarget 
     }
 
     fn new(
         size: Option<DeviceUintSize>,
         _: DeviceIntSize,
     ) -> Self {
         AlphaRenderTarget {
             clip_batcher: ClipBatcher::new(),
-            brush_mask_rounded_rects: Vec::new(),
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
             scalings: Vec::new(),
             zero_clears: Vec::new(),
             allocator: TextureAllocator::new(size.expect("bug: alpha targets need size")),
         }
     }
 
@@ -527,16 +525,17 @@ impl RenderTarget for AlphaRenderTarget 
             ClearMode::One => {}
             ClearMode::Transparent => {
                 panic!("bug: invalid clear mode for alpha task");
             }
         }
 
         match task.kind {
             RenderTaskKind::Readback(..) |
+            RenderTaskKind::Picture(..) |
             RenderTaskKind::Blit(..) => {
                 panic!("BUG: should not be added to alpha target!");
             }
             RenderTaskKind::VerticalBlur(ref info) => {
                 info.add_instances(
                     &mut self.vertical_blurs,
                     task_id,
                     task.children[0],
@@ -548,94 +547,34 @@ impl RenderTarget for AlphaRenderTarget 
                 info.add_instances(
                     &mut self.horizontal_blurs,
                     task_id,
                     task.children[0],
                     BlurDirection::Horizontal,
                     render_tasks,
                 );
             }
-            RenderTaskKind::Picture(ref task_info) => {
-                let prim_metadata = ctx.prim_store.get_metadata(task_info.prim_index);
-
-                match prim_metadata.prim_kind {
-                    PrimitiveKind::Picture => {
-                        let prim = &ctx.prim_store.cpu_pictures[prim_metadata.cpu_prim_index.0];
-
-                        let task_index = render_tasks.get_task_address(task_id);
-
-                        for run in &prim.runs {
-                            for i in 0 .. run.count {
-                                let sub_prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
-
-                                let sub_metadata = ctx.prim_store.get_metadata(sub_prim_index);
-                                let sub_prim_address =
-                                    gpu_cache.get_address(&sub_metadata.gpu_location);
-
-                                match sub_metadata.prim_kind {
-                                    PrimitiveKind::Brush => {
-                                        let instance = BrushInstance {
-                                            picture_address: task_index,
-                                            prim_address: sub_prim_address,
-                                            // TODO(gw): In the future, when brush
-                                            //           primitives on picture backed
-                                            //           tasks support clip masks and
-                                            //           transform primitives, these
-                                            //           will need to be filled out!
-                                            clip_chain_rect_index: ClipChainRectIndex(0),
-                                            scroll_id: GPUClipScrollNodeIndex(0),
-                                            clip_task_address: RenderTaskAddress(0),
-                                            z: 0,
-                                            segment_index: 0,
-                                            brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
-                                            edge_flags: EdgeAaSegmentMask::empty(),
-                                            user_data: [0; 3],
-                                        };
-                                        let brush = &ctx.prim_store.cpu_brushes[sub_metadata.cpu_prim_index.0];
-                                        let batch = match brush.kind {
-                                            BrushKind::Solid { .. } |
-                                            BrushKind::Clear |
-                                            BrushKind::Picture |
-                                            BrushKind::Line { .. } |
-                                            BrushKind::YuvImage { .. } |
-                                            BrushKind::RadialGradient { .. } |
-                                            BrushKind::LinearGradient { .. } |
-                                            BrushKind::Image { .. } => {
-                                                unreachable!("bug: unexpected brush here");
-                                            }
-                                            BrushKind::Mask { .. } => {
-                                                &mut self.brush_mask_rounded_rects
-                                            }
-                                        };
-                                        batch.push(PrimitiveInstance::from(instance));
-                                    }
-                                    _ => {
-                                        unreachable!("Unexpected sub primitive type");
-                                    }
-                                }
-                            }
-                        }
-                    }
-                    _ => {
-                        // No other primitives make use of primitive caching yet!
-                        unreachable!()
-                    }
-                }
-            }
             RenderTaskKind::CacheMask(ref task_info) => {
                 let task_address = render_tasks.get_task_address(task_id);
                 self.clip_batcher.add(
                     task_address,
                     &task_info.clips,
                     task_info.coordinate_system_id,
                     &ctx.resource_cache,
                     gpu_cache,
                     clip_store,
                 );
             }
+            RenderTaskKind::ClipRegion(ref task) => {
+                let task_address = render_tasks.get_task_address(task_id);
+                self.clip_batcher.add_clip_region(
+                    task_address,
+                    task.clip_data_address,
+                );
+            }
             RenderTaskKind::Scaling(..) => {
                 self.scalings.push(ScalingInfo {
                     src_task_id: task.children[0],
                     dest_task_id: task_id,
                 });
             }
         }
     }
@@ -699,16 +638,17 @@ impl TextureCacheRenderTarget {
                             source: BlitJobSource::RenderTask(task_id),
                             target_rect,
                         });
                     }
                 }
             }
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Picture(..) |
+            RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) => {
                 panic!("BUG: unexpected task kind for texture cache target");
             }
         }
     }
 }
@@ -796,20 +736,19 @@ impl RenderPass {
                         clip_store,
                         deferred_resolves,
                     );
                 }
                 target.build(ctx, gpu_cache, render_tasks, deferred_resolves);
             }
             RenderPassKind::OffScreen { ref mut color, ref mut alpha, ref mut texture_cache } => {
                 let is_shared_alpha = self.tasks.iter().any(|&task_id| {
-                    match render_tasks[task_id].kind {
-                        RenderTaskKind::CacheMask(..) => true,
-                        _ => false,
-                    }
+                    let task = &render_tasks[task_id];
+                    task.is_shared() &&
+                        task.target_kind() == RenderTargetKind::Alpha
                 });
                 let saved_color = if self.tasks.iter().any(|&task_id| {
                     let t = &render_tasks[task_id];
                     t.target_kind() == RenderTargetKind::Color && t.saved_index.is_some()
                 }) {
                     Some(render_tasks.save_target())
                 } else {
                     None
--- a/gfx/webrender/tests/angle_shader_validation.rs
+++ b/gfx/webrender/tests/angle_shader_validation.rs
@@ -28,16 +28,20 @@ const SHADERS: &[Shader] = &[
         name: "cs_clip_rectangle",
         features: CLIP_FEATURES,
     },
     Shader {
         name: "cs_clip_image",
         features: CLIP_FEATURES,
     },
     Shader {
+        name: "cs_clip_box_shadow",
+        features: CLIP_FEATURES,
+    },
+    Shader {
         name: "cs_clip_border",
         features: CLIP_FEATURES,
     },
     // Cache shaders
     Shader {
         name: "cs_blur",
         features: CACHE_FEATURES,
     },
@@ -79,20 +83,16 @@ const SHADERS: &[Shader] = &[
         name: "brush_mask",
         features: &[],
     },
     Shader {
         name: "brush_solid",
         features: &[],
     },
     Shader {
-        name: "brush_picture",
-        features: &[],
-    },
-    Shader {
         name: "brush_blend",
         features: &[],
     },
     Shader {
         name: "brush_composite",
         features: &[],
     },
     Shader {
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-0da6c839b3a0e165f1115fb9fe286be7540c24ed
+5cb71f0f23719795e7c89417d91a7abad8ac20e9