diff --git a/webrender/src/display_list_flattener.rs b/webrender/src/display_list_flattener.rs
index 2ca5d136cb..be18249508 100644
--- a/webrender/src/display_list_flattener.rs
+++ b/webrender/src/display_list_flattener.rs
@@ -34,6 +34,7 @@ use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::DocumentResources;
 use spatial_node::{SpatialNodeType, StickyFrameInfo};
 use std::{f32, mem};
+use std::collections::vec_deque::VecDeque;
 use tiling::{CompositeOps};
 use util::{MaxRect, RectHelpers};
 
@@ -132,8 +133,8 @@ pub struct DisplayListFlattener<'a> {
     /// A stack of stacking context properties.
     sc_stack: Vec<FlattenedStackingContext>,
 
-    /// A stack of the currently active shadows
-    shadow_stack: Vec<(Shadow, PrimitiveIndex)>,
+    /// Maintains state for any currently active shadows
+    pending_shadow_items: VecDeque<ShadowItem>,
 
     /// The stack keeping track of the root clip chains associated with pipelines.
     pipeline_clip_chain_stack: Vec<ClipChainId>,
@@ -188,7 +189,7 @@ impl<'a> DisplayListFlattener<'a> {
             output_pipelines,
             id_to_index_mapper: ClipIdToIndexMapper::default(),
             hit_testing_runs: Vec::new(),
-            shadow_stack: Vec::new(),
+            pending_shadow_items: VecDeque::new(),
             sc_stack: Vec::new(),
             pipeline_clip_chain_stack: vec![ClipChainId::NONE],
             prim_store: PrimitiveStore::new(),
@@ -881,62 +882,37 @@ impl<'a> DisplayListFlattener<'a> {
         clip_items: Vec<ClipItemKey>,
         container: PrimitiveContainer,
     ) {
-        if !self.shadow_stack.is_empty() {
-            // TODO(gw): Restructure this so we don't need to move the shadow
-            //           stack out (borrowck due to create_primitive below).
-            let shadow_stack = mem::replace(&mut self.shadow_stack, Vec::new());
-            for &(ref shadow, shadow_pic_prim_index) in &shadow_stack {
-                // Offset the local rect and clip rect by the shadow offset.
-                let mut info = info.clone();
-                info.rect = info.rect.translate(&shadow.offset);
-                info.clip_rect = info.clip_rect.translate(&shadow.offset);
-
-                // Offset any local clip sources by the shadow offset.
-                let clip_items: Vec<ClipItemKey> = clip_items
-                    .iter()
-                    .map(|cs| cs.offset(&shadow.offset))
-                    .collect();
+        // If a shadow context is not active, then add the primitive
+        // directly to the parent picture.
+        if self.pending_shadow_items.is_empty() {
+            if container.is_visible() {
                 let clip_chain_id = self.build_clip_chain(
                     clip_items,
                     clip_and_scroll.spatial_node_index,
                     clip_and_scroll.clip_chain_id,
                 );
-
-                // Construct and add a primitive for the given shadow.
-                let shadow_prim_index = self.create_primitive(
-                    &info,
+                let prim_index = self.create_primitive(
+                    info,
                     clip_chain_id,
                     clip_and_scroll.spatial_node_index,
-                    container.create_shadow(shadow),
+                    container,
                 );
-
-                // Add the new primitive to the shadow picture.
-                let shadow_pic = self.prim_store.get_pic_mut(shadow_pic_prim_index);
-                shadow_pic.add_primitive(shadow_prim_index);
+                if cfg!(debug_assertions) && ChasePrimitive::LocalRect(info.rect) == self.config.chase_primitive {
+                    println!("Chasing {:?} by local rect", prim_index);
+                    self.prim_store.chase_id = Some(prim_index);
+                }
+                self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
+                self.add_primitive_to_draw_list(prim_index);
             }
-            self.shadow_stack = shadow_stack;
-        }
-
-        if container.is_visible() {
-            let clip_chain_id = self.build_clip_chain(
+        } else {
+            // There is an active shadow context. Store as a pending primitive
+            // for processing during pop_all_shadows.
+            self.pending_shadow_items.push_back(ShadowItem::Primitive(PendingPrimitive {
+                clip_and_scroll,
+                info: *info,
                 clip_items,
-                clip_and_scroll.spatial_node_index,
-                clip_and_scroll.clip_chain_id,
-            );
-            let prim_index = self.create_primitive(
-                info,
-                clip_chain_id,
-                clip_and_scroll.spatial_node_index,
                 container,
-            );
-            if cfg!(debug_assertions) && ChasePrimitive::LocalRect(info.rect) == self.config.chase_primitive {
-                println!("Chasing {:?} by local rect", prim_index);
-                self.prim_store.chase_id = Some(prim_index);
-            }
-            self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
-            self.add_primitive_to_draw_list(
-                prim_index,
-            );
+            }));
         }
     }
 
@@ -1207,8 +1183,8 @@ impl<'a> DisplayListFlattener<'a> {
         }
 
         assert!(
-            self.shadow_stack.is_empty(),
-            "Found unpopped text shadows when popping stacking context!"
+            self.pending_shadow_items.is_empty(),
+            "Found unpopped shadows when popping stacking context!"
         );
     }
 
@@ -1400,69 +1376,156 @@ impl<'a> DisplayListFlattener<'a> {
         shadow: Shadow,
         clip_and_scroll: ScrollNodeAndClipChain,
     ) {
+        // 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,
+        }));
+    }
+
+    pub fn pop_all_shadows(&mut self) {
+        assert!(!self.pending_shadow_items.is_empty(), "popped shadows, but none were present");
+
         let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
         let max_clip = LayoutRect::max_rect();
+        let mut items = mem::replace(&mut self.pending_shadow_items, VecDeque::new());
 
-        // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
-        // "the image that would be generated by applying to the shadow a
-        // Gaussian blur with a standard deviation equal to half the blur radius."
-        let std_deviation = shadow.blur_radius * 0.5;
-
-        // If the shadow has no blur, any elements will get directly rendered
-        // into the parent picture surface, instead of allocating and drawing
-        // into an intermediate surface. In this case, we will need to apply
-        // the local clip rect to primitives.
-        let is_passthrough = shadow.blur_radius == 0.0;
-
-        // shadows always rasterize in local space.
-        // TODO(gw): expose API for clients to specify a raster scale
-        let raster_space = if is_passthrough {
-            let parent_pic_prim_index = self.sc_stack.last().unwrap().leaf_prim_index;
-            self.prim_store
-                .get_pic(parent_pic_prim_index)
-                .requested_raster_space
-        } else {
-            RasterSpace::Local(1.0)
-        };
+        //
+        // The pending_shadow_items queue contains a list of shadows and primitives
+        // that were pushed during the active shadow context. To process these, we:
+        //
+        // Iterate the list, popping an item from the front each iteration.
+        //
+        // If the item is a shadow:
+        //      - Create a shadow picture primitive.
+        //      - Add *any* primitives that remain in the item list to this shadow.
+        // If the item is a primitive:
+        //      - Add that primitive as a normal item (if alpha > 0)
+        //
 
-        // 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 shadow_pic = PicturePrimitive::new_image(
-            self.picture_id_generator.next(),
-            Some(PictureCompositeMode::Filter(blur_filter)),
-            false,
-            pipeline_id,
-            None,
-            is_passthrough,
-            raster_space,
-        );
+        while let Some(item) = items.pop_front() {
+            match item {
+                ShadowItem::Shadow(pending_shadow) => {
+                    // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
+                    // "the image that would be generated by applying to the shadow a
+                    // Gaussian blur with a standard deviation equal to half the blur radius."
+                    let std_deviation = pending_shadow.shadow.blur_radius * 0.5;
+
+                    // If the shadow has no blur, any elements will get directly rendered
+                    // into the parent picture surface, instead of allocating and drawing
+                    // into an intermediate surface. In this case, we will need to apply
+                    // the local clip rect to primitives.
+                    let is_passthrough = pending_shadow.shadow.blur_radius == 0.0;
+
+                    // shadows always rasterize in local space.
+                    // TODO(gw): expose API for clients to specify a raster scale
+                    let raster_space = if is_passthrough {
+                        let parent_pic_prim_index = self.sc_stack.last().unwrap().leaf_prim_index;
+                        self.prim_store
+                            .get_pic(parent_pic_prim_index)
+                            .requested_raster_space
+                    } else {
+                        RasterSpace::Local(1.0)
+                    };
+
+                    // 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 shadow_pic = PicturePrimitive::new_image(
+                        self.picture_id_generator.next(),
+                        Some(PictureCompositeMode::Filter(blur_filter)),
+                        false,
+                        pipeline_id,
+                        None,
+                        is_passthrough,
+                        raster_space,
+                    );
 
-        // Create the primitive to draw the shadow picture into the scene.
-        let shadow_prim = BrushPrimitive::new_picture(shadow_pic);
-        let shadow_prim_index = self.prim_store.add_primitive(
-            &LayoutRect::zero(),
-            &max_clip,
-            true,
-            clip_and_scroll.clip_chain_id,
-            clip_and_scroll.spatial_node_index,
-            None,
-            PrimitiveContainer::Brush(shadow_prim),
-        );
+                    // Add any primitives that come after this shadow in the item
+                    // list to this shadow.
+                    for item in &items {
+                        if let ShadowItem::Primitive(ref pending_primitive) = item {
+                            // 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);
+
+                            // Offset any local clip sources by the shadow offset.
+                            let clip_items: Vec<ClipItemKey> = pending_primitive
+                                .clip_items
+                                .iter()
+                                .map(|cs| cs.offset(&pending_shadow.shadow.offset))
+                                .collect();
+                            let clip_chain_id = self.build_clip_chain(
+                                clip_items,
+                                pending_primitive.clip_and_scroll.spatial_node_index,
+                                pending_primitive.clip_and_scroll.clip_chain_id,
+                            );
 
-        // Add the shadow primitive. This must be done before pushing this
-        // picture on to the shadow stack, to avoid infinite recursion!
-        self.add_primitive_to_draw_list(
-            shadow_prim_index,
-        );
-        self.shadow_stack.push((shadow, shadow_prim_index));
-    }
+                            // Construct and add a primitive for the given shadow.
+                            let shadow_prim_index = self.create_primitive(
+                                &info,
+                                clip_chain_id,
+                                pending_primitive.clip_and_scroll.spatial_node_index,
+                                pending_primitive.container.create_shadow(&pending_shadow.shadow),
+                            );
 
-    pub fn pop_all_shadows(&mut self) {
-        assert!(self.shadow_stack.len() > 0, "popped shadows, but none were present");
-        self.shadow_stack.clear();
+                            // Add the new primitive to the shadow picture.
+                            shadow_pic.add_primitive(shadow_prim_index);
+                        }
+                    }
+
+                    // No point in adding a shadow here if there were no primitives
+                    // added to the shadow.
+                    if !shadow_pic.is_empty() {
+                        // Create the primitive to draw the shadow picture into the scene.
+                        let shadow_prim = BrushPrimitive::new_picture(shadow_pic);
+                        let shadow_prim_index = self.prim_store.add_primitive(
+                            &LayoutRect::zero(),
+                            &max_clip,
+                            true,
+                            pending_shadow.clip_and_scroll.clip_chain_id,
+                            pending_shadow.clip_and_scroll.spatial_node_index,
+                            None,
+                            PrimitiveContainer::Brush(shadow_prim),
+                        );
+
+                        // Add the shadow primitive. This must be done before pushing this
+                        // picture on to the shadow stack, to avoid infinite recursion!
+                        self.add_primitive_to_draw_list(shadow_prim_index);
+                    }
+                }
+                ShadowItem::Primitive(pending_primitive) => {
+                    // For a normal primitive, if it has alpha > 0, then we add this
+                    // as a normal primitive to the parent picture.
+                    if pending_primitive.container.is_visible() {
+                        let clip_chain_id = self.build_clip_chain(
+                            pending_primitive.clip_items,
+                            pending_primitive.clip_and_scroll.spatial_node_index,
+                            pending_primitive.clip_and_scroll.clip_chain_id,
+                        );
+                        let prim_index = self.create_primitive(
+                            &pending_primitive.info,
+                            clip_chain_id,
+                            pending_primitive.clip_and_scroll.spatial_node_index,
+                            pending_primitive.container,
+                        );
+                        if cfg!(debug_assertions) && ChasePrimitive::LocalRect(pending_primitive.info.rect) == self.config.chase_primitive {
+                            println!("Chasing {:?} by local rect", prim_index);
+                            self.prim_store.chase_id = Some(prim_index);
+                        }
+                        self.add_primitive_to_hit_testing_list(&pending_primitive.info, pending_primitive.clip_and_scroll);
+                        self.add_primitive_to_draw_list(prim_index);
+                    }
+                }
+            }
+        }
+
+        debug_assert!(items.is_empty());
+        self.pending_shadow_items = items;
     }
 
     pub fn add_solid_rectangle(
@@ -2066,3 +2129,25 @@ struct FlattenedStackingContext {
     participating_in_3d_context: bool,
     has_mix_blend_mode: bool,
 }
+
+/// A primitive that is added while a shadow context is
+/// active is stored as a pending primitive and only
+/// added to pictures during pop_all_shadows.
+struct PendingPrimitive {
+    clip_and_scroll: ScrollNodeAndClipChain,
+    info: LayoutPrimitiveInfo,
+    clip_items: Vec<ClipItemKey>,
+    container: PrimitiveContainer,
+}
+
+/// As shadows are pushed, they are stored as pending
+/// shadows, and handled at once during pop_all_shadows.
+struct PendingShadow {
+    shadow: Shadow,
+    clip_and_scroll: ScrollNodeAndClipChain,
+}
+
+enum ShadowItem {
+    Shadow(PendingShadow),
+    Primitive(PendingPrimitive),
+}
diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs
index e5a318e4ff..293448c33b 100644
--- a/webrender/src/picture.rs
+++ b/webrender/src/picture.rs
@@ -378,6 +378,11 @@ impl PicturePrimitive {
         Some((context, state, instances))
     }
 
+    /// Return true if this picture doesn't contain any primitives.
+    pub fn is_empty(&self) -> bool {
+        self.prim_instances.is_empty()
+    }
+
     pub fn add_primitive(
         &mut self,
         prim_index: PrimitiveIndex,