Merge mozilla-central to autoland. CLOSED TREE
authorCsoregi Natalia <ncsoregi@mozilla.com>
Mon, 13 May 2019 00:45:32 +0300
changeset 532373 e0910edd4fc73cc0ed2932e7c7e2744ae7972e1f
parent 532372 e4f5f5cdc59d5f2f9a890f7cacf96f275c704ebd (current diff)
parent 532369 b83d8a064f1694627e66f2dd3a683b66c350b3b3 (diff)
child 532374 703783d5484d173508e8a13d41fde8d121149211
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone68.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
Merge mozilla-central to autoland. CLOSED TREE
--- a/gfx/ipc/GfxMessageUtils.h
+++ b/gfx/ipc/GfxMessageUtils.h
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #ifndef __GFXMESSAGEUTILS_H__
 #define __GFXMESSAGEUTILS_H__
 
+#include "mozilla/webrender/webrender_ffi.h"
 #include "FilterSupport.h"
 #include "ImageTypes.h"
 #include "RegionBuilder.h"
 #include "base/process_util.h"
 #include "chrome/common/ipc_message_utils.h"
 #include "gfxFeature.h"
 #include "gfxFallback.h"
 #include "gfxPoint.h"
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -1141,26 +1141,28 @@ void DisplayListBuilder::PushLine(const 
                   &mCurrentSpaceAndClipChain, &aLine.bounds,
                   aLine.wavyLineThickness, aLine.orientation, &aLine.color,
                   aLine.style);
 }
 
 void DisplayListBuilder::PushShadow(const wr::LayoutRect& aRect,
                                     const wr::LayoutRect& aClip,
                                     bool aIsBackfaceVisible,
-                                    const wr::Shadow& aShadow) {
+                                    const wr::Shadow& aShadow,
+                                    bool aShouldInflate) {
   // Local clip_rects are translated inside of shadows, as they are assumed to
   // be part of the element drawing itself, and not a parent frame clipping it.
   // As such, it is not sound to apply the MergeClipLeaf optimization inside of
   // shadows. So we disable the optimization when we encounter a shadow.
   // Shadows don't span frames, so we don't have to worry about MergeClipLeaf
   // being re-enabled mid-shadow. The optimization is restored in PopAllShadows.
   SuspendClipLeafMerging();
   wr_dp_push_shadow(mWrState, aRect, aClip, aIsBackfaceVisible,
-                    &mCurrentSpaceAndClipChain, aShadow);
+                    &mCurrentSpaceAndClipChain, aShadow,
+                    aShouldInflate);
 }
 
 void DisplayListBuilder::PopAllShadows() {
   wr_dp_pop_all_shadows(mWrState);
   ResumeClipLeafMerging();
 }
 
 void DisplayListBuilder::SuspendClipLeafMerging() {
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -523,17 +523,18 @@ class DisplayListBuilder final {
                 wr::FontInstanceKey aFontKey,
                 Range<const wr::GlyphInstance> aGlyphBuffer,
                 const wr::GlyphOptions* aGlyphOptions = nullptr);
 
   void PushLine(const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
                 const wr::Line& aLine);
 
   void PushShadow(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
-                  bool aIsBackfaceVisible, const wr::Shadow& aShadow);
+                  bool aIsBackfaceVisible, const wr::Shadow& aShadow,
+                  bool aShouldInflate);
 
   void PopAllShadows();
 
   void PushBoxShadow(const wr::LayoutRect& aRect, const wr::LayoutRect& aClip,
                      bool aIsBackfaceVisible, const wr::LayoutRect& aBoxBounds,
                      const wr::LayoutVector2D& aOffset,
                      const wr::ColorF& aColor, const float& aBlurRadius,
                      const float& aSpreadRadius,
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -2657,22 +2657,24 @@ pub extern "C" fn wr_dp_push_text(state:
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_shadow(state: &mut WrState,
                                     _bounds: LayoutRect,
                                     _clip: LayoutRect,
                                     _is_backface_visible: bool,
                                     parent: &WrSpaceAndClipChain,
-                                    shadow: Shadow) {
+                                    shadow: Shadow,
+                                    should_inflate: bool) {
     debug_assert!(unsafe { is_in_main_thread() });
 
     state.frame_builder.dl_builder.push_shadow(
         &parent.to_webrender(state.pipeline_id),
         shadow.into(),
+        should_inflate,
     );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_pop_all_shadows(state: &mut WrState) {
     debug_assert!(unsafe { is_in_main_thread() });
 
     state.frame_builder.dl_builder.pop_all_shadows();
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -1,36 +1,35 @@
 /* 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, ClipMode, ExternalImageType, FilterOp, ImageRendering};
+use api::{AlphaType, ClipMode, ExternalImageType, ImageRendering};
 use api::{YuvColorSpace, YuvFormat, ColorDepth, PremultipliedColorF, RasterSpace};
 use api::units::*;
 use crate::clip::{ClipDataStore, ClipNodeFlags, ClipNodeRange, ClipItem, ClipStore, ClipNodeInstance};
 use crate::clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex, CoordinateSystemId};
 use crate::glyph_rasterizer::GlyphFormat;
 use crate::gpu_cache::{GpuBlockData, GpuCache, GpuCacheHandle, GpuCacheAddress};
 use crate::gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders, ZBufferId, ZBufferIdGenerator};
 use crate::gpu_types::{ClipMaskInstance, SplitCompositeInstance, SnapOffsets};
 use crate::gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
 use crate::gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
-use crate::internal_types::{FastHashMap, SavedTargetIndex, TextureSource};
+use crate::internal_types::{FastHashMap, SavedTargetIndex, TextureSource, Filter};
 use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive};
 use crate::prim_store::{DeferredResolve, EdgeAaSegmentMask, PrimitiveInstanceKind, PrimitiveVisibilityIndex};
 use crate::prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
 use crate::prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, VECS_PER_SEGMENT};
 use crate::prim_store::{recompute_snap_offsets};
 use crate::prim_store::image::ImageSource;
 use crate::render_backend::DataStores;
 use crate::render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree, TileBlit};
 use crate::renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use crate::renderer::{BLOCKS_PER_UV_RECT, MAX_VERTEX_TEXTURE_WIDTH};
 use crate::resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
-use crate::scene::FilterOpHelpers;
 use smallvec::SmallVec;
 use std::{f32, i32, usize};
 use crate::tiling::{RenderTargetContext};
 use crate::util::{project_rect, TransformedRectKind};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff);
@@ -631,16 +630,17 @@ impl BatchBuilder {
             );
 
         // TODO(gw): Calculating this for every primitive is a bit
         //           wasteful. We should probably cache this in
         //           the scroll node...
         let transform_kind = transform_id.transform_kind();
         let prim_info = &ctx.scratch.prim_info[prim_instance.visibility_info.0 as usize];
         let bounding_rect = &prim_info.clip_chain.pic_clip_rect;
+
         let z_id = z_generator.next();
 
         let prim_common_data = &ctx.data_stores.as_common_data(&prim_instance);
         let prim_rect = LayoutRect::new(
             prim_instance.prim_origin,
             prim_common_data.prim_size,
         );
 
@@ -1282,17 +1282,17 @@ impl BatchBuilder {
                                             Vec::new(),
                                         );
                                     }
                                 }
                             }
                             PictureCompositeMode::Filter(ref filter) => {
                                 assert!(filter.is_visible());
                                 match filter {
-                                    FilterOp::Blur(..) => {
+                                    Filter::Blur(..) => {
                                         let kind = BatchKind::Brush(
                                             BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
                                         );
                                         let (uv_rect_address, textures) = render_tasks.resolve_surface(
                                             surface.expect("bug: surface must be allocated by now"),
                                             gpu_cache,
                                         );
                                         let key = BatchKey::new(
@@ -1318,17 +1318,115 @@ impl BatchBuilder {
 
                                         batcher.current_batch_list().push_single_instance(
                                             key,
                                             bounding_rect,
                                             z_id,
                                             PrimitiveInstanceData::from(instance),
                                         );
                                     }
-                                    FilterOp::DropShadow(offset, ..) => {
+                                    Filter::DropShadowStack(shadows) => {
+                                        // Draw an instance per shadow first, following by the content.
+
+                                        // The shadows and the content get drawn as a brush image.
+                                        let kind = BatchKind::Brush(
+                                            BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
+                                        );
+
+                                        // Gets the saved render task ID of the content, which is
+                                        // deeper in the render task tree than the direct child.
+                                        let secondary_id = picture.secondary_render_task_id.expect("no secondary!?");
+                                        let saved_index = render_tasks[secondary_id].saved_index.expect("no saved index!?");
+                                        debug_assert_ne!(saved_index, SavedTargetIndex::PENDING);
+
+                                        // Build BatchTextures for shadow/content
+                                        let shadow_textures = BatchTextures::render_target_cache();
+                                        let content_textures = BatchTextures {
+                                            colors: [
+                                                TextureSource::RenderTaskCache(saved_index),
+                                                TextureSource::Invalid,
+                                                TextureSource::Invalid,
+                                            ],
+                                        };
+
+                                        // Build batch keys for shadow/content
+                                        let shadow_key = BatchKey::new(kind, non_segmented_blend_mode, shadow_textures);
+                                        let content_key = BatchKey::new(kind, non_segmented_blend_mode, content_textures);
+
+                                        // Retrieve the UV rect addresses for shadow/content.
+                                        let cache_task_id = surface
+                                            .expect("bug: surface must be allocated by now");
+                                        let shadow_uv_rect_address = render_tasks[cache_task_id]
+                                            .get_texture_address(gpu_cache)
+                                            .as_int();
+                                        let content_uv_rect_address = render_tasks[secondary_id]
+                                            .get_texture_address(gpu_cache)
+                                            .as_int();
+
+                                        for (shadow, shadow_gpu_data) in shadows.iter().zip(picture.extra_gpu_data_handles.iter()) {
+                                            // Get the GPU cache address of the extra data handle.
+                                            let shadow_prim_address = gpu_cache.get_address(shadow_gpu_data);
+
+                                            let shadow_rect = prim_header.local_rect.translate(&shadow.offset);
+
+                                            let shadow_prim_header = PrimitiveHeader {
+                                                local_rect: shadow_rect,
+                                                specific_prim_address: shadow_prim_address,
+                                                ..prim_header
+                                            };
+
+                                            let shadow_prim_header_index = prim_headers.push(&shadow_prim_header, z_id, [
+                                                ShaderColorMode::Alpha as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
+                                                RasterizationSpace::Screen as i32,
+                                                get_shader_opacity(1.0),
+                                                0,
+                                            ]);
+
+                                            let shadow_instance = BrushInstance {
+                                                prim_header_index: shadow_prim_header_index,
+                                                clip_task_address,
+                                                segment_index: INVALID_SEGMENT_INDEX,
+                                                edge_flags: EdgeAaSegmentMask::empty(),
+                                                brush_flags,
+                                                user_data: shadow_uv_rect_address,
+                                            };
+
+                                            batcher.current_batch_list().push_single_instance(
+                                                shadow_key,
+                                                bounding_rect,
+                                                z_id,
+                                                PrimitiveInstanceData::from(shadow_instance),
+                                            );
+                                        }
+                                        let z_id_content = z_generator.next();
+
+                                        let content_prim_header_index = prim_headers.push(&prim_header, z_id_content, [
+                                            ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
+                                            RasterizationSpace::Screen as i32,
+                                            get_shader_opacity(1.0),
+                                            0,
+                                        ]);
+
+                                        let content_instance = BrushInstance {
+                                            prim_header_index: content_prim_header_index,
+                                            clip_task_address,
+                                            segment_index: INVALID_SEGMENT_INDEX,
+                                            edge_flags: EdgeAaSegmentMask::empty(),
+                                            brush_flags,
+                                            user_data: content_uv_rect_address,
+                                        };
+
+                                        batcher.current_batch_list().push_single_instance(
+                                            content_key,
+                                            bounding_rect,
+                                            z_id_content,
+                                            PrimitiveInstanceData::from(content_instance),
+                                        );
+                                    }
+                                    Filter::DropShadow(shadow) => {
                                         // Draw an instance of the shadow first, following by the content.
 
                                         // Both the shadow and the content get drawn as a brush image.
                                         let kind = BatchKind::Brush(
                                             BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
                                         );
 
                                         // Gets the saved render task ID of the content, which is
@@ -1356,29 +1454,29 @@ impl BatchBuilder {
                                         let shadow_uv_rect_address = render_tasks[cache_task_id]
                                             .get_texture_address(gpu_cache)
                                             .as_int();
                                         let content_uv_rect_address = render_tasks[secondary_id]
                                             .get_texture_address(gpu_cache)
                                             .as_int();
 
                                         // Get the GPU cache address of the extra data handle.
-                                        let shadow_prim_address = gpu_cache.get_address(&picture.extra_gpu_data_handle);
+                                        let shadow_prim_address = gpu_cache.get_address(&picture.extra_gpu_data_handles[0]);
 
                                         let z_id_shadow = z_id;
                                         let z_id_content = z_generator.next();
 
                                         let content_prim_header_index = prim_headers.push(&prim_header, z_id_content, [
                                             ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
                                             RasterizationSpace::Screen as i32,
                                             get_shader_opacity(1.0),
                                             0,
                                         ]);
 
-                                        let shadow_rect = prim_header.local_rect.translate(&offset);
+                                        let shadow_rect = prim_header.local_rect.translate(&shadow.offset);
 
                                         let shadow_prim_header = PrimitiveHeader {
                                             local_rect: shadow_rect,
                                             snap_offsets: prim_info.shadow_snap_offsets,
                                             specific_prim_address: shadow_prim_address,
                                             ..prim_header
                                         };
 
@@ -1418,57 +1516,59 @@ impl BatchBuilder {
                                             content_key,
                                             bounding_rect,
                                             z_id_content,
                                             PrimitiveInstanceData::from(content_instance),
                                         );
                                     }
                                     _ => {
                                         let filter_mode = match filter {
-                                            FilterOp::Identity => 1, // matches `Contrast(1)`
-                                            FilterOp::Blur(..) => 0,
-                                            FilterOp::Contrast(..) => 1,
-                                            FilterOp::Grayscale(..) => 2,
-                                            FilterOp::HueRotate(..) => 3,
-                                            FilterOp::Invert(..) => 4,
-                                            FilterOp::Saturate(..) => 5,
-                                            FilterOp::Sepia(..) => 6,
-                                            FilterOp::Brightness(..) => 7,
-                                            FilterOp::Opacity(..) => 8,
-                                            FilterOp::DropShadow(..) => 9,
-                                            FilterOp::ColorMatrix(..) => 10,
-                                            FilterOp::SrgbToLinear => 11,
-                                            FilterOp::LinearToSrgb => 12,
-                                            FilterOp::ComponentTransfer => unreachable!(),
+                                            Filter::Identity => 1, // matches `Contrast(1)`
+                                            Filter::Blur(..) => 0,
+                                            Filter::Contrast(..) => 1,
+                                            Filter::Grayscale(..) => 2,
+                                            Filter::HueRotate(..) => 3,
+                                            Filter::Invert(..) => 4,
+                                            Filter::Saturate(..) => 5,
+                                            Filter::Sepia(..) => 6,
+                                            Filter::Brightness(..) => 7,
+                                            Filter::Opacity(..) => 8,
+                                            Filter::DropShadow(..) |
+                                            Filter::DropShadowStack(..) => 9,
+                                            Filter::ColorMatrix(..) => 10,
+                                            Filter::SrgbToLinear => 11,
+                                            Filter::LinearToSrgb => 12,
+                                            Filter::ComponentTransfer => unreachable!(),
                                         };
 
                                         let user_data = match filter {
-                                            FilterOp::Identity => 0x10000i32, // matches `Contrast(1)`
-                                            FilterOp::Contrast(amount) |
-                                            FilterOp::Grayscale(amount) |
-                                            FilterOp::Invert(amount) |
-                                            FilterOp::Saturate(amount) |
-                                            FilterOp::Sepia(amount) |
-                                            FilterOp::Brightness(amount) |
-                                            FilterOp::Opacity(_, amount) => {
+                                            Filter::Identity => 0x10000i32, // matches `Contrast(1)`
+                                            Filter::Contrast(amount) |
+                                            Filter::Grayscale(amount) |
+                                            Filter::Invert(amount) |
+                                            Filter::Saturate(amount) |
+                                            Filter::Sepia(amount) |
+                                            Filter::Brightness(amount) |
+                                            Filter::Opacity(_, amount) => {
                                                 (amount * 65536.0) as i32
                                             }
-                                            FilterOp::SrgbToLinear | FilterOp::LinearToSrgb => 0,
-                                            FilterOp::HueRotate(angle) => {
+                                            Filter::SrgbToLinear | Filter::LinearToSrgb => 0,
+                                            Filter::HueRotate(angle) => {
                                                 (0.01745329251 * angle * 65536.0) as i32
                                             }
                                             // Go through different paths
-                                            FilterOp::Blur(..) |
-                                            FilterOp::DropShadow(..) => {
+                                            Filter::Blur(..) |
+                                            Filter::DropShadowStack(..) |
+                                            Filter::DropShadow(..) => {
                                                 unreachable!();
                                             }
-                                            FilterOp::ColorMatrix(_) => {
-                                                picture.extra_gpu_data_handle.as_int(gpu_cache)
+                                            Filter::ColorMatrix(_) => {
+                                                picture.extra_gpu_data_handles[0].as_int(gpu_cache)
                                             }
-                                            FilterOp::ComponentTransfer => unreachable!(),
+                                            Filter::ComponentTransfer => unreachable!(),
                                         };
 
                                         let (uv_rect_address, textures) = render_tasks.resolve_surface(
                                             surface.expect("bug: surface must be allocated by now"),
                                             gpu_cache,
                                         );
 
                                         let key = BatchKey::new(
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -14,17 +14,17 @@ use api::{ClipMode, PrimitiveKeyKind, Tr
 use api::units::*;
 use crate::clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore};
 use crate::clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex};
 use crate::frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use crate::glyph_rasterizer::FontInstance;
 use crate::hit_test::{HitTestingItem, HitTestingScene};
 use crate::image::simplify_repeated_primitive;
 use crate::intern::Interner;
-use crate::internal_types::{FastHashMap, FastHashSet, LayoutPrimitiveInfo};
+use crate::internal_types::{FastHashMap, FastHashSet, LayoutPrimitiveInfo, Filter};
 use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureOptions};
 use crate::picture::{BlitReason, PrimitiveList, TileCache};
 use crate::prim_store::{PrimitiveInstance, PrimitiveSceneData};
 use crate::prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveStore};
 use crate::prim_store::{ScrollNodeAndClipChain, PictureIndex};
 use crate::prim_store::{InternablePrimitive, SegmentInstanceIndex};
 use crate::prim_store::{register_prim_chase_id, get_line_decoration_sizes};
 use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
@@ -1275,17 +1275,17 @@ impl<'a> DisplayListFlattener<'a> {
             }
             DisplayItem::PushShadow(info) => {
                 let clip_and_scroll = self.get_clip_and_scroll(
                     &info.space_and_clip.clip_id,
                     &info.space_and_clip.spatial_id,
                     apply_pipeline_clip
                 );
 
-                self.push_shadow(info.shadow, clip_and_scroll);
+                self.push_shadow(info.shadow, clip_and_scroll, info.should_inflate);
             }
             DisplayItem::PopAllShadows => {
                 self.pop_all_shadows();
             }
         }
 
         None
     }
@@ -1751,21 +1751,21 @@ impl<'a> DisplayListFlattener<'a> {
                 ClipChainId::NONE,
                 stacking_context.spatial_node_index,
                 &mut self.interners,
             );
         }
 
         // For each filter, create a new image with that composite mode.
         let mut current_filter_data_index = 0;
-        for filter in &stacking_context.composite_ops.filters {
-            let filter = filter.sanitize();
+        for filter in &mut stacking_context.composite_ops.filters {
+            filter.sanitize();
 
-            let composite_mode = Some(match filter {
-                FilterOp::ComponentTransfer => {
+            let composite_mode = Some(match *filter {
+                Filter::ComponentTransfer => {
                     let filter_data =
                         &stacking_context.composite_ops.filter_datas[current_filter_data_index];
                     let filter_data = filter_data.sanitize();
                     current_filter_data_index = current_filter_data_index + 1;
                     if filter_data.is_identity() {
                         continue
                     } else {
                         let filter_data_key = SFilterDataKey {
@@ -2093,22 +2093,24 @@ impl<'a> DisplayListFlattener<'a> {
         self.id_to_index_mapper.map_spatial_node(new_node_id, node_index);
         node_index
     }
 
     pub fn push_shadow(
         &mut self,
         shadow: Shadow,
         clip_and_scroll: ScrollNodeAndClipChain,
+        should_inflate: bool,
     ) {
         // Store this shadow in the pending list, for processing
         // during pop_all_shadows.
         self.pending_shadow_items.push_back(ShadowItem::Shadow(PendingShadow {
             shadow,
             clip_and_scroll,
+            should_inflate,
         }));
     }
 
     pub fn pop_all_shadows(
         &mut self,
     ) {
         assert!(!self.pending_shadow_items.is_empty(), "popped shadows, but none were present");
 
@@ -2197,25 +2199,26 @@ impl<'a> DisplayListFlattener<'a> {
 
                     // No point in adding a shadow here if there were no primitives
                     // added to the shadow.
                     if !prims.is_empty() {
                         // Create a picture that the shadow primitives will be added to. If the
                         // blur radius is 0, the code in Picture::prepare_for_render will
                         // detect this and mark the picture to be drawn directly into the
                         // parent picture, which avoids an intermediate surface and blur.
-                        let blur_filter = FilterOp::Blur(std_deviation).sanitize();
+                        let mut blur_filter = Filter::Blur(std_deviation);
+                        blur_filter.sanitize();
                         let composite_mode = PictureCompositeMode::Filter(blur_filter);
                         let composite_mode_key = Some(composite_mode.clone()).into();
                         let is_backface_visible = true; //TODO: double check this
 
                         // Pass through configuration information about whether WR should
                         // do the bounding rect inflation for text shadows.
                         let options = PictureOptions {
-                            inflate_if_required: pending_shadow.shadow.should_inflate,
+                            inflate_if_required: pending_shadow.should_inflate,
                         };
 
                         // Create the primitive to draw the shadow picture into the scene.
                         let shadow_pic_index = PictureIndex(self.prim_store.pictures
                             .alloc()
                             .init(PicturePrimitive::new_image(
                                 Some(composite_mode),
                                 Picture3DContext::Out,
@@ -2307,26 +2310,26 @@ impl<'a> DisplayListFlattener<'a> {
     )
     where
         P: InternablePrimitive + CreateShadow,
         Interners: AsMut<Interner<P>>,
     {
         // Offset the local rect and clip rect by the shadow offset.
         let mut info = pending_primitive.info.clone();
         info.rect = info.rect.translate(&pending_shadow.shadow.offset);
-        info.clip_rect = info.clip_rect.translate(&pending_shadow.shadow.offset);
+        info.clip_rect = info.clip_rect.translate(
+            &pending_shadow.shadow.offset
+        );
 
         // Construct and add a primitive for the given shadow.
         let shadow_prim_instance = self.create_primitive(
             &info,
             pending_primitive.clip_and_scroll.clip_chain_id,
             pending_primitive.clip_and_scroll.spatial_node_index,
-            pending_primitive.prim.create_shadow(
-                &pending_shadow.shadow,
-            ),
+            pending_primitive.prim.create_shadow(&pending_shadow.shadow),
         );
 
         // Add the new primitive to the shadow picture.
         prims.push(shadow_prim_instance);
     }
 
     fn add_shadow_prim_to_draw_list<P>(
         &mut self,
@@ -3026,16 +3029,17 @@ pub struct PendingPrimitive<T> {
     info: LayoutPrimitiveInfo,
     prim: T,
 }
 
 /// As shadows are pushed, they are stored as pending
 /// shadows, and handled at once during pop_all_shadows.
 pub struct PendingShadow {
     shadow: Shadow,
+    should_inflate: bool,
     clip_and_scroll: ScrollNodeAndClipChain,
 }
 
 pub enum ShadowItem {
     Shadow(PendingShadow),
     Image(PendingPrimitive<Image>),
     LineDecoration(PendingPrimitive<LineDecoration>),
     NormalBorder(PendingPrimitive<NormalBorderPrim>),
--- a/gfx/wr/webrender/src/internal_types.rs
+++ b/gfx/wr/webrender/src/internal_types.rs
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{DebugCommand, DocumentId, ExternalImageData, ExternalImageId};
-use api::{ImageFormat, ItemTag, NotificationRequest};
+use api::{ImageFormat, ItemTag, NotificationRequest, Shadow, FilterOp, MAX_BLUR_RADIUS};
 use api::units::*;
+use api;
 use crate::device::TextureFilter;
 use crate::renderer::PipelineInfo;
 use crate::gpu_cache::GpuCacheUpdateList;
 use fxhash::FxHasher;
 use plane_split::BspSplitter;
 use crate::profiler::BackendProfileCounters;
 use std::{usize, i32};
 use std::collections::{HashMap, HashSet};
@@ -25,16 +26,146 @@ use crate::capture::PlainExternalImage;
 use crate::tiling;
 
 pub type FastHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FxHasher>>;
 pub type FastHashSet<K> = HashSet<K, BuildHasherDefault<FxHasher>>;
 
 /// A concrete plane splitter type used in WebRender.
 pub type PlaneSplitter = BspSplitter<f64, WorldPixel>;
 
+/// An arbitrary number which we assume opacity is invisible below.
+const OPACITY_EPSILON: f32 = 0.001;
+
+/// Equivalent to api::FilterOp with added internal information
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum Filter {
+    Identity,
+    Blur(f32),
+    Brightness(f32),
+    Contrast(f32),
+    Grayscale(f32),
+    HueRotate(f32),
+    Invert(f32),
+    Opacity(api::PropertyBinding<f32>, f32),
+    Saturate(f32),
+    Sepia(f32),
+    DropShadow(Shadow),
+    #[allow(dead_code)]
+    DropShadowStack(Vec<Shadow>),
+    ColorMatrix([f32; 20]),
+    SrgbToLinear,
+    LinearToSrgb,
+    ComponentTransfer,
+}
+
+impl Filter {
+    /// Ensure that the parameters for a filter operation
+    /// are sensible.
+    pub fn sanitize(&mut self) {
+        match self {
+            Filter::Blur(ref mut radius) => {
+                *radius = radius.min(MAX_BLUR_RADIUS);
+            }
+            Filter::DropShadow(ref mut shadow) => {
+                shadow.blur_radius = shadow.blur_radius.min(MAX_BLUR_RADIUS);
+            }
+            Filter::DropShadowStack(ref mut stack) => {
+                for shadow in stack {
+                    shadow.blur_radius = shadow.blur_radius.min(MAX_BLUR_RADIUS);
+                }
+            }
+            _ => {},
+        }
+    }
+
+    pub fn is_visible(&self) -> bool {
+        match *self {
+            Filter::Identity |
+            Filter::Blur(..) |
+            Filter::Brightness(..) |
+            Filter::Contrast(..) |
+            Filter::Grayscale(..) |
+            Filter::HueRotate(..) |
+            Filter::Invert(..) |
+            Filter::Saturate(..) |
+            Filter::Sepia(..) |
+            Filter::DropShadow(..) |
+            Filter::DropShadowStack(..) |
+            Filter::ColorMatrix(..) |
+            Filter::SrgbToLinear |
+            Filter::LinearToSrgb |
+            Filter::ComponentTransfer  => true,
+            Filter::Opacity(_, amount) => {
+                amount > OPACITY_EPSILON
+            }
+        }
+    }
+
+    pub fn is_noop(&self) -> bool {
+        match *self {
+            Filter::Identity => false, // this is intentional
+            Filter::Blur(length) => length == 0.0,
+            Filter::Brightness(amount) => amount == 1.0,
+            Filter::Contrast(amount) => amount == 1.0,
+            Filter::Grayscale(amount) => amount == 0.0,
+            Filter::HueRotate(amount) => amount == 0.0,
+            Filter::Invert(amount) => amount == 0.0,
+            Filter::Opacity(_, amount) => amount >= 1.0,
+            Filter::Saturate(amount) => amount == 1.0,
+            Filter::Sepia(amount) => amount == 0.0,
+            Filter::DropShadowStack(ref shadows) => {
+                for shadow in shadows {
+                    if shadow.offset.x != 0.0 || shadow.offset.y != 0.0 || shadow.blur_radius != 0.0 {
+                        return false;
+                    }
+                }
+
+                true
+            }
+            Filter::DropShadow(shadow) => {
+                shadow.offset.x == 0.0 && shadow.offset.y == 0.0 && shadow.blur_radius == 0.0
+            },
+            Filter::ColorMatrix(matrix) => {
+                matrix == [1.0, 0.0, 0.0, 0.0,
+                           0.0, 1.0, 0.0, 0.0,
+                           0.0, 0.0, 1.0, 0.0,
+                           0.0, 0.0, 0.0, 1.0,
+                           0.0, 0.0, 0.0, 0.0]
+            }
+            Filter::SrgbToLinear |
+            Filter::LinearToSrgb |
+            Filter::ComponentTransfer => false,
+        }
+    }
+}
+
+impl From<FilterOp> for Filter {
+    fn from(op: FilterOp) -> Self {
+        match op {
+            FilterOp::Identity => Filter::Identity,
+            FilterOp::Blur(r) => Filter::Blur(r),
+            FilterOp::Brightness(b) => Filter::Brightness(b),
+            FilterOp::Contrast(c) => Filter::Contrast(c),
+            FilterOp::Grayscale(g) => Filter::Grayscale(g),
+            FilterOp::HueRotate(h) => Filter::HueRotate(h),
+            FilterOp::Invert(i) => Filter::Invert(i),
+            FilterOp::Opacity(binding, opacity) => Filter::Opacity(binding, opacity),
+            FilterOp::Saturate(s) => Filter::Saturate(s),
+            FilterOp::Sepia(s) => Filter::Sepia(s),
+            FilterOp::DropShadow(shadow) => Filter::DropShadow(shadow),
+            FilterOp::ColorMatrix(mat) => Filter::ColorMatrix(mat),
+            FilterOp::SrgbToLinear => Filter::SrgbToLinear,
+            FilterOp::LinearToSrgb => Filter::LinearToSrgb,
+            FilterOp::ComponentTransfer => Filter::ComponentTransfer,
+        }
+    }
+}
+
 /// An ID for a texture that is owned by the `texture_cache` module.
 ///
 /// This can include atlases or standalone textures allocated via the texture
 /// cache (e.g.  if an image is too large to be added to an atlas). The texture
 /// cache manages the allocation and freeing of these IDs, and the rendering
 /// thread maintains a map from cache texture ID to native texture.
 ///
 /// We never reuse IDs, so we use a u64 here to be safe.
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -1,41 +1,41 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{FilterOp, MixBlendMode, PipelineId, PremultipliedColorF};
+use api::{MixBlendMode, PipelineId, PremultipliedColorF};
 use api::{PropertyBinding, PropertyBindingId};
 use api::{DebugFlags, RasterSpace, ColorF, ImageKey, ClipMode};
 use api::units::*;
 use crate::box_shadow::{BLUR_SAMPLE_SCALE};
 use crate::clip::{ClipChainId, ClipChainNode, ClipItem, ClipStore, ClipDataStore, ClipChainStack};
 use crate::clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX,
     ClipScrollTree, CoordinateSystemId, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace
 };
 use crate::debug_colors;
 use euclid::{size2, vec3, TypedPoint2D, TypedScale, TypedSize2D, Vector2D};
 use euclid::approxeq::ApproxEq;
 use crate::frame_builder::{FrameVisibilityContext, FrameVisibilityState};
 use crate::intern::ItemUid;
-use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter};
+use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, Filter};
 use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
 use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use crate::gpu_types::UvRectKind;
 use plane_split::{Clipper, Polygon, Splitter};
 use crate::prim_store::SpaceMapper;
 use crate::prim_store::{PictureIndex, PrimitiveInstance, PrimitiveInstanceKind};
 use crate::prim_store::{get_raster_rects, PrimitiveScratchBuffer, VectorKey, PointKey};
 use crate::prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex, RectangleKey};
 use crate::print_tree::PrintTreePrinter;
 use crate::render_backend::DataStores;
 use crate::render_task::{ClearMode, RenderTask, TileBlit};
-use crate::render_task::{RenderTaskId, RenderTaskLocation};
+use crate::render_task::{RenderTaskId, RenderTaskLocation, BlurTaskCache};
 use crate::resource_cache::ResourceCache;
-use crate::scene::{FilterOpHelpers, SceneProperties};
+use crate::scene::SceneProperties;
 use crate::scene_builder::Interners;
 use smallvec::SmallVec;
 use std::{mem, u16};
 use std::sync::atomic::{AtomicUsize, Ordering};
 use crate::texture_cache::TextureCacheHandle;
 use crate::tiling::RenderTargetKind;
 use crate::util::{ComparableVec, TransformedRectKind, MatrixHelpers, MaxRect, scale_factors};
 use crate::filterdata::{FilterDataHandle};
@@ -1128,17 +1128,17 @@ impl TileCache {
         // tile (in cases where the picture local rect affects the tile, but the clip
         // rect eventually means it doesn't affect that tile).
         // TODO(gw): Get picture clips earlier (during the initial picture traversal
         //           pass) so that we can calculate these correctly.
         let include_clip_rect = match prim_instance.kind {
             PrimitiveInstanceKind::Picture { pic_index,.. } => {
                 // Pictures can depend on animated opacity bindings.
                 let pic = &pictures[pic_index.0];
-                if let Some(PictureCompositeMode::Filter(FilterOp::Opacity(binding, _))) = pic.requested_composite_mode {
+                if let Some(PictureCompositeMode::Filter(Filter::Opacity(binding, _))) = pic.requested_composite_mode {
                     opacity_bindings.push(binding.into());
                 }
 
                 false
             }
             PrimitiveInstanceKind::Rectangle { opacity_binding_index, .. } => {
                 if opacity_binding_index != OpacityBindingIndex::INVALID {
                     let opacity_binding = &opacity_binding_store[opacity_binding_index];
@@ -1889,17 +1889,17 @@ bitflags! {
 /// onto the target it belongs to.
 #[allow(dead_code)]
 #[derive(Debug, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub enum PictureCompositeMode {
     /// Apply CSS mix-blend-mode effect.
     MixBlend(MixBlendMode),
     /// Apply a CSS filter (except component transfer).
-    Filter(FilterOp),
+    Filter(Filter),
     /// Apply a component transfer filter.
     ComponentTransferFilter(FilterDataHandle),
     /// Draw to intermediate surface, copy straight across. This
     /// is used for CSS isolation, and plane splitting.
     Blit(BlitReason),
     /// Used to cache a picture as a series of tiles.
     TileCache {
         clear_color: ColorF,
@@ -2183,20 +2183,20 @@ pub struct PicturePrimitive {
 
     pub raster_config: Option<RasterConfig>,
     pub context_3d: Picture3DContext<OrderedPictureChild>,
 
     // If requested as a frame output (for rendering
     // pages to a texture), this is the pipeline this
     // picture is the root of.
     pub frame_output_pipeline_id: Option<PipelineId>,
-    // An optional cache handle for storing extra data
+    // Optional cache handles for storing extra data
     // in the GPU cache, depending on the type of
     // picture.
-    pub extra_gpu_data_handle: GpuCacheHandle,
+    pub extra_gpu_data_handles: SmallVec<[GpuCacheHandle; 1]>,
 
     /// The spatial node index of this picture when it is
     /// composited into the parent picture.
     pub spatial_node_index: SpatialNodeIndex,
 
     /// The local rect of this picture. It is built
     /// dynamically when updating visibility. It takes
     /// into account snapping in device space for its
@@ -2263,17 +2263,17 @@ impl PicturePrimitive {
             }
         }
     }
 
     fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool {
         match self.requested_composite_mode {
             Some(PictureCompositeMode::Filter(ref mut filter)) => {
                 match *filter {
-                    FilterOp::Opacity(ref binding, ref mut value) => {
+                    Filter::Opacity(ref binding, ref mut value) => {
                         *value = properties.resolve_float(binding);
                     }
                     _ => {}
                 }
 
                 filter.is_visible()
             }
             _ => true,
@@ -2331,17 +2331,17 @@ impl PicturePrimitive {
         PicturePrimitive {
             prim_list,
             state: None,
             secondary_render_task_id: None,
             requested_composite_mode,
             raster_config: None,
             context_3d,
             frame_output_pipeline_id,
-            extra_gpu_data_handle: GpuCacheHandle::new(),
+            extra_gpu_data_handles: SmallVec::new(),
             apply_local_clip_rect,
             is_backface_visible,
             pipeline_id,
             requested_raster_space,
             spatial_node_index,
             snapped_local_rect: LayoutRect::zero(),
             unsnapped_local_rect: LayoutRect::zero(),
             tile_cache,
@@ -2462,17 +2462,17 @@ impl PicturePrimitive {
                     }
                 };
                 let transform = map_pic_to_raster.get_transform();
 
                 let (root, port) = match raster_config.composite_mode {
                     PictureCompositeMode::TileCache { .. } => {
                         unreachable!();
                     }
-                    PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)) => {
+                    PictureCompositeMode::Filter(Filter::Blur(blur_radius)) => {
                         let blur_std_deviation = blur_radius * device_pixel_scale.0;
                         let scale_factors = scale_factors(&transform);
                         let blur_std_deviation = DeviceSize::new(
                             blur_std_deviation * scale_factors.0,
                             blur_std_deviation * scale_factors.1
                         );
                         let inflation_factor = frame_state.surfaces[raster_config.surface_index.0].inflation_factor;
                         let inflation_factor = (inflation_factor * device_pixel_scale.0).ceil() as i32;
@@ -2513,30 +2513,29 @@ impl PicturePrimitive {
                             Vec::new(),
                             uv_rect_kind,
                             raster_spatial_node_index,
                             device_pixel_scale,
                         );
 
                         let picture_task_id = frame_state.render_tasks.add(picture_task);
 
-                        let blur_render_task = RenderTask::new_blur(
+                        let blur_render_task_id = RenderTask::new_blur(
                             blur_std_deviation,
                             picture_task_id,
                             frame_state.render_tasks,
                             RenderTargetKind::Color,
                             ClearMode::Transparent,
+                            None,
                         );
 
-                        let render_task_id = frame_state.render_tasks.add(blur_render_task);
-
-                        (render_task_id, picture_task_id)
+                        (blur_render_task_id, picture_task_id)
                     }
-                    PictureCompositeMode::Filter(FilterOp::DropShadow(_, blur_radius, _)) => {
-                        let blur_std_deviation = blur_radius * device_pixel_scale.0;
+                    PictureCompositeMode::Filter(Filter::DropShadow(shadow)) => {
+                        let blur_std_deviation = shadow.blur_radius * device_pixel_scale.0;
                         let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
                         let rounded_std_dev = blur_std_deviation.round();
                         let rounded_std_dev = DeviceSize::new(rounded_std_dev, rounded_std_dev);
                         // The clipped field is the part of the picture that is visible
                         // on screen. The unclipped field is the screen-space rect of
                         // the complete picture, if no screen / clip-chain was applied
                         // (this includes the extra space for blur region). To ensure
                         // that we draw a large enough part of the picture to get correct
@@ -2568,29 +2567,90 @@ impl PicturePrimitive {
                             uv_rect_kind,
                             raster_spatial_node_index,
                             device_pixel_scale,
                         );
                         picture_task.mark_for_saving();
 
                         let picture_task_id = frame_state.render_tasks.add(picture_task);
 
-                        let blur_render_task = RenderTask::new_blur(
+                        let blur_render_task_id = RenderTask::new_blur(
                             rounded_std_dev,
                             picture_task_id,
                             frame_state.render_tasks,
                             RenderTargetKind::Color,
                             ClearMode::Transparent,
+                            None,
                         );
 
                         self.secondary_render_task_id = Some(picture_task_id);
 
-                        let render_task_id = frame_state.render_tasks.add(blur_render_task);
-
-                        (render_task_id, picture_task_id)
+                        (blur_render_task_id, picture_task_id)
+                    }
+                    PictureCompositeMode::Filter(Filter::DropShadowStack(ref shadows)) => {
+                        let mut max_std_deviation = 0.0;
+                        for shadow in shadows {
+                            // TODO(nical) presumably we should compute the clipped rect for each shadow
+                            // and compute the union of them to determine what we need to rasterize and blur?
+                            max_std_deviation = f32::max(max_std_deviation, shadow.blur_radius * device_pixel_scale.0);
+                        }
+        
+                        max_std_deviation = max_std_deviation.round();
+                        let max_blur_range = (max_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
+                        let mut device_rect = clipped.inflate(max_blur_range, max_blur_range)
+                                .intersection(&unclipped.to_i32())
+                                .unwrap();
+                        device_rect.size = RenderTask::adjusted_blur_source_size(
+                            device_rect.size,
+                            DeviceSize::new(max_std_deviation, max_std_deviation),
+                        );
+        
+                        let uv_rect_kind = calculate_uv_rect_kind(
+                            &pic_rect,
+                            &transform,
+                            &device_rect,
+                            device_pixel_scale,
+                            true,
+                        );
+        
+                        let mut picture_task = RenderTask::new_picture(
+                            RenderTaskLocation::Dynamic(None, device_rect.size),
+                            unclipped.size,
+                            pic_index,
+                            device_rect.origin,
+                            Vec::new(),
+                            uv_rect_kind,
+                            raster_spatial_node_index,
+                            device_pixel_scale,
+                        );
+                        picture_task.mark_for_saving();
+        
+                        let picture_task_id = frame_state.render_tasks.add(picture_task);
+        
+                        self.secondary_render_task_id = Some(picture_task_id);
+        
+                        let mut blur_tasks = BlurTaskCache::default();
+        
+                        self.extra_gpu_data_handles.resize(shadows.len(), GpuCacheHandle::new());
+        
+                        let mut blur_render_task_id = picture_task_id;
+                        for shadow in shadows {
+                            let std_dev = f32::round(shadow.blur_radius * device_pixel_scale.0);
+                            blur_render_task_id = RenderTask::new_blur(
+                                DeviceSize::new(std_dev, std_dev),
+                                picture_task_id,
+                                frame_state.render_tasks,
+                                RenderTargetKind::Color,
+                                ClearMode::Transparent,
+                                Some(&mut blur_tasks),
+                            );      
+                        }
+        
+                        // TODO(nical) the second one should to be the blur's task id but we have several blurs now
+                        (picture_task_id, blur_render_task_id)
                     }
                     PictureCompositeMode::MixBlend(..) if !frame_context.fb_config.gpu_supports_advanced_blend => {
                         let uv_rect_kind = calculate_uv_rect_kind(
                             &pic_rect,
                             &transform,
                             &clipped,
                             device_pixel_scale,
                             true,
@@ -2997,17 +3057,17 @@ impl PicturePrimitive {
             let (parent_raster_node_index, parent_allows_subpixel_aa)= {
                 let parent_surface = state.current_surface();
                 (parent_surface.raster_spatial_node_index, parent_surface.allow_subpixel_aa)
             };
             let surface_spatial_node_index = self.spatial_node_index;
 
             // This inflation factor is to be applied to all primitives within the surface.
             let inflation_factor = match composite_mode {
-                PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)) => {
+                PictureCompositeMode::Filter(Filter::Blur(blur_radius)) => {
                     // Only inflate if the caller hasn't already inflated
                     // the bounding rects for this filter.
                     if self.options.inflate_if_required {
                         // The amount of extra space needed for primitives inside
                         // this picture to ensure the visibility check is correct.
                         BLUR_SAMPLE_SCALE * blur_radius
                     } else {
                         0.0
@@ -3125,20 +3185,30 @@ impl PicturePrimitive {
         // If this picture establishes a surface, then map the surface bounding
         // rect into the parent surface coordinate space, and propagate that up
         // to the parent.
         if let Some(ref mut raster_config) = self.raster_config {
             let mut surface_rect = {
                 let surface = state.current_surface_mut();
                 // Inflate the local bounding rect if required by the filter effect.
                 // This inflaction factor is to be applied to the surface itsefl.
+                // TODO: in prepare_for_render we round before multiplying with the
+                // blur sample scale. Should we do this here as well?
                 let inflation_size = match raster_config.composite_mode {
-                    PictureCompositeMode::Filter(FilterOp::Blur(_)) => surface.inflation_factor,
-                    PictureCompositeMode::Filter(FilterOp::DropShadow(_, blur_radius, _)) =>
-                        (blur_radius * BLUR_SAMPLE_SCALE).ceil(),
+                    PictureCompositeMode::Filter(Filter::Blur(_)) => surface.inflation_factor,
+                    PictureCompositeMode::Filter(Filter::DropShadow(shadow)) => {
+                        (shadow.blur_radius * BLUR_SAMPLE_SCALE).ceil()
+                    }
+                    PictureCompositeMode::Filter(Filter::DropShadowStack(ref shadows)) => {
+                        let mut max = 0.0;
+                        for shadow in shadows {
+                            max = f32::max(max, shadow.blur_radius * BLUR_SAMPLE_SCALE);
+                        }
+                        max.ceil()
+                    }
                     _ => 0.0,
                 };
                 surface.rect = surface.rect.inflate(inflation_size, inflation_size);
                 surface.rect * TypedScale::new(1.0)
             };
 
             // Pop this surface from the stack
             let surface_index = state.pop_surface();
@@ -3156,20 +3226,30 @@ impl PicturePrimitive {
                 {
                     raster_config.establishes_raster_root = false;
                     state.are_raster_roots_assigned = false;
                 }
             }
 
             // Drop shadows draw both a content and shadow rect, so need to expand the local
             // rect of any surfaces to be composited in parent surfaces correctly.
-            if let PictureCompositeMode::Filter(FilterOp::DropShadow(offset, ..)) = raster_config.composite_mode {
-                let content_rect = surface_rect;
-                let shadow_rect = surface_rect.translate(&offset);
-                surface_rect = content_rect.union(&shadow_rect);
+            match raster_config.composite_mode {
+                PictureCompositeMode::Filter(Filter::DropShadow(shadow)) => {
+                    let content_rect = surface_rect;
+                    let shadow_rect = surface_rect.translate(&shadow.offset);
+                    surface_rect = content_rect.union(&shadow_rect);
+                }
+                PictureCompositeMode::Filter(Filter::DropShadowStack(ref shadows)) => {
+                    for shadow in shadows {
+                        let content_rect = surface_rect;
+                        let shadow_rect = surface_rect.translate(&shadow.offset);
+                        surface_rect = content_rect.union(&shadow_rect);
+                    }
+                }
+                _ => {}
             }
 
             // Propagate up to parent surface, now that we know this surface's static rect
             let parent_surface = state.current_surface_mut();
             parent_surface.map_local_to_surface.set_target_spatial_node(
                 self.spatial_node_index,
                 frame_context.clip_scroll_tree,
             );
@@ -3209,50 +3289,82 @@ impl PicturePrimitive {
         //           to store the same type of data. The exception is the filter
         //           with a ColorMatrix, which stores the color matrix here. It's
         //           probably worth tidying this code up to be a bit more consistent.
         //           Perhaps store the color matrix after the common data, even though
         //           it's not used by that shader.
 
         match raster_config.composite_mode {
             PictureCompositeMode::TileCache { .. } => {}
-            PictureCompositeMode::Filter(FilterOp::Blur(..)) => {}
-            PictureCompositeMode::Filter(FilterOp::DropShadow(offset, _, color)) => {
-                if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
+            PictureCompositeMode::Filter(Filter::Blur(..)) => {}
+            PictureCompositeMode::Filter(Filter::DropShadow(shadow)) => {
+                if self.extra_gpu_data_handles.is_empty() {
+                    self.extra_gpu_data_handles.push(GpuCacheHandle::new());
+                }
+
+                if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handles[0]) {
                     // TODO(gw): This is very hacky code below! It stores an extra
                     //           brush primitive below for the special case of a
                     //           drop-shadow where we need a different local
                     //           rect for the shadow. To tidy this up in future,
                     //           we could consider abstracting the code in prim_store.rs
                     //           that writes a brush primitive header.
 
                     // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
                     //  [brush specific data]
                     //  [segment_rect, segment data]
-                    let shadow_rect = self.snapped_local_rect.translate(&offset);
+                    let shadow_rect = self.snapped_local_rect.translate(&shadow.offset);
 
                     // ImageBrush colors
-                    request.push(color.premultiplied());
+                    request.push(shadow.color.premultiplied());
                     request.push(PremultipliedColorF::WHITE);
                     request.push([
                         self.snapped_local_rect.size.width,
                         self.snapped_local_rect.size.height,
                         0.0,
                         0.0,
                     ]);
 
                     // segment rect / extra data
                     request.push(shadow_rect);
                     request.push([0.0, 0.0, 0.0, 0.0]);
                 }
             }
+            PictureCompositeMode::Filter(Filter::DropShadowStack(ref shadows)) => {
+                self.extra_gpu_data_handles.resize(shadows.len(), GpuCacheHandle::new());
+                for (shadow, extra_handle) in shadows.iter().zip(self.extra_gpu_data_handles.iter_mut()) {
+                    if let Some(mut request) = frame_state.gpu_cache.request(extra_handle) {
+                        // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
+                        //  [brush specific data]
+                        //  [segment_rect, segment data]
+                        let shadow_rect = self.snapped_local_rect.translate(&shadow.offset);
+
+                        // ImageBrush colors
+                        request.push(shadow.color.premultiplied());
+                        request.push(PremultipliedColorF::WHITE);
+                        request.push([
+                            self.snapped_local_rect.size.width,
+                            self.snapped_local_rect.size.height,
+                            0.0,
+                            0.0,
+                        ]);
+
+                        // segment rect / extra data
+                        request.push(shadow_rect);
+                        request.push([0.0, 0.0, 0.0, 0.0]);
+                    }
+                }
+            }
             PictureCompositeMode::MixBlend(..) if !frame_context.fb_config.gpu_supports_advanced_blend => {}
             PictureCompositeMode::Filter(ref filter) => {
-                if let FilterOp::ColorMatrix(m) = *filter {
-                    if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
+                if let Filter::ColorMatrix(m) = *filter {
+                    if self.extra_gpu_data_handles.is_empty() {
+                        self.extra_gpu_data_handles.push(GpuCacheHandle::new());
+                    }
+                    if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handles[0]) {
                         for i in 0..5 {
                             request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
                         }
                     }
                 }
             }
             PictureCompositeMode::ComponentTransferFilter(handle) => {
                 let filter_data = &mut data_stores.filter_data[handle];
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, ClipMode, ColorF};
-use api::{FilterOp, ImageRendering, RepeatMode};
+use api::{ImageRendering, RepeatMode};
 use api::{PremultipliedColorF, PropertyBinding, Shadow, GradientStop};
 use api::{BoxShadowClipMode, LineStyle, LineOrientation};
 use api::{PrimitiveKeyKind, RasterSpace};
 use api::units::*;
 use crate::border::{get_max_scale_for_border, build_border_instances};
 use crate::border::BorderSegmentCacheKey;
 use crate::box_shadow::{BLUR_SAMPLE_SCALE};
 use crate::clip::{ClipStore};
@@ -47,17 +47,17 @@ use crate::scene::SceneProperties;
 use crate::segment::SegmentBuilder;
 use std::{cmp, fmt, hash, ops, u32, usize, mem};
 #[cfg(debug_assertions)]
 use std::sync::atomic::{AtomicUsize, Ordering};
 use crate::storage;
 use crate::texture_cache::TEXTURE_REGION_DIMENSIONS;
 use crate::util::{MatrixHelpers, MaxRect, Recycler};
 use crate::util::{clamp_to_scale_factor, pack_as_float, project_rect, raster_rect_to_device_pixels};
-use crate::internal_types::LayoutPrimitiveInfo;
+use crate::internal_types::{LayoutPrimitiveInfo, Filter};
 use smallvec::SmallVec;
 
 pub mod borders;
 pub mod gradient;
 pub mod image;
 pub mod line_dec;
 pub mod picture;
 pub mod text_run;
@@ -1847,17 +1847,25 @@ impl PrimitiveStore {
                     } else {
                         frame_state.clip_chain_stack.pop_clip();
                     }
 
                     let shadow_rect = match pic.raster_config {
                         Some(ref rc) => match rc.composite_mode {
                             // If we have a drop shadow filter, we also need to include the shadow in
                             // our local rect for the purpose of calculating the size of the picture.
-                            PictureCompositeMode::Filter(FilterOp::DropShadow(offset, ..)) => pic.snapped_local_rect.translate(&offset),
+                            PictureCompositeMode::Filter(Filter::DropShadow(shadow)) => pic.snapped_local_rect.translate(&shadow.offset),
+                            PictureCompositeMode::Filter(Filter::DropShadowStack(ref shadows)) => {
+                                let mut rect = LayoutRect::zero();
+                                for shadow in shadows {
+                                    rect = rect.union(&pic.snapped_local_rect.translate(&shadow.offset));
+                                }
+
+                                rect
+                            }
                             _ => LayoutRect::zero(),
                         }
                         None => {
                             if let Some(ref rect) = pic_surface_rect {
                                 surface_rect = surface_rect.union(rect);
                             }
                             LayoutRect::zero()
                         }
@@ -2127,29 +2135,44 @@ impl PrimitiveStore {
         // size for the extra GPU cache data handle.
         // TODO(gw): In future, if we support specifying a flag which gets the
         //           stretch size from the segment rect in the shaders, we can
         //           remove this invalidation here completely.
         if let Some(ref raster_config) = pic.raster_config {
             // Inflate the local bounding rect if required by the filter effect.
             // This inflaction factor is to be applied to the surface itself.
             let inflation_size = match raster_config.composite_mode {
-                PictureCompositeMode::Filter(FilterOp::Blur(_)) => surface.inflation_factor,
-                PictureCompositeMode::Filter(FilterOp::DropShadow(_, blur_radius, _)) =>
-                    (blur_radius * BLUR_SAMPLE_SCALE).ceil(),
+                PictureCompositeMode::Filter(Filter::Blur(_)) => surface.inflation_factor,
+                PictureCompositeMode::Filter(Filter::DropShadow(shadow)) => {
+                    (shadow.blur_radius * BLUR_SAMPLE_SCALE).ceil()
+                }
+                PictureCompositeMode::Filter(Filter::DropShadowStack(ref shadows)) => {
+                    let mut max = 0.0;
+                    for shadow in shadows {
+                        max = f32::max(max, shadow.blur_radius * BLUR_SAMPLE_SCALE);
+                    }
+                    max.ceil()
+                }
                 _ => 0.0,
             };
             surface_rect = surface_rect.inflate(inflation_size, inflation_size);
 
             // Layout space for the picture is picture space from the
             // perspective of its child primitives.
             let pic_local_rect = surface_rect * TypedScale::new(1.0);
             if pic.snapped_local_rect != pic_local_rect {
-                if let PictureCompositeMode::Filter(FilterOp::DropShadow(..)) = raster_config.composite_mode {
-                    frame_state.gpu_cache.invalidate(&pic.extra_gpu_data_handle);
+                match raster_config.composite_mode {
+                    PictureCompositeMode::Filter(Filter::DropShadow(..)) 
+                    | PictureCompositeMode::Filter(Filter::DropShadowStack(..))
+                    => {
+                        for handle in &pic.extra_gpu_data_handles {
+                            frame_state.gpu_cache.invalidate(handle);
+                        }
+                    }
+                    _ => {}
                 }
                 // Invalidate any segments built for this picture, since the local
                 // rect has changed.
                 pic.segments_are_valid = false;
                 pic.snapped_local_rect = pic_local_rect;
             }
 
             if let PictureCompositeMode::TileCache { .. } = raster_config.composite_mode {
@@ -2392,17 +2415,17 @@ impl PrimitiveStore {
     // by pushing that opacity value into the color of a primitive
     // if that picture contains one compatible primitive.
     pub fn optimize_picture_if_possible(
         &mut self,
         pic_index: PictureIndex,
     ) {
         // Only handle opacity filters for now.
         let binding = match self.pictures[pic_index.0].requested_composite_mode {
-            Some(PictureCompositeMode::Filter(FilterOp::Opacity(binding, _))) => {
+            Some(PictureCompositeMode::Filter(Filter::Opacity(binding, _))) => {
                 binding
             }
             _ => {
                 return;
             }
         };
 
         // See if this picture contains a single primitive that supports
--- a/gfx/wr/webrender/src/prim_store/picture.rs
+++ b/gfx/wr/webrender/src/prim_store/picture.rs
@@ -1,21 +1,21 @@
 /* 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::{
-    ColorU, FilterOp, MixBlendMode,
+    ColorU, MixBlendMode,
     PropertyBinding, PropertyBindingId,
 };
 use api::units::{Au, LayoutSize, LayoutVector2D};
 use crate::intern::ItemUid;
 use crate::display_list_flattener::IsVisible;
 use crate::intern::{Internable, InternDebug, Handle as InternHandle};
-use crate::internal_types::LayoutPrimitiveInfo;
+use crate::internal_types::{LayoutPrimitiveInfo, Filter};
 use crate::picture::PictureCompositeMode;
 use crate::prim_store::{
     PrimKey, PrimKeyCommonData, PrimTemplate, PrimTemplateCommonData,
     PrimitiveInstanceKind, PrimitiveSceneData, PrimitiveStore, VectorKey,
     InternablePrimitive,
 };
 
 /// Represents a hashable description of how a picture primitive
@@ -34,16 +34,17 @@ pub enum PictureCompositeKey {
     Grayscale(Au),
     HueRotate(Au),
     Invert(Au),
     Opacity(Au),
     OpacityBinding(PropertyBindingId, Au),
     Saturate(Au),
     Sepia(Au),
     DropShadow(VectorKey, Au, ColorU),
+    DropShadowStack(Vec<(VectorKey, Au, ColorU)>),
     ColorMatrix([Au; 20]),
     SrgbToLinear,
     LinearToSrgb,
     ComponentTransfer(ItemUid),
 
     // MixBlendMode
     Multiply,
     Screen,
@@ -82,48 +83,55 @@ impl From<Option<PictureCompositeMode>> 
                     MixBlendMode::Hue => PictureCompositeKey::Hue,
                     MixBlendMode::Saturation => PictureCompositeKey::Saturation,
                     MixBlendMode::Color => PictureCompositeKey::Color,
                     MixBlendMode::Luminosity => PictureCompositeKey::Luminosity,
                 }
             }
             Some(PictureCompositeMode::Filter(op)) => {
                 match op {
-                    FilterOp::Blur(value) => PictureCompositeKey::Blur(Au::from_f32_px(value)),
-                    FilterOp::Brightness(value) => PictureCompositeKey::Brightness(Au::from_f32_px(value)),
-                    FilterOp::Contrast(value) => PictureCompositeKey::Contrast(Au::from_f32_px(value)),
-                    FilterOp::Grayscale(value) => PictureCompositeKey::Grayscale(Au::from_f32_px(value)),
-                    FilterOp::HueRotate(value) => PictureCompositeKey::HueRotate(Au::from_f32_px(value)),
-                    FilterOp::Invert(value) => PictureCompositeKey::Invert(Au::from_f32_px(value)),
-                    FilterOp::Saturate(value) => PictureCompositeKey::Saturate(Au::from_f32_px(value)),
-                    FilterOp::Sepia(value) => PictureCompositeKey::Sepia(Au::from_f32_px(value)),
-                    FilterOp::SrgbToLinear => PictureCompositeKey::SrgbToLinear,
-                    FilterOp::LinearToSrgb => PictureCompositeKey::LinearToSrgb,
-                    FilterOp::Identity => PictureCompositeKey::Identity,
-                    FilterOp::DropShadow(offset, radius, color) => {
-                        PictureCompositeKey::DropShadow(offset.into(), Au::from_f32_px(radius), color.into())
+                    Filter::Blur(value) => PictureCompositeKey::Blur(Au::from_f32_px(value)),
+                    Filter::Brightness(value) => PictureCompositeKey::Brightness(Au::from_f32_px(value)),
+                    Filter::Contrast(value) => PictureCompositeKey::Contrast(Au::from_f32_px(value)),
+                    Filter::Grayscale(value) => PictureCompositeKey::Grayscale(Au::from_f32_px(value)),
+                    Filter::HueRotate(value) => PictureCompositeKey::HueRotate(Au::from_f32_px(value)),
+                    Filter::Invert(value) => PictureCompositeKey::Invert(Au::from_f32_px(value)),
+                    Filter::Saturate(value) => PictureCompositeKey::Saturate(Au::from_f32_px(value)),
+                    Filter::Sepia(value) => PictureCompositeKey::Sepia(Au::from_f32_px(value)),
+                    Filter::SrgbToLinear => PictureCompositeKey::SrgbToLinear,
+                    Filter::LinearToSrgb => PictureCompositeKey::LinearToSrgb,
+                    Filter::Identity => PictureCompositeKey::Identity,
+                    Filter::DropShadowStack(ref shadows) => {
+                        PictureCompositeKey::DropShadowStack(
+                            shadows.iter().map(|shadow| {
+                                (shadow.offset.into(), Au::from_f32_px(shadow.blur_radius), shadow.color.into())
+                            }).collect()
+                        )
                     }
-                    FilterOp::Opacity(binding, _) => {
+                    Filter::DropShadow(shadow) => {
+                        PictureCompositeKey::DropShadow(shadow.offset.into(), Au::from_f32_px(shadow.blur_radius), shadow.color.into())
+                    }
+                    Filter::Opacity(binding, _) => {
                         match binding {
                             PropertyBinding::Value(value) => {
                                 PictureCompositeKey::Opacity(Au::from_f32_px(value))
                             }
                             PropertyBinding::Binding(key, default) => {
                                 PictureCompositeKey::OpacityBinding(key.id, Au::from_f32_px(default))
                             }
                         }
                     }
-                    FilterOp::ColorMatrix(values) => {
+                    Filter::ColorMatrix(values) => {
                         let mut quantized_values: [Au; 20] = [Au(0); 20];
                         for (value, result) in values.iter().zip(quantized_values.iter_mut()) {
                             *result = Au::from_f32_px(*value);
                         }
                         PictureCompositeKey::ColorMatrix(quantized_values)
                     }
-                    FilterOp::ComponentTransfer => unreachable!(),
+                    Filter::ComponentTransfer => unreachable!(),
                 }
             }
             Some(PictureCompositeMode::ComponentTransferFilter(handle)) => {
                 PictureCompositeKey::ComponentTransfer(handle.uid())
             }
             Some(PictureCompositeMode::Blit(_)) |
             Some(PictureCompositeMode::TileCache { .. }) |
             None => {
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -671,16 +671,42 @@ pub enum ClearMode {
     One,
     /// This task doesn't care what it is cleared to - it will completely overwrite it.
     DontCare,
 
     // Applicable to color targets only.
     Transparent,
 }
 
+/// In order to avoid duplicating the down-scaling and blur passes when a picture has several blurs,
+/// we use a local (primitive-level) cache of the render tasks generated for a single shadowed primitive
+/// in a single frame.
+pub type BlurTaskCache = FastHashMap<BlurTaskKey, RenderTaskId>;
+
+/// Since we only use it within a single primitive, the key only needs to contain the down-scaling level
+/// and the blur std deviation.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub enum BlurTaskKey {
+    DownScale(u32),
+    Blur { downscale_level: u32, stddev_x: u32, stddev_y: u32 },
+}
+
+impl BlurTaskKey {
+    fn downscale_and_blur(downscale_level: u32, blur_stddev: DeviceSize) -> Self {
+        // Quantise the std deviations and store it as integers to work around
+        // Eq and Hash's f32 allergy.
+        // The blur radius is rounded before RenderTask::new_blur so we don't need
+        // a lot of precision.
+        const QUANTIZATION_FACTOR: f32 = 1024.0;
+        let stddev_x = (blur_stddev.width * QUANTIZATION_FACTOR) as u32;
+        let stddev_y = (blur_stddev.height * QUANTIZATION_FACTOR) as u32;
+        BlurTaskKey::Blur { downscale_level, stddev_x, stddev_y }
+    }
+}
+
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTask {
     pub location: RenderTaskLocation,
     pub children: Vec<RenderTaskId>,
     pub kind: RenderTaskKind,
     pub clear_mode: ClearMode,
@@ -900,25 +926,24 @@ impl RenderTask {
                                 info.minimal_shadow_rect.origin,
                                 device_pixel_scale,
                                 fb_config,
                             );
 
                             let mask_task_id = render_tasks.add(mask_task);
 
                             // Blur it
-                            let blur_render_task = RenderTask::new_blur(
+                            RenderTask::new_blur(
                                 DeviceSize::new(blur_radius_dp, blur_radius_dp),
                                 mask_task_id,
                                 render_tasks,
                                 RenderTargetKind::Alpha,
                                 ClearMode::Zero,
-                            );
-
-                            render_tasks.add(blur_render_task)
+                                None,
+                            )
                         }
                     ));
                 }
                 ClipItem::Rectangle(_, ClipMode::Clip) => {
                     if !clip_instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) {
                         // This is conservative - it's only the case that we actually need
                         // a clear here if we end up adding this mask via add_tiled_clip_mask,
                         // but for simplicity we will just clear if any of these are encountered,
@@ -1018,69 +1043,103 @@ impl RenderTask {
     //           +---- This is stored as the input task to the primitive shader.
     //
     pub fn new_blur(
         blur_std_deviation: DeviceSize,
         src_task_id: RenderTaskId,
         render_tasks: &mut RenderTaskTree,
         target_kind: RenderTargetKind,
         clear_mode: ClearMode,
-    ) -> Self {
+        mut blur_cache: Option<&mut BlurTaskCache>,
+    ) -> RenderTaskId {
         // Adjust large std deviation value.
         let mut adjusted_blur_std_deviation = blur_std_deviation;
         let (blur_target_size, uv_rect_kind) = {
             let src_task = &render_tasks[src_task_id];
             (src_task.get_dynamic_size(), src_task.uv_rect_kind())
         };
         let mut adjusted_blur_target_size = blur_target_size;
         let mut downscaling_src_task_id = src_task_id;
         let mut scale_factor = 1.0;
+        let mut n_downscales = 1;
         while adjusted_blur_std_deviation.width > MAX_BLUR_STD_DEVIATION &&
               adjusted_blur_std_deviation.height > MAX_BLUR_STD_DEVIATION {
             if adjusted_blur_target_size.width < MIN_DOWNSCALING_RT_SIZE ||
                adjusted_blur_target_size.height < MIN_DOWNSCALING_RT_SIZE {
                 break;
             }
             adjusted_blur_std_deviation = adjusted_blur_std_deviation * 0.5;
             scale_factor *= 2.0;
             adjusted_blur_target_size = (blur_target_size.to_f32() / scale_factor).to_i32();
-            let downscaling_task = RenderTask::new_scaling(
-                downscaling_src_task_id,
-                render_tasks,
-                target_kind,
-                adjusted_blur_target_size,
-            );
-            downscaling_src_task_id = render_tasks.add(downscaling_task);
+
+            let cached_task = match blur_cache {
+                Some(ref mut cache) => cache.get(&BlurTaskKey::DownScale(n_downscales)).cloned(),
+                None => None,
+            };
+
+            downscaling_src_task_id = cached_task.unwrap_or_else(|| {
+                let downscaling_task = RenderTask::new_scaling(
+                    downscaling_src_task_id,
+                    render_tasks,
+                    target_kind,
+                    adjusted_blur_target_size,
+                );
+                render_tasks.add(downscaling_task)
+            });
+
+            if let Some(ref mut cache) = blur_cache {
+                cache.insert(BlurTaskKey::DownScale(n_downscales), downscaling_src_task_id);
+            }
+
+            n_downscales += 1;
         }
 
-        let blur_task_v = RenderTask::with_dynamic_location(
-            adjusted_blur_target_size,
-            vec![downscaling_src_task_id],
-            RenderTaskKind::VerticalBlur(BlurTask {
-                blur_std_deviation: adjusted_blur_std_deviation.height,
-                target_kind,
-                uv_rect_handle: GpuCacheHandle::new(),
-                uv_rect_kind,
-            }),
-            clear_mode,
-        );
+
+        let blur_key = BlurTaskKey::downscale_and_blur(n_downscales, adjusted_blur_std_deviation);
+
+        let cached_task = match blur_cache {
+            Some(ref mut cache) => cache.get(&blur_key).cloned(),
+            None => None,
+        };
+
+        let blur_task_id = cached_task.unwrap_or_else(|| {
+            let blur_task_v = RenderTask::with_dynamic_location(
+                adjusted_blur_target_size,
+                vec![downscaling_src_task_id],
+                RenderTaskKind::VerticalBlur(BlurTask {
+                    blur_std_deviation: adjusted_blur_std_deviation.height,
+                    target_kind,
+                    uv_rect_handle: GpuCacheHandle::new(),
+                    uv_rect_kind,
+                }),
+                clear_mode,
+            );
 
-        let blur_task_v_id = render_tasks.add(blur_task_v);
+            let blur_task_v_id = render_tasks.add(blur_task_v);
 
-        RenderTask::with_dynamic_location(
-            adjusted_blur_target_size,
-            vec![blur_task_v_id],
-            RenderTaskKind::HorizontalBlur(BlurTask {
-                blur_std_deviation: adjusted_blur_std_deviation.width,
-                target_kind,
-                uv_rect_handle: GpuCacheHandle::new(),
-                uv_rect_kind,
-            }),
-            clear_mode,
-        )
+            let blur_task_h = RenderTask::with_dynamic_location(
+                adjusted_blur_target_size,
+                vec![blur_task_v_id],
+                RenderTaskKind::HorizontalBlur(BlurTask {
+                    blur_std_deviation: adjusted_blur_std_deviation.width,
+                    target_kind,
+                    uv_rect_handle: GpuCacheHandle::new(),
+                    uv_rect_kind,
+                }),
+                clear_mode,
+            );
+
+            render_tasks.add(blur_task_h)
+        });
+
+        if let Some(ref mut cache) = blur_cache {
+            cache.insert(blur_key, blur_task_id);
+        }
+
+        blur_task_id
     }
 
     pub fn new_border_segment(
         size: DeviceIntSize,
         instances: Vec<BorderInstance>,
     ) -> Self {
         RenderTask::with_dynamic_location(
             size,
@@ -1388,17 +1447,16 @@ impl RenderTask {
             RenderTaskKind::Test(..) => {
                 panic!("RenderTask tests aren't expected to exercise this code");
             }
         };
 
         if let Some(mut request) = gpu_cache.request(cache_handle) {
             let p0 = target_rect.origin.to_f32();
             let p1 = target_rect.bottom_right().to_f32();
-
             let image_source = ImageSource {
                 p0,
                 p1,
                 texture_layer: target_index.0 as f32,
                 user_data: [0.0; 3],
                 uv_rect_kind,
             };
             image_source.write_gpu_blocks(&mut request);
--- a/gfx/wr/webrender/src/scene.rs
+++ b/gfx/wr/webrender/src/scene.rs
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BuiltDisplayList, ColorF, DynamicProperties, Epoch};
 use api::{FilterOp, TempFilterData, FilterData, ComponentTransferFuncType};
 use api::{PipelineId, PropertyBinding, PropertyBindingId, ItemRange, MixBlendMode, StackingContext};
 use api::units::{LayoutSize, LayoutTransform};
-use crate::internal_types::FastHashMap;
+use crate::internal_types::{FastHashMap, Filter};
 use std::sync::Arc;
 
 /// Stores a map of the animated property bindings for the current display list. These
 /// can be used to animate the transform and/or opacity of a display list without
 /// re-submitting the display list itself.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct SceneProperties {
@@ -198,83 +198,23 @@ impl Scene {
         if let Some(ref root_id) = self.root_pipeline_id {
             return self.pipelines.contains_key(root_id);
         }
 
         false
     }
 }
 
-/// An arbitrary number which we assume opacity is invisible below.
-pub const OPACITY_EPSILON: f32 = 0.001;
-
-pub trait FilterOpHelpers {
-    fn is_visible(&self) -> bool;
-    fn is_noop(&self) -> bool;
-}
-
-impl FilterOpHelpers for FilterOp {
-    fn is_visible(&self) -> bool {
-        match *self {
-            FilterOp::Identity |
-            FilterOp::Blur(..) |
-            FilterOp::Brightness(..) |
-            FilterOp::Contrast(..) |
-            FilterOp::Grayscale(..) |
-            FilterOp::HueRotate(..) |
-            FilterOp::Invert(..) |
-            FilterOp::Saturate(..) |
-            FilterOp::Sepia(..) |
-            FilterOp::DropShadow(..) |
-            FilterOp::ColorMatrix(..) |
-            FilterOp::SrgbToLinear |
-            FilterOp::LinearToSrgb |
-            FilterOp::ComponentTransfer  => true,
-            FilterOp::Opacity(_, amount) => {
-                amount > OPACITY_EPSILON
-            }
-        }
-    }
-
-    fn is_noop(&self) -> bool {
-        match *self {
-            FilterOp::Identity => false, // this is intentional
-            FilterOp::Blur(length) => length == 0.0,
-            FilterOp::Brightness(amount) => amount == 1.0,
-            FilterOp::Contrast(amount) => amount == 1.0,
-            FilterOp::Grayscale(amount) => amount == 0.0,
-            FilterOp::HueRotate(amount) => amount == 0.0,
-            FilterOp::Invert(amount) => amount == 0.0,
-            FilterOp::Opacity(_, amount) => amount >= 1.0,
-            FilterOp::Saturate(amount) => amount == 1.0,
-            FilterOp::Sepia(amount) => amount == 0.0,
-            FilterOp::DropShadow(offset, blur, _) => {
-                offset.x == 0.0 && offset.y == 0.0 && blur == 0.0
-            },
-            FilterOp::ColorMatrix(matrix) => {
-                matrix == [1.0, 0.0, 0.0, 0.0,
-                           0.0, 1.0, 0.0, 0.0,
-                           0.0, 0.0, 1.0, 0.0,
-                           0.0, 0.0, 0.0, 1.0,
-                           0.0, 0.0, 0.0, 0.0]
-            }
-            FilterOp::SrgbToLinear |
-            FilterOp::LinearToSrgb |
-            FilterOp::ComponentTransfer => false,
-        }
-    }
-}
-
 pub trait StackingContextHelpers {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode>;
     fn filter_ops_for_compositing(
         &self,
         display_list: &BuiltDisplayList,
         input_filters: ItemRange<FilterOp>,
-    ) -> Vec<FilterOp>;
+    ) -> Vec<Filter>;
     fn filter_datas_for_compositing(
         &self,
         display_list: &BuiltDisplayList,
         input_filter_datas: &[TempFilterData],
     ) -> Vec<FilterData>;
 }
 
 impl StackingContextHelpers for StackingContext {
@@ -284,23 +224,23 @@ impl StackingContextHelpers for Stacking
             _ => Some(self.mix_blend_mode),
         }
     }
 
     fn filter_ops_for_compositing(
         &self,
         display_list: &BuiltDisplayList,
         input_filters: ItemRange<FilterOp>,
-    ) -> Vec<FilterOp> {
+    ) -> Vec<Filter> {
         // TODO(gw): Now that we resolve these later on,
         //           we could probably make it a bit
         //           more efficient than cloning these here.
         let mut filters = vec![];
         for filter in display_list.get(input_filters) {
-            filters.push(filter);
+            filters.push(filter.into());
         }
         filters
     }
 
     fn filter_datas_for_compositing(
         &self,
         display_list: &BuiltDisplayList,
         input_filter_datas: &[TempFilterData],
--- a/gfx/wr/webrender/src/tiling.rs
+++ b/gfx/wr/webrender/src/tiling.rs
@@ -1,29 +1,29 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ColorF, BorderStyle, MixBlendMode, PipelineId, PremultipliedColorF};
-use api::{DocumentLayer, FilterData, FilterOp, ImageFormat, LineOrientation};
+use api::{DocumentLayer, FilterData, ImageFormat, LineOrientation};
 use api::units::*;
 #[cfg(feature = "pathfinder")]
 use api::FontRenderMode;
 use crate::batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image, BatchBuilder};
 use crate::clip::ClipStore;
 use crate::clip_scroll_tree::{ClipScrollTree};
 use crate::debug_render::DebugItem;
 use crate::device::{Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use crate::frame_builder::FrameGlobalResources;
 use crate::gpu_cache::{GpuCache};
 use crate::gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
 use crate::gpu_types::{TransformData, TransformPalette, ZBufferIdGenerator};
-use crate::internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex, TextureSource};
+use crate::internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex, TextureSource, Filter};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use crate::picture::{RecordedDirtyRegion, SurfaceInfo};
 use crate::prim_store::gradient::GRADIENT_FP_STOPS;
 use crate::prim_store::{PrimitiveStore, DeferredResolve, PrimitiveScratchBuffer};
 use crate::profiler::FrameProfileCounters;
 use crate::render_backend::{DataStores, FrameId};
 use crate::render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
@@ -1155,25 +1155,25 @@ impl RenderPass {
             }
         }
     }
 }
 
 #[derive(Debug, Clone, Default)]
 pub struct CompositeOps {
     // Requires only a single texture as input (e.g. most filters)
-    pub filters: Vec<FilterOp>,
+    pub filters: Vec<Filter>,
     pub filter_datas: Vec<FilterData>,
 
     // Requires two source textures (e.g. mix-blend-mode)
     pub mix_blend_mode: Option<MixBlendMode>,
 }
 
 impl CompositeOps {
-    pub fn new(filters: Vec<FilterOp>,
+    pub fn new(filters: Vec<Filter>,
                filter_datas: Vec<FilterData>,
                mix_blend_mode: Option<MixBlendMode>) -> Self {
         CompositeOps {
             filters,
             filter_datas,
             mix_blend_mode,
         }
     }
--- a/gfx/wr/webrender_api/src/display_item.rs
+++ b/gfx/wr/webrender_api/src/display_item.rs
@@ -516,25 +516,25 @@ pub struct BoxShadowDisplayItem {
     pub border_radius: BorderRadius,
     pub clip_mode: BoxShadowClipMode,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct PushShadowDisplayItem {
     pub space_and_clip: SpaceAndClipInfo,
     pub shadow: Shadow,
+    pub should_inflate: bool,
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct Shadow {
     pub offset: LayoutVector2D,
     pub color: ColorF,
     pub blur_radius: f32,
-    pub should_inflate: bool,
 }
 
 #[repr(u8)]
 #[derive(Debug, Copy, Clone, Hash, Eq, MallocSizeOf, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
 pub enum ExtendMode {
     Clamp,
     Repeat,
 }
@@ -707,41 +707,23 @@ pub enum FilterOp {
     Brightness(f32),
     Contrast(f32),
     Grayscale(f32),
     HueRotate(f32),
     Invert(f32),
     Opacity(PropertyBinding<f32>, f32),
     Saturate(f32),
     Sepia(f32),
-    DropShadow(LayoutVector2D, f32, ColorF),
+    DropShadow(Shadow),
     ColorMatrix([f32; 20]),
     SrgbToLinear,
     LinearToSrgb,
     ComponentTransfer,
 }
 
-impl FilterOp {
-    /// Ensure that the parameters for a filter operation
-    /// are sensible.
-    pub fn sanitize(&self) -> FilterOp {
-        match self {
-            FilterOp::Blur(radius) => {
-                let radius = radius.min(MAX_BLUR_RADIUS);
-                FilterOp::Blur(radius)
-            }
-            FilterOp::DropShadow(offset, radius, color) => {
-                let radius = radius.min(MAX_BLUR_RADIUS);
-                FilterOp::DropShadow(*offset, radius, *color)
-            }
-            filter => filter.clone(),
-        }
-    }
-}
-
 #[repr(u8)]
 #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
 pub enum ComponentTransferFuncType {
   Identity = 0,
   Table = 1,
   Discrete = 2,
   Linear = 3,
   Gamma = 4,
--- a/gfx/wr/webrender_api/src/display_list.rs
+++ b/gfx/wr/webrender_api/src/display_list.rs
@@ -1635,20 +1635,22 @@ impl DisplayListBuilder {
         });
         self.push_item(&item);
     }
 
     pub fn push_shadow(
         &mut self,
         space_and_clip: &di::SpaceAndClipInfo,
         shadow: di::Shadow,
+        should_inflate: bool,
     ) {
         let item = di::DisplayItem::PushShadow(di::PushShadowDisplayItem {
             space_and_clip: *space_and_clip,
             shadow,
+            should_inflate,
         });
         self.push_item(&item);
     }
 
     pub fn pop_all_shadows(&mut self) {
         self.push_item(&di::DisplayItem::PopAllShadows);
     }
 
--- a/gfx/wr/wrench/src/rawtest.rs
+++ b/gfx/wr/wrench/src/rawtest.rs
@@ -947,18 +947,18 @@ impl<'a> RawtestHarness<'a> {
                     ColorF::new(0.0, 1.0, 0.0, 1.0),
                 );
                 builder.push_shadow(
                     &space_and_clip,
                     Shadow {
                         offset: LayoutVector2D::new(1.0, 1.0),
                         blur_radius: 1.0,
                         color: ColorF::new(0.0, 0.0, 0.0, 1.0),
-                        should_inflate: true,
                     },
+                    true,
                 );
                 let info = CommonItemProperties {
                     clip_rect: rect(110., 110., 50., 2.),
                     clip_id,
                     spatial_id,
                     is_backface_visible: true,
                     hit_info: None,
                 };
@@ -1025,18 +1025,18 @@ impl<'a> RawtestHarness<'a> {
             };
 
             builder.push_shadow(
                 &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
                 Shadow {
                     offset: LayoutVector2D::new(1.0, 1.0),
                     blur_radius: 1.0,
                     color: shadow_color,
-                    should_inflate: true,
                 },
+                true,
             );
             let info = self.make_common_properties(rect(110., 110., 50., 2.));
             builder.push_line(
                 &info,
                 &info.clip_rect,
                 0.0, LineOrientation::Horizontal,
                 &ColorF::new(0.0, 0.0, 0.0, 1.0),
                 LineStyle::Solid,
--- a/gfx/wr/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wr/wrench/src/yaml_frame_reader.rs
@@ -1691,18 +1691,18 @@ impl YamlFrameReader {
         let color = yaml["color"].as_colorf().unwrap_or(ColorF::BLACK);
 
         dl.push_shadow(
             &SpaceAndClipInfo { spatial_id: info.spatial_id, clip_id: info.clip_id },
             Shadow {
                 blur_radius,
                 offset,
                 color,
-                should_inflate: true,
             },
+            true,
         );
     }
 
     fn handle_pop_all_shadows(&mut self, dl: &mut DisplayListBuilder) {
         dl.pop_all_shadows();
     }
 
     fn handle_clip_chain(&mut self, builder: &mut DisplayListBuilder, yaml: &Yaml) {
--- a/gfx/wr/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wr/wrench/src/yaml_frame_writer.rs
@@ -233,16 +233,25 @@ fn write_reference_frame(
             ReferenceFrameKind::Perspective { .. } => "perspective",
         },
         &properties.resolve_layout_transform(&reference_frame.transform)
     );
 
     usize_node(parent, "id", clip_id_mapper.add_spatial_id(reference_frame.id));
 }
 
+fn shadow_parameters(shadow: &Shadow) -> String {
+    format!(
+        "[{},{}],{},[{}]",
+        shadow.offset.x, shadow.offset.y,
+        shadow.blur_radius,
+        color_to_string(shadow.color)
+    )
+}
+
 fn write_stacking_context(
     parent: &mut Table,
     sc: &StackingContext,
     properties: &SceneProperties,
     filter_iter: AuxIter<FilterOp>,
     filter_data_iter: &[TempFilterData],
     display_list: &BuiltDisplayList,
 ) {
@@ -274,21 +283,21 @@ fn write_stacking_context(
             FilterOp::HueRotate(x) => { filters.push(Yaml::String(format!("hue-rotate({})", x))) }
             FilterOp::Invert(x) => { filters.push(Yaml::String(format!("invert({})", x))) }
             FilterOp::Opacity(x, _) => {
                 filters.push(Yaml::String(format!("opacity({})",
                                                   properties.resolve_float(&x))))
             }
             FilterOp::Saturate(x) => { filters.push(Yaml::String(format!("saturate({})", x))) }
             FilterOp::Sepia(x) => { filters.push(Yaml::String(format!("sepia({})", x))) }
-            FilterOp::DropShadow(offset, blur, color) => {
-                filters.push(Yaml::String(format!("drop-shadow([{},{}],{},[{}])",
-                                                  offset.x, offset.y,
-                                                  blur,
-                                                  color_to_string(color))))
+            FilterOp::DropShadow(shadow) => {
+                filters.push(Yaml::String(format!(
+                    "drop-shadow({})",
+                    shadow_parameters(&shadow)
+                )))
             }
             FilterOp::ColorMatrix(matrix) => {
                 filters.push(Yaml::String(format!("color-matrix({:?})", matrix)))
             }
             FilterOp::SrgbToLinear => {
                 filters.push(Yaml::String("srgb-to-linear".to_string()))
             }
             FilterOp::LinearToSrgb => {
--- a/gfx/wr/wrench/src/yaml_helper.rs
+++ b/gfx/wr/wrench/src/yaml_helper.rs
@@ -596,19 +596,21 @@ impl YamlHelper for Yaml {
                     Some(FilterOp::Sepia(args[0].parse().unwrap()))
                 }
                 ("srgb-to-linear", _, _)  => Some(FilterOp::SrgbToLinear),
                 ("linear-to-srgb", _, _)  => Some(FilterOp::LinearToSrgb),
                 ("drop-shadow", ref args, _) if args.len() == 3 => {
                     let str = format!("---\noffset: {}\nblur-radius: {}\ncolor: {}\n", args[0], args[1], args[2]);
                     let mut yaml_doc = YamlLoader::load_from_str(&str).expect("Failed to parse drop-shadow");
                     let yaml = yaml_doc.pop().unwrap();
-                    Some(FilterOp::DropShadow(yaml["offset"].as_vector().unwrap(),
-                                              yaml["blur-radius"].as_f32().unwrap(),
-                                              yaml["color"].as_colorf().unwrap()))
+                    Some(FilterOp::DropShadow(Shadow {
+                        offset: yaml["offset"].as_vector().unwrap(),
+                        blur_radius: yaml["blur-radius"].as_f32().unwrap(),
+                        color: yaml["color"].as_colorf().unwrap()
+                    }))
                 }
                 ("color-matrix", ref args, _) if args.len() == 20 => {
                     let m: Vec<f32> = args.iter().map(|f| f.parse().unwrap()).collect();
                     let mut matrix: [f32; 20] = [0.0; 20];
                     matrix.clone_from_slice(&m);
                     Some(FilterOp::ColorMatrix(matrix))
                 }
                 (_, _, _) => None,
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -6109,29 +6109,28 @@ void nsLayoutUtils::PaintTextShadow(
     nsContextBoxBlur contextBoxBlur;
 
     nscolor shadowColor = shadowDetails->mColor.CalcColor(aForegroundColor);
 
     // Webrender just needs the shadow details
     if (auto* textDrawer = aContext->GetTextDrawer()) {
       wr::Shadow wrShadow;
 
-      // Gecko already inflates the bounding rect of text shadows,
-      // so tell WR not to inflate again.
-      wrShadow.should_inflate = false;
-
       wrShadow.offset = {
           presCtx->AppUnitsToFloatDevPixels(shadowDetails->mXOffset),
           presCtx->AppUnitsToFloatDevPixels(shadowDetails->mYOffset)};
 
       wrShadow.blur_radius =
           presCtx->AppUnitsToFloatDevPixels(shadowDetails->mRadius);
       wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
 
-      textDrawer->AppendShadow(wrShadow);
+      // Gecko already inflates the bounding rect of text shadows,
+      // so tell WR not to inflate again.
+      bool inflate = false;
+      textDrawer->AppendShadow(wrShadow, inflate);
       continue;
     }
 
     gfxContext* shadowContext = contextBoxBlur.Init(
         shadowRect, 0, blurRadius, presCtx->AppUnitsPerDevPixel(), aDestCtx,
         aDirtyRect, nullptr,
         nsContextBoxBlur::DISABLE_HARDWARE_ACCELERATION_BLUR);
     if (!shadowContext) continue;
--- a/layout/generic/TextDrawTarget.h
+++ b/layout/generic/TextDrawTarget.h
@@ -223,18 +223,19 @@ class TextDrawTarget : public DrawTarget
     rect = rect.Intersect(mClipStack.LastElement());
     mClipStack.AppendElement(rect);
   }
 
   void PopClip() override { mClipStack.RemoveLastElement(); }
 
   IntSize GetSize() const override { return mSize; }
 
-  void AppendShadow(const wr::Shadow& aShadow) {
-    mBuilder.PushShadow(mBoundsRect, ClipRect(), mBackfaceVisible, aShadow);
+  void AppendShadow(const wr::Shadow& aShadow, bool aInflate) {
+    mBuilder.PushShadow(mBoundsRect, ClipRect(), mBackfaceVisible, aShadow,
+                        aInflate);
     mHasShadows = true;
   }
 
   void TerminateShadows() {
     if (mHasShadows) {
       mBuilder.PopAllShadows();
       mHasShadows = false;
     }
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -5854,28 +5854,25 @@ void nsTextFrame::PaintOneShadow(const P
   nscoord blurRadius = std::max(aShadowDetails->mRadius, 0);
 
   nscolor shadowColor =
       aShadowDetails->mColor.CalcColor(aParams.foregroundColor);
 
   if (auto* textDrawer = aParams.context->GetTextDrawer()) {
     wr::Shadow wrShadow;
 
-    // Gecko already inflates the bounding rect of text shadows,
-    // so tell WR not to inflate again.
-    wrShadow.should_inflate = true;
-
     wrShadow.offset = {
         PresContext()->AppUnitsToFloatDevPixels(aShadowDetails->mXOffset),
         PresContext()->AppUnitsToFloatDevPixels(aShadowDetails->mYOffset)};
 
     wrShadow.blur_radius = PresContext()->AppUnitsToFloatDevPixels(blurRadius);
     wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
 
-    textDrawer->AppendShadow(wrShadow);
+    bool inflate = true;
+    textDrawer->AppendShadow(wrShadow, inflate);
     return;
   }
 
   // This rect is the box which is equivalent to where the shadow will be
   // painted. The origin of aBoundingBox is the text baseline left, so we must
   // translate it by that much in order to make the origin the top-left corner
   // of the text bounding box. Note that aLeftSideOffset is line-left, so
   // actually means top offset in vertical writing modes.
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -9946,28 +9946,25 @@ bool nsDisplayFilters::CreateWebRenderCS
               "Exactly one drop shadow should have been "
               "parsed.");
           return false;
         }
 
         nsCSSShadowItem* shadow = shadows->ShadowAt(0);
         nscolor color = shadow->mColor.CalcColor(mFrame);
 
-        auto filterOp = wr::FilterOp::DropShadow(
-            {
-                NSAppUnitsToFloatPixels(shadow->mXOffset, appUnitsPerDevPixel),
-                NSAppUnitsToFloatPixels(shadow->mYOffset, appUnitsPerDevPixel),
-            },
-            NSAppUnitsToFloatPixels(shadow->mRadius, appUnitsPerDevPixel),
-            {
-                NS_GET_R(color) / 255.0f,
-                NS_GET_G(color) / 255.0f,
-                NS_GET_B(color) / 255.0f,
-                NS_GET_A(color) / 255.0f,
-            });
+        wr::Shadow wrShadow;
+        wrShadow.offset = {
+          NSAppUnitsToFloatPixels(shadow->mXOffset, appUnitsPerDevPixel),
+          NSAppUnitsToFloatPixels(shadow->mYOffset, appUnitsPerDevPixel)};
+        wrShadow.blur_radius =
+          NSAppUnitsToFloatPixels(shadow->mRadius, appUnitsPerDevPixel);
+        wrShadow.color = {NS_GET_R(color) / 255.0f, NS_GET_G(color) / 255.0f,
+                          NS_GET_B(color) / 255.0f, NS_GET_A(color) / 255.0f};
+        auto filterOp = wr::FilterOp::DropShadow(wrShadow);
 
         wrFilters.filters.AppendElement(filterOp);
         break;
       }
       default:
         return false;
     }
   }
--- a/layout/svg/nsFilterInstance.cpp
+++ b/layout/svg/nsFilterInstance.cpp
@@ -259,27 +259,27 @@ bool nsFilterInstance::BuildWebRenderFil
 
       const DropShadowAttributes& shadow = attr.as<DropShadowAttributes>();
 
       const Size& stdDev = shadow.mStdDeviation;
       if (stdDev.width != stdDev.height) {
         return false;
       }
 
-      wr::LayoutVector2D offset = {(float)shadow.mOffset.x,
-                                   (float)shadow.mOffset.y};
-      float radius = stdDev.width;
       Color color = shadow.mColor;
       if (!primNeedsSrgb) {
         color = Color(gsRGBToLinearRGBMap[uint8_t(color.r * 255)],
                       gsRGBToLinearRGBMap[uint8_t(color.g * 255)],
                       gsRGBToLinearRGBMap[uint8_t(color.b * 255)], color.a);
       }
-      wr::FilterOp filterOp = wr::FilterOp::DropShadow(
-          offset, radius, wr::ToColorF(ToDeviceColor(color)));
+      wr::Shadow wrShadow;
+      wrShadow.offset = {(float)shadow.mOffset.x, (float)shadow.mOffset.y};
+      wrShadow.color = wr::ToColorF(ToDeviceColor(color));
+      wrShadow.blur_radius = stdDev.width;
+      wr::FilterOp filterOp = wr::FilterOp::DropShadow(wrShadow);
 
       aWrFilters.filters.AppendElement(filterOp);
     } else if (attr.is<ComponentTransferAttributes>()) {
       const ComponentTransferAttributes& attributes =
           attr.as<ComponentTransferAttributes>();
 
       size_t numValues =
           attributes.mValues[0].Length() + attributes.mValues[1].Length() +