Bug 1524385 - Set the current clip chain on the stacking context item. r=kvark
authorGlenn Watson <github@intuitionlibrary.com>
Wed, 06 Feb 2019 04:35:37 +0000
changeset 457360 2853480ed90d0a995593e66fcd68a8bff1ad8d96
parent 457359 8c235a9b55a2b2d89115a7cc0f56a58c8883a980
child 457380 e697db5f161b25cdaa4ceb43e918ada09e997acb
child 457381 89c4f87550e8c8c1f456fe51d3adf79d10db2cea
push id35506
push useropoprus@mozilla.com
push dateWed, 06 Feb 2019 09:47:29 +0000
treeherdermozilla-central@2853480ed90d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskvark
bugs1524385
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1524385 - Set the current clip chain on the stacking context item. r=kvark Differential Revision: https://phabricator.services.mozilla.com/D18527
gfx/layers/wr/ClipManager.cpp
gfx/layers/wr/StackingContextHelper.cpp
gfx/layers/wr/StackingContextHelper.h
gfx/layers/wr/WebRenderCommandBuilder.cpp
gfx/webrender_bindings/WebRenderAPI.cpp
gfx/webrender_bindings/WebRenderAPI.h
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/clip.rs
gfx/wr/webrender/src/display_list_flattener.rs
gfx/wr/webrender/src/frame_builder.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/prim_store/mod.rs
gfx/wr/webrender/src/prim_store/picture.rs
gfx/wr/wrench/reftests/clip/reftest.list
gfx/wr/wrench/reftests/transforms/perspective-clip.png
layout/painting/nsDisplayList.cpp
layout/reftests/async-scrolling/reftest.list
layout/reftests/bugs/reftest.list
layout/reftests/table-background/reftest.list
layout/reftests/transform-3d/reftest.list
--- a/gfx/layers/wr/ClipManager.cpp
+++ b/gfx/layers/wr/ClipManager.cpp
@@ -351,32 +351,21 @@ Maybe<wr::WrClipChainId> ClipManager::De
     spaceAndClip->space = SpatialIdAfterOverride(spaceAndClip->space);
     wr::WrClipId clipId = mBuilder->DefineClip(
         spaceAndClip, wr::ToRoundedLayoutRect(clip), &wrRoundedRects);
     clipIds.AppendElement(clipId);
     cache[chain] = clipId;
     CLIP_LOG("cache[%p] <= %zu\n", chain, clipId.id);
   }
 
-  // Now find the parent display item's clipchain id
-  Maybe<wr::WrClipChainId> parentChainId;
-  if (!mItemClipStack.empty()) {
-    parentChainId = mItemClipStack.top().mClipChainId;
+  if (clipIds.IsEmpty()) {
+    return Nothing();
   }
 
-  // And define the current display item's clipchain using the clips and the
-  // parent. If the current item has no clips of its own, just use the parent
-  // item's clipchain.
-  Maybe<wr::WrClipChainId> chainId;
-  if (clipIds.Length() > 0) {
-    chainId = Some(mBuilder->DefineClipChain(parentChainId, clipIds));
-  } else {
-    chainId = parentChainId;
-  }
-  return chainId;
+  return Some(mBuilder->DefineClipChain(clipIds));
 }
 
 ClipManager::~ClipManager() {
   MOZ_ASSERT(!mBuilder);
   MOZ_ASSERT(mCacheStack.empty());
   MOZ_ASSERT(mItemClipStack.empty());
 }
 
--- a/gfx/layers/wr/StackingContextHelper.cpp
+++ b/gfx/layers/wr/StackingContextHelper.cpp
@@ -58,16 +58,17 @@ StackingContextHelper::StackingContextHe
     mScale = aParentSC.mScale;
   }
 
   auto rasterSpace =
       mRasterizeLocally
           ? wr::RasterSpace::Local(std::max(mScale.width, mScale.height))
           : wr::RasterSpace::Screen();
 
+  MOZ_ASSERT(!aParams.clip.IsNone());
   mReferenceFrameId = mBuilder->PushStackingContext(
       aParams, wr::ToLayoutRect(aBounds), rasterSpace);
 
   if (mReferenceFrameId) {
     mSpaceAndClipChainHelper.emplace(aBuilder, mReferenceFrameId.ref());
   }
 
   mAffectsClipPositioning =
--- a/gfx/layers/wr/StackingContextHelper.h
+++ b/gfx/layers/wr/StackingContextHelper.h
@@ -22,22 +22,23 @@ struct ActiveScrolledRoot;
 namespace layers {
 
 /**
  * This is a helper class that pushes/pops a stacking context, and manages
  * some of the coordinate space transformations needed.
  */
 class MOZ_RAII StackingContextHelper {
  public:
-  StackingContextHelper(
-      const StackingContextHelper& aParentSC, const ActiveScrolledRoot* aAsr,
-      nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem,
-      wr::DisplayListBuilder& aBuilder,
-      const wr::StackingContextParams& aParams = wr::StackingContextParams(),
-      const LayoutDeviceRect& aBounds = LayoutDeviceRect());
+  StackingContextHelper(const StackingContextHelper& aParentSC,
+                        const ActiveScrolledRoot* aAsr,
+                        nsIFrame* aContainerFrame,
+                        nsDisplayItem* aContainerItem,
+                        wr::DisplayListBuilder& aBuilder,
+                        const wr::StackingContextParams& aParams,
+                        const LayoutDeviceRect& aBounds = LayoutDeviceRect());
 
   // This version of the constructor should only be used at the root level
   // of the tree, so that we have a StackingContextHelper to pass down into
   // the RenderLayer traversal, but don't actually want it to push a stacking
   // context on the display list builder.
   StackingContextHelper();
 
   // Pops the stacking context, if one was pushed during the constructor.
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -1437,16 +1437,18 @@ void WebRenderCommandBuilder::BuildWebRe
         aDisplayListBuilder->RootReferenceFrame()->PresContext();
     bool isTopLevelContent =
         presContext->Document()->IsTopLevelContentDocument();
 
     wr::StackingContextParams params;
     params.mFilters = std::move(aFilters);
     params.animation = mZoomProp.ptrOr(nullptr);
     params.cache_tiles = isTopLevelContent;
+    params.clip =
+        wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
 
     StackingContextHelper pageRootSc(sc, nullptr, nullptr, nullptr, aBuilder,
                                      params);
     if (ShouldDumpDisplayList(aDisplayListBuilder)) {
       mBuilderDumpIndex =
           aBuilder.Dump(mDumpIndent + 1, Some(mBuilderDumpIndex), Nothing());
     }
     CreateWebRenderCommandsFromDisplayList(aDisplayList, nullptr,
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -696,23 +696,20 @@ Maybe<wr::WrSpatialId> DisplayListBuilde
 }
 
 void DisplayListBuilder::PopStackingContext(bool aIsReferenceFrame) {
   WRDL_LOG("PopStackingContext\n", mWrState);
   wr_dp_pop_stacking_context(mWrState, aIsReferenceFrame);
 }
 
 wr::WrClipChainId DisplayListBuilder::DefineClipChain(
-    const Maybe<wr::WrClipChainId>& aParent,
     const nsTArray<wr::WrClipId>& aClips) {
-  uint64_t clipchainId =
-      wr_dp_define_clipchain(mWrState, aParent ? &(aParent->id) : nullptr,
-                             aClips.Elements(), aClips.Length());
-  WRDL_LOG("DefineClipChain id=%" PRIu64 " p=%s clips=%zu\n", mWrState,
-           clipchainId, aParent ? Stringify(aParent->id).c_str() : "(nil)",
+  uint64_t clipchainId = wr_dp_define_clipchain(
+      mWrState, nullptr, aClips.Elements(), aClips.Length());
+  WRDL_LOG("DefineClipChain id=%" PRIu64 " clips=%zu\n", mWrState, clipchainId,
            aClips.Length());
   return wr::WrClipChainId{clipchainId};
 }
 
 wr::WrClipId DisplayListBuilder::DefineClip(
     const Maybe<wr::WrSpaceAndClip>& aParent, const wr::LayoutRect& aClipRect,
     const nsTArray<wr::ComplexClipRegion>* aComplex,
     const wr::WrImageMask* aMask) {
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -357,18 +357,17 @@ class DisplayListBuilder {
   void Finalize(wr::LayoutSize& aOutContentSize,
                 wr::BuiltDisplayList& aOutDisplayList);
 
   Maybe<wr::WrSpatialId> PushStackingContext(
       const StackingContextParams& aParams, const wr::LayoutRect& aBounds,
       const wr::RasterSpace& aRasterSpace);
   void PopStackingContext(bool aIsReferenceFrame);
 
-  wr::WrClipChainId DefineClipChain(const Maybe<wr::WrClipChainId>& aParent,
-                                    const nsTArray<wr::WrClipId>& aClips);
+  wr::WrClipChainId DefineClipChain(const nsTArray<wr::WrClipId>& aClips);
 
   wr::WrClipId DefineClip(
       const Maybe<wr::WrSpaceAndClip>& aParent, const wr::LayoutRect& aClipRect,
       const nsTArray<wr::ComplexClipRegion>* aComplex = nullptr,
       const wr::WrImageMask* aMask = nullptr);
 
   wr::WrSpatialId DefineStickyFrame(const wr::LayoutRect& aContentRect,
                                     const float* aTopMargin,
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -1463,17 +1463,17 @@ impl AlphaBatchBuilder {
 
                                 self.current_batch_list().push_single_instance(
                                     key,
                                     bounding_rect,
                                     z_id,
                                     PrimitiveInstanceData::from(instance),
                                 );
                             }
-                            PictureCompositeMode::Blit => {
+                            PictureCompositeMode::Blit(_) => {
                                 let surface = ctx.surfaces[raster_config.surface_index.0]
                                     .surface
                                     .as_ref()
                                     .expect("bug: surface must be allocated by now");
                                 let cache_task_id = surface.resolve_render_task_id();
                                 let kind = BatchKind::Brush(
                                     BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
                                 );
--- a/gfx/wr/webrender/src/clip.rs
+++ b/gfx/wr/webrender/src/clip.rs
@@ -11,19 +11,18 @@ use app_units::Au;
 use border::{ensure_no_corner_overlap, BorderRadiusAu};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use ellipse::Ellipse;
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use gpu_types::{BoxShadowStretchMode};
 use image::{self, Repetition};
 use intern;
-use internal_types::FastHashSet;
 use prim_store::{ClipData, ImageMaskData, SpaceMapper, VisibleMaskImageTile};
-use prim_store::{PointKey, PrimitiveInstance, SizeKey, RectangleKey};
+use prim_store::{PointKey, SizeKey, RectangleKey};
 use render_task::to_cache_size;
 use resource_cache::{ImageRequest, ResourceCache};
 use std::{cmp, u32};
 use util::{extract_inner_rect_safe, project_rect, ScaleOffset};
 
 /*
 
  Module Overview
@@ -431,17 +430,16 @@ impl ClipNode {
 }
 
 /// The main clipping public interface that other modules access.
 #[derive(MallocSizeOf)]
 pub struct ClipStore {
     pub clip_chain_nodes: Vec<ClipChainNode>,
     clip_node_instances: Vec<ClipNodeInstance>,
     clip_node_info: Vec<ClipNodeInfo>,
-    clip_node_collectors: Vec<ClipNodeCollector>,
 }
 
 // A clip chain instance is what gets built for a given clip
 // chain id + local primitive region + positioning node.
 #[derive(Debug)]
 pub struct ClipChainInstance {
     pub clips_range: ClipNodeRange,
     // Combined clip rect for clips that are in the
@@ -466,23 +464,68 @@ impl ClipChainInstance {
             local_clip_rect: LayoutRect::zero(),
             has_non_local_clips: false,
             needs_mask: false,
             pic_clip_rect: PictureRect::zero(),
         }
     }
 }
 
+/// Maintains a stack of clip chain ids that are currently active,
+/// when a clip exists on a picture that has no surface, and is passed
+/// on down to the child primitive(s).
+pub struct ClipChainStack {
+    // TODO(gw): Consider using SmallVec, or recycling the clip stacks here.
+    /// A stack of clip chain lists. Each time a new surface is pushed,
+    /// a new entry is added to the main stack. Each time a new picture
+    /// without surface is pushed, it adds the picture clip chain to the
+    /// current stack list.
+    pub stack: Vec<Vec<ClipChainId>>,
+}
+
+impl ClipChainStack {
+    pub fn new() -> Self {
+        ClipChainStack {
+            stack: vec![vec![]],
+        }
+    }
+
+    /// Push a clip chain root onto the currently active list.
+    pub fn push_clip(&mut self, clip_chain_id: ClipChainId) {
+        self.stack.last_mut().unwrap().push(clip_chain_id);
+    }
+
+    /// Pop a clip chain root from the currently active list.
+    pub fn pop_clip(&mut self) {
+        self.stack.last_mut().unwrap().pop().unwrap();
+    }
+
+    /// When a surface is created, it takes all clips and establishes a new
+    /// stack of clips to be propagated.
+    pub fn push_surface(&mut self) {
+        self.stack.push(Vec::new());
+    }
+
+    /// Pop a surface from the clip chain stack
+    pub fn pop_surface(&mut self) {
+        self.stack.pop().unwrap();
+    }
+
+    /// Get the list of currently active clip chains
+    pub fn current_clips(&self) -> &[ClipChainId] {
+        self.stack.last().unwrap()
+    }
+}
+
 impl ClipStore {
     pub fn new() -> Self {
         ClipStore {
             clip_chain_nodes: Vec::new(),
             clip_node_instances: Vec::new(),
             clip_node_info: Vec::new(),
-            clip_node_collectors: Vec::new(),
         }
     }
 
     pub fn get_clip_chain(&self, clip_chain_id: ClipChainId) -> &ClipChainNode {
         &self.clip_chain_nodes[clip_chain_id.0 as usize]
     }
 
     pub fn add_clip_chain_node(
@@ -505,113 +548,59 @@ impl ClipStore {
     pub fn get_instance_from_range(
         &self,
         node_range: &ClipNodeRange,
         index: u32,
     ) -> &ClipNodeInstance {
         &self.clip_node_instances[(node_range.first + index) as usize]
     }
 
-    /// Notify the clip store that a new raster root has been created.
-    ///
-    /// This means any clips from an earlier root should be collected rather than
-    /// applied on the primitive itself.
-    ///
-    /// This is sound because raster roots are necessarily reference frames,
-    /// which establish fixed-positioning containing blocks in CSS.
-    pub fn push_raster_root(&mut self, spatial_node_index: SpatialNodeIndex) {
-        self.clip_node_collectors.push(
-            ClipNodeCollector::new(spatial_node_index),
-        );
-    }
-
-    /// Mark the end of a raster root.
-    pub fn pop_raster_root(&mut self) -> ClipNodeCollector {
-        self.clip_node_collectors.pop().unwrap()
-    }
-
     // The main interface other code uses. Given a local primitive, positioning
     // information, and a clip chain id, build an optimized clip chain instance.
     pub fn build_clip_chain_instance(
         &mut self,
-        prim_instance: &PrimitiveInstance,
+        clip_chains: &[ClipChainId],
         local_prim_rect: LayoutRect,
         local_prim_clip_rect: LayoutRect,
         spatial_node_index: SpatialNodeIndex,
         prim_to_pic_mapper: &SpaceMapper<LayoutPixel, PicturePixel>,
         pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
         clip_scroll_tree: &ClipScrollTree,
         gpu_cache: &mut GpuCache,
         resource_cache: &mut ResourceCache,
         device_pixel_scale: DevicePixelScale,
         world_rect: &WorldRect,
-        clip_node_collector: Option<&ClipNodeCollector>,
         clip_data_store: &mut ClipDataStore,
     ) -> Option<ClipChainInstance> {
         let mut local_clip_rect = local_prim_clip_rect;
 
         // Walk the clip chain to build local rects, and collect the
         // smallest possible local/device clip area.
 
         self.clip_node_info.clear();
-        let mut current_clip_chain_id = prim_instance.clip_chain_id;
 
-        // for each clip chain node
-        while current_clip_chain_id != ClipChainId::NONE {
-            let clip_chain_node = &self.clip_chain_nodes[current_clip_chain_id.0 as usize];
+        for clip_chain_root in clip_chains {
+            let mut current_clip_chain_id = *clip_chain_root;
 
-            // Check if any clip node index should actually be
-            // handled during compositing of a rasterization root.
-            match self.clip_node_collectors.iter_mut().find(|c| {
-                clip_chain_node.spatial_node_index < c.spatial_node_index
-            }) {
-                Some(collector) => {
-                    collector.insert(current_clip_chain_id);
-                }
-                None => {
-                    if prim_instance.is_chased() {
-                        println!("\t\tclip node (from chain) {:?} at {:?}",
-                            clip_chain_node.spatial_node_index, clip_chain_node.local_pos);
-                    }
-                    if !add_clip_node_to_current_chain(
-                        clip_chain_node,
-                        spatial_node_index,
-                        &mut local_clip_rect,
-                        &mut self.clip_node_info,
-                        clip_data_store,
-                        clip_scroll_tree,
-                    ) {
-                        return None;
-                    }
-                }
-            }
-
-            current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
-        }
-
-        // Add any collected clips from primitives that should be
-        // handled as part of this rasterization root.
-        if let Some(clip_node_collector) = clip_node_collector {
-            for clip_chain_id in &clip_node_collector.clips {
-                let clip_chain_node = &self.clip_chain_nodes[clip_chain_id.0 as usize];
-                if prim_instance.is_chased() {
-                    println!("\t\tclip node (from collector) {:?} at {:?}",
-                        clip_chain_node.spatial_node_index, clip_chain_node.local_pos);
-                }
+            // for each clip chain node
+            while current_clip_chain_id != ClipChainId::NONE {
+                let clip_chain_node = &self.clip_chain_nodes[current_clip_chain_id.0 as usize];
 
                 if !add_clip_node_to_current_chain(
                     clip_chain_node,
                     spatial_node_index,
                     &mut local_clip_rect,
                     &mut self.clip_node_info,
                     clip_data_store,
                     clip_scroll_tree,
                 ) {
                     return None;
                 }
+
+                current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
             }
         }
 
         let local_bounding_rect = local_prim_rect.intersection(&local_clip_rect)?;
         let pic_clip_rect = prim_to_pic_mapper.map(&local_bounding_rect)?;
         let world_clip_rect = pic_to_world_mapper.map(&pic_clip_rect)?;
 
         // Now, we've collected all the clip nodes that *potentially* affect this
@@ -667,20 +656,16 @@ impl ClipStore {
                     // Create the clip node instance for this clip node
                     let instance = node_info.create_instance(
                         node,
                         &local_bounding_rect,
                         gpu_cache,
                         resource_cache,
                     );
 
-                    if prim_instance.is_chased() {
-                        println!("\t\tpartial {:?}", node.item);
-                    }
-
                     // As a special case, a partial accept of a clip rect that is
                     // in the same coordinate system as the primitive doesn't need
                     // a clip mask. Instead, it can be handled by the primitive
                     // vertex shader as part of the local clip rect. This is an
                     // important optimization for reducing the number of clip
                     // masks that are allocated on common pages.
                     needs_mask |= match node.item {
                         ClipItem::Rectangle(_, ClipMode::ClipOut) |
@@ -1281,42 +1266,16 @@ pub fn project_inner_rect(
     xs.sort_by(|a, b| a.partial_cmp(b).unwrap_or(cmp::Ordering::Equal));
     ys.sort_by(|a, b| a.partial_cmp(b).unwrap_or(cmp::Ordering::Equal));
     Some(WorldRect::new(
         WorldPoint::new(xs[1], ys[1]),
         WorldSize::new(xs[2] - xs[1], ys[2] - ys[1]),
     ))
 }
 
-// Collects a list of unique clips to be applied to a rasterization
-// root at the end of primitive preparation.
-#[derive(Debug, MallocSizeOf)]
-pub struct ClipNodeCollector {
-    spatial_node_index: SpatialNodeIndex,
-    clips: FastHashSet<ClipChainId>,
-}
-
-impl ClipNodeCollector {
-    pub fn new(
-        spatial_node_index: SpatialNodeIndex,
-    ) -> Self {
-        ClipNodeCollector {
-            spatial_node_index,
-            clips: FastHashSet::default(),
-        }
-    }
-
-    pub fn insert(
-        &mut self,
-        clip_chain_id: ClipChainId,
-    ) {
-        self.clips.insert(clip_chain_id);
-    }
-}
-
 // Add a clip node into the list of clips to be processed
 // for the current clip chain. Returns false if the clip
 // results in the entire primitive being culled out.
 fn add_clip_node_to_current_chain(
     node: &ClipChainNode,
     spatial_node_index: SpatialNodeIndex,
     local_clip_rect: &mut LayoutRect,
     clip_node_info: &mut Vec<ClipNodeInfo>,
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -16,17 +16,18 @@ use app_units::Au;
 use clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore};
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex};
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use intern::{Handle, Internable, InternDebug};
 use internal_types::{FastHashMap, FastHashSet};
-use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureOptions, PrimitiveList, TileCache};
+use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureOptions};
+use picture::{BlitReason, PrimitiveList, TileCache};
 use prim_store::{PrimitiveInstance, PrimitiveKeyKind, PrimitiveSceneData};
 use prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveStore};
 use prim_store::{PrimitiveStoreStats, ScrollNodeAndClipChain, PictureIndex};
 use prim_store::{register_prim_chase_id, get_line_decoration_sizes};
 use prim_store::borders::{ImageBorder, NormalBorderPrim};
 use prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams};
 use prim_store::image::{Image, YuvImage};
 use prim_store::line_dec::{LineDecoration, LineDecorationCacheKey};
@@ -1277,30 +1278,34 @@ impl<'a> DisplayListFlattener<'a> {
         } else {
             Picture3DContext::Out
         };
 
         // Force an intermediate surface if the stacking context
         // has a clip node. In the future, we may decide during
         // prepare step to skip the intermediate surface if the
         // clip node doesn't affect the stacking context rect.
-        let should_isolate = clip_chain_id != ClipChainId::NONE;
+        let blit_reason = if clip_chain_id == ClipChainId::NONE {
+            BlitReason::empty()
+        } else {
+            BlitReason::CLIP
+        };
 
         // Push the SC onto the stack, so we know how to handle things in
         // pop_stacking_context.
         self.sc_stack.push(FlattenedStackingContext {
             primitives: Vec::new(),
             pipeline_id,
             is_backface_visible,
             requested_raster_space,
             spatial_node_index,
             clip_chain_id,
             frame_output_pipeline_id,
             composite_ops,
-            should_isolate,
+            blit_reason,
             transform_style,
             context_3d,
             create_tile_cache,
         });
     }
 
     pub fn pop_stacking_context(&mut self) {
         let mut stacking_context = self.sc_stack.pop().unwrap();
@@ -1350,28 +1355,28 @@ impl<'a> DisplayListFlattener<'a> {
             //           a 3D context, we draw it to an intermediate
             //           surface and apply plane splitting. However,
             //           there is a large optimization opportunity here.
             //           During culling, we can check if there is actually
             //           perspective present, and skip the plane splitting
             //           completely when that is not the case.
             Picture3DContext::In { ancestor_index, .. } => (
                 Picture3DContext::In { root_data: None, ancestor_index },
-                Some(PictureCompositeMode::Blit),
+                Some(PictureCompositeMode::Blit(BlitReason::PRESERVE3D | stacking_context.blit_reason)),
                 None,
             ),
             Picture3DContext::Out => (
                 Picture3DContext::Out,
-                if stacking_context.should_isolate {
-                    // Add a dummy composite filter if the SC has to be isolated.
-                    Some(PictureCompositeMode::Blit)
-                } else {
+                if stacking_context.blit_reason.is_empty() {
                     // By default, this picture will be collapsed into
                     // the owning target.
                     None
+                } else {
+                    // Add a dummy composite filter if the SC has to be isolated.
+                    Some(PictureCompositeMode::Blit(stacking_context.blit_reason))
                 },
                 stacking_context.frame_output_pipeline_id
             ),
         };
 
         // Add picture for this actual stacking context contents to render into.
         let leaf_pic_index = PictureIndex(self.prim_store.pictures
             .alloc()
@@ -1405,18 +1410,16 @@ impl<'a> DisplayListFlattener<'a> {
             stacking_context.spatial_node_index,
             &mut self.interners,
         );
 
         if cur_instance.is_chased() {
             println!("\tis a leaf primitive for a stacking context");
         }
 
-        let mut clip_chain = Some(stacking_context.clip_chain_id);
-
         // If establishing a 3d context, the `cur_instance` represents
         // a picture with all the *trailing* immediate children elements.
         // We append this to the preserve-3D picture set and make a container picture of them.
         if let Picture3DContext::In { root_data: Some(mut prims), ancestor_index } = stacking_context.context_3d {
             prims.push(cur_instance);
 
             // This is the acttual picture representing our 3D hierarchy root.
             current_pic_index = PictureIndex(self.prim_store.pictures
@@ -1437,26 +1440,16 @@ impl<'a> DisplayListFlattener<'a> {
                     ),
                     stacking_context.spatial_node_index,
                     max_clip,
                     None,
                     PictureOptions::default(),
                 ))
             );
 
-            // If this is going to be the last picture for the stacking context,
-            // then put the clip onto the leaf picture, since this one uses
-            // PictureCompositeKey::Identity and won't have a surface to be
-            // clipped.
-            if stacking_context.composite_ops.filters.is_empty() &&
-               stacking_context.composite_ops.mix_blend_mode.is_none() {
-                cur_instance.clip_chain_id = clip_chain.take().unwrap();
-            }
-
-
             cur_instance = create_prim_instance(
                 current_pic_index,
                 PictureCompositeKey::Identity,
                 stacking_context.is_backface_visible,
                 ClipChainId::NONE,
                 stacking_context.spatial_node_index,
                 &mut self.interners,
             );
@@ -1542,17 +1535,17 @@ impl<'a> DisplayListFlattener<'a> {
 
             if cur_instance.is_chased() {
                 println!("\tis a mix-blend picture for a stacking context with {:?}", mix_blend_mode);
             }
         }
 
         // Set the stacking context clip on the outermost picture in the chain,
         // unless we already set it on the leaf picture.
-        cur_instance.clip_chain_id = clip_chain.unwrap_or(ClipChainId::NONE);
+        cur_instance.clip_chain_id = stacking_context.clip_chain_id;
 
         let has_mix_blend_on_secondary_framebuffer =
             stacking_context.composite_ops.mix_blend_mode.is_some() &&
             self.sc_stack.len() > 2;
 
         // The primitive instance for the remainder of flat children of this SC
         // if it's a part of 3D hierarchy but not the root of it.
         let trailing_children_instance = match self.sc_stack.last_mut() {
@@ -1563,17 +1556,17 @@ impl<'a> DisplayListFlattener<'a> {
             // Regular parenting path
             Some(ref mut parent_sc) => {
                 // If we have a mix-blend-mode, and we aren't the primary framebuffer,
                 // the stacking context needs to be isolated to blend correctly as per
                 // the CSS spec.
                 // If not already isolated for some other reason,
                 // make this picture as isolated.
                 if has_mix_blend_on_secondary_framebuffer {
-                    parent_sc.should_isolate = true;
+                    parent_sc.blit_reason |= BlitReason::ISOLATE;
                 }
                 parent_sc.primitives.push(cur_instance);
                 None
             }
             // This must be the root stacking context
             None => {
                 self.root_pic_index = current_pic_index;
                 None
@@ -2596,19 +2589,19 @@ struct FlattenedStackingContext {
     /// If set, this should be provided to caller
     /// as an output texture.
     frame_output_pipeline_id: Option<PipelineId>,
 
     /// The list of filters / mix-blend-mode for this
     /// stacking context.
     composite_ops: CompositeOps,
 
-    /// If true, this stacking context should be
-    /// isolated by forcing an off-screen surface.
-    should_isolate: bool,
+    /// Bitfield of reasons this stacking context needs to
+    /// be an offscreen surface.
+    blit_reason: BlitReason,
 
     /// Pipeline this stacking context belongs to.
     pipeline_id: PipelineId,
 
     /// CSS transform-style property.
     transform_style: TransformStyle,
 
     /// Defines the relationship to a preserve-3D hiearachy.
@@ -2651,17 +2644,17 @@ impl FlattenedStackingContext {
         }
 
         // If different clip chains
         if self.clip_chain_id != parent.clip_chain_id {
             return false;
         }
 
         // If need to isolate in surface due to clipping / mix-blend-mode
-        if self.should_isolate {
+        if !self.blit_reason.is_empty() {
             return false;
         }
 
         // If represents a transform, it may affect backface visibility of children
         if !clip_scroll_tree.node_is_identity(self.spatial_node_index) {
             return false;
         }
 
@@ -2690,17 +2683,17 @@ impl FlattenedStackingContext {
                 ancestor_index,
             },
             Picture3DContext::Out => panic!("Unexpected out of 3D context"),
         };
 
         let pic_index = PictureIndex(prim_store.pictures
             .alloc()
             .init(PicturePrimitive::new_image(
-                Some(PictureCompositeMode::Blit),
+                Some(PictureCompositeMode::Blit(BlitReason::PRESERVE3D)),
                 flat_items_context_3d,
                 self.pipeline_id,
                 None,
                 true,
                 self.requested_raster_space,
                 PrimitiveList::new(
                     mem::replace(&mut self.primitives, Vec::new()),
                     interners,
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -1,16 +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::{ColorF, DeviceIntPoint, DevicePixelScale, LayoutPixel, PicturePixel, RasterPixel};
 use api::{DeviceIntRect, DeviceIntSize, DocumentLayer, FontRenderMode, DebugFlags};
 use api::{LayoutPoint, LayoutRect, LayoutSize, PipelineId, RasterSpace, WorldPoint, WorldRect, WorldPixel};
-use clip::{ClipDataStore, ClipStore};
+use clip::{ClipDataStore, ClipStore, ClipChainStack};
 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
 use gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind, ZBufferIdGenerator};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap, PlaneSplitter};
 use picture::{PictureSurface, PictureUpdateState, SurfaceInfo, ROOT_SURFACE_INDEX, SurfaceIndex};
 use picture::{RetainedTiles, TileCache, DirtyRegion};
@@ -85,16 +85,17 @@ pub struct FrameVisibilityContext<'a> {
 pub struct FrameVisibilityState<'a> {
     pub clip_store: &'a mut ClipStore,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub scratch: &'a mut PrimitiveScratchBuffer,
     pub tile_cache: Option<TileCache>,
     pub retained_tiles: &'a mut RetainedTiles,
     pub data_stores: &'a mut DataStores,
+    pub clip_chain_stack: ClipChainStack,
 }
 
 pub struct FrameBuildingContext<'a> {
     pub device_pixel_scale: DevicePixelScale,
     pub scene_properties: &'a SceneProperties,
     pub pipelines: &'a FastHashMap<PipelineId, Arc<ScenePipeline>>,
     pub screen_world_rect: WorldRect,
     pub clip_scroll_tree: &'a ClipScrollTree,
@@ -107,16 +108,17 @@ pub struct FrameBuildingState<'a> {
     pub profile_counters: &'a mut FrameProfileCounters,
     pub clip_store: &'a mut ClipStore,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub transforms: &'a mut TransformPalette,
     pub segment_builder: SegmentBuilder,
     pub surfaces: &'a mut Vec<SurfaceInfo>,
     pub dirty_region_stack: Vec<DirtyRegion>,
+    pub clip_chain_stack: ClipChainStack,
 }
 
 impl<'a> FrameBuildingState<'a> {
     /// Retrieve the current dirty region during primitive traversal.
     pub fn current_dirty_region(&self) -> &DirtyRegion {
         self.dirty_region_stack.last().unwrap()
     }
 
@@ -321,16 +323,18 @@ impl FrameBuilder {
         // which surfaces have valid cached surfaces that don't need to
         // be rendered this frame.
         PictureUpdateState::update_all(
             surfaces,
             self.root_pic_index,
             &mut self.prim_store.pictures,
             &frame_context,
             gpu_cache,
+            &self.clip_store,
+            &data_stores.clip,
         );
 
         {
             let visibility_context = FrameVisibilityContext {
                 device_pixel_scale,
                 clip_scroll_tree,
                 screen_world_rect,
                 surfaces,
@@ -342,16 +346,17 @@ impl FrameBuilder {
             let mut visibility_state = FrameVisibilityState {
                 resource_cache,
                 gpu_cache,
                 clip_store: &mut self.clip_store,
                 scratch,
                 tile_cache: None,
                 retained_tiles: &mut retained_tiles,
                 data_stores,
+                clip_chain_stack: ClipChainStack::new(),
             };
 
             self.prim_store.update_visibility(
                 self.root_pic_index,
                 ROOT_SURFACE_INDEX,
                 &visibility_context,
                 &mut visibility_state,
             );
@@ -362,16 +367,17 @@ impl FrameBuilder {
             profile_counters,
             clip_store: &mut self.clip_store,
             resource_cache,
             gpu_cache,
             transforms: transform_palette,
             segment_builder: SegmentBuilder::new(),
             surfaces,
             dirty_region_stack: Vec::new(),
+            clip_chain_stack: ClipChainStack::new(),
         };
 
         // Push a default dirty region which culls primitives
         // against the screen world rect, in absence of any
         // other dirty regions.
         let mut default_dirty_region = DirtyRegion::new();
         default_dirty_region.push(
             frame_context.screen_world_rect,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -4,17 +4,17 @@
 
 use api::{FilterOp, MixBlendMode, PipelineId, PremultipliedColorF, PictureRect, PicturePoint, WorldPoint};
 use api::{DeviceIntRect, DeviceIntSize, DevicePoint, DeviceRect};
 use api::{LayoutRect, PictureToRasterTransform, LayoutPixel, PropertyBinding, PropertyBindingId};
 use api::{DevicePixelScale, RasterRect, RasterSpace, ColorF, ImageKey, DirtyRect, WorldSize, ClipMode, LayoutSize};
 use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect, ImageFormat, ImageDescriptor, WorldVector2D, LayoutPoint};
 use api::{DebugFlags, DeviceHomogeneousVector, DeviceVector2D};
 use box_shadow::{BLUR_SAMPLE_SCALE};
-use clip::{ClipChainId, ClipChainNode, ClipItem};
+use clip::{ClipChainId, ClipChainNode, ClipItem, ClipStore, ClipDataStore, ClipChainStack};
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex, CoordinateSystemId};
 use debug_colors;
 use device::TextureFilter;
 use euclid::{size2, vec3, TypedPoint2D, TypedScale, TypedSize2D};
 use euclid::approxeq::ApproxEq;
 use frame_builder::{FrameVisibilityContext, FrameVisibilityState};
 use intern::ItemUid;
 use internal_types::{FastHashMap, FastHashSet, PlaneSplitter};
@@ -1018,16 +1018,17 @@ impl TileCache {
             tile.clear();
         }
     }
 
     /// Update the dependencies for each tile for a given primitive instance.
     pub fn update_prim_dependencies(
         &mut self,
         prim_instance: &PrimitiveInstance,
+        clip_chain_stack: &ClipChainStack,
         prim_rect: LayoutRect,
         clip_scroll_tree: &ClipScrollTree,
         data_stores: &DataStores,
         clip_chain_nodes: &[ClipChainNode],
         pictures: &[PicturePrimitive],
         resource_cache: &ResourceCache,
         opacity_binding_store: &OpacityBindingStorage,
         image_instances: &ImageInstanceStorage,
@@ -1140,119 +1141,128 @@ impl TileCache {
 
         // The transforms of any clips that are relative to the picture may affect
         // the content rendered by this primitive.
         let mut world_clip_rect = world_rect;
         let mut culling_rect = prim_rect
             .intersection(&prim_instance.local_clip_rect)
             .unwrap_or(LayoutRect::zero());
 
-        let mut current_clip_chain_id = prim_instance.clip_chain_id;
-        while current_clip_chain_id != ClipChainId::NONE {
-            let clip_chain_node = &clip_chain_nodes[current_clip_chain_id.0 as usize];
-            let clip_node = &data_stores.clip[clip_chain_node.handle];
-
-            // We can skip the root clip node - it will be taken care of by the
-            // world bounding rect calculated for the cache.
-            if current_clip_chain_id == self.root_clip_chain_id {
-                current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
-                continue;
-            }
-
-            self.map_local_to_world.set_target_spatial_node(
-                clip_chain_node.spatial_node_index,
-                clip_scroll_tree,
-            );
-
-            // Clips that are simple rects and handled by collapsing them into a single
-            // clip rect. This avoids the need to store vertices for these cases, and also
-            // allows easy calculation of the overall bounds of the tile cache.
-            let add_to_clip_deps = match clip_node.item {
-                ClipItem::Rectangle(size, ClipMode::Clip) => {
-                    let clip_spatial_node = &clip_scroll_tree.spatial_nodes[clip_chain_node.spatial_node_index.0 as usize];
-
-                    let local_clip_rect = LayoutRect::new(
-                        clip_chain_node.local_pos,
-                        size,
+        // To maintain the previous logic, consider every clip in the current active
+        // clip stack that could affect this primitive.
+        // TODO(gw): We can make this much more efficient now, by taking advantage
+        //           of the per-picture clip chain information, rather then considering
+        //           it for every primitive, as we do here for simplicity.
+        for clip_stack in &clip_chain_stack.stack {
+            for clip_chain_id in clip_stack {
+                let mut current_clip_chain_id = *clip_chain_id;
+                while current_clip_chain_id != ClipChainId::NONE {
+                    let clip_chain_node = &clip_chain_nodes[current_clip_chain_id.0 as usize];
+                    let clip_node = &data_stores.clip[clip_chain_node.handle];
+
+                    // We can skip the root clip node - it will be taken care of by the
+                    // world bounding rect calculated for the cache.
+                    if current_clip_chain_id == self.root_clip_chain_id {
+                        current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
+                        continue;
+                    }
+
+                    self.map_local_to_world.set_target_spatial_node(
+                        clip_chain_node.spatial_node_index,
+                        clip_scroll_tree,
                     );
 
-                    if clip_spatial_node.coordinate_system_id == CoordinateSystemId(0) {
-                        // Clips that are not in the root coordinate system are not axis-aligned,
-                        // so we need to treat them as normal style clips with vertices.
-                        match self.map_local_to_world.map(&local_clip_rect) {
-                            Some(clip_world_rect) => {
-                                // Even if this ends up getting clipped out by the current clip
-                                // stack, we want to ensure the primitive gets added to the tiles
-                                // below, to ensure invalidation isn't tripped up by the wrong
-                                // number of primitives that affect this tile.
-                                world_clip_rect = world_clip_rect
-                                    .intersection(&clip_world_rect)
-                                    .unwrap_or(WorldRect::zero());
-
-                                // If the clip rect is in the same spatial node, it can be handled by the
-                                // local clip rect.
-                                if clip_chain_node.spatial_node_index == prim_instance.spatial_node_index {
-                                    culling_rect = culling_rect.intersection(&local_clip_rect).unwrap_or(LayoutRect::zero());
-
-                                    false
-                                } else if !clip_scroll_tree.is_same_or_child_of(
-                                    clip_chain_node.spatial_node_index,
-                                    self.spatial_node_index,
-                                ) {
-                                    // If the clip node is *not* a child of the main scroll root,
-                                    // add it to the list of potential world clips to be checked later.
-                                    // If it *is* a child of the main scroll root, then just track
-                                    // it as a normal clip dependency, since it likely moves in
-                                    // the same way as the primitive when scrolling (and if it doesn't,
-                                    // we want to invalidate and rasterize).
-                                    world_clips.push((
-                                        clip_world_rect.into(),
-                                        clip_chain_node.spatial_node_index,
-                                    ));
-
-                                    false
-                                } else {
-                                    true
+                    // Clips that are simple rects and handled by collapsing them into a single
+                    // clip rect. This avoids the need to store vertices for these cases, and also
+                    // allows easy calculation of the overall bounds of the tile cache.
+                    let add_to_clip_deps = match clip_node.item {
+                        ClipItem::Rectangle(size, ClipMode::Clip) => {
+                            let clip_spatial_node = &clip_scroll_tree.spatial_nodes[clip_chain_node.spatial_node_index.0 as usize];
+
+                            let local_clip_rect = LayoutRect::new(
+                                clip_chain_node.local_pos,
+                                size,
+                            );
+
+                            if clip_spatial_node.coordinate_system_id == CoordinateSystemId(0) {
+                                // Clips that are not in the root coordinate system are not axis-aligned,
+                                // so we need to treat them as normal style clips with vertices.
+                                match self.map_local_to_world.map(&local_clip_rect) {
+                                    Some(clip_world_rect) => {
+                                        // Even if this ends up getting clipped out by the current clip
+                                        // stack, we want to ensure the primitive gets added to the tiles
+                                        // below, to ensure invalidation isn't tripped up by the wrong
+                                        // number of primitives that affect this tile.
+                                        world_clip_rect = world_clip_rect
+                                            .intersection(&clip_world_rect)
+                                            .unwrap_or(WorldRect::zero());
+
+                                        // If the clip rect is in the same spatial node, it can be handled by the
+                                        // local clip rect.
+                                        if clip_chain_node.spatial_node_index == prim_instance.spatial_node_index {
+                                            culling_rect = culling_rect.intersection(&local_clip_rect).unwrap_or(LayoutRect::zero());
+
+                                            false
+                                        } else if !clip_scroll_tree.is_same_or_child_of(
+                                            clip_chain_node.spatial_node_index,
+                                            self.spatial_node_index,
+                                        ) {
+                                            // If the clip node is *not* a child of the main scroll root,
+                                            // add it to the list of potential world clips to be checked later.
+                                            // If it *is* a child of the main scroll root, then just track
+                                            // it as a normal clip dependency, since it likely moves in
+                                            // the same way as the primitive when scrolling (and if it doesn't,
+                                            // we want to invalidate and rasterize).
+                                            world_clips.push((
+                                                clip_world_rect.into(),
+                                                clip_chain_node.spatial_node_index,
+                                            ));
+
+                                            false
+                                        } else {
+                                            true
+                                        }
+                                    }
+                                    None => {
+                                        true
+                                    }
                                 }
-                            }
-                            None => {
+                            } else {
                                 true
                             }
                         }
-                    } else {
-                        true
+                        ClipItem::Rectangle(_, ClipMode::ClipOut) |
+                        ClipItem::RoundedRectangle(..) |
+                        ClipItem::Image { .. } |
+                        ClipItem::BoxShadow(..) => {
+                            true
+                        }
+                    };
+
+                    if add_to_clip_deps {
+                        clip_chain_uids.push(clip_chain_node.handle.uid());
+
+                        // If the clip has the same spatial node, the relative transform
+                        // will always be the same, so there's no need to depend on it.
+                        if clip_chain_node.spatial_node_index != self.spatial_node_index {
+                            clip_spatial_nodes.insert(clip_chain_node.spatial_node_index);
+                        }
+
+                        let local_clip_rect = LayoutRect::new(
+                            clip_chain_node.local_pos,
+                            LayoutSize::zero(),
+                        );
+                        if let Some(world_clip_rect) = self.map_local_to_world.map(&local_clip_rect) {
+                            clip_vertices.push(world_clip_rect.origin);
+                        }
                     }
-                }
-                ClipItem::Rectangle(_, ClipMode::ClipOut) |
-                ClipItem::RoundedRectangle(..) |
-                ClipItem::Image { .. } |
-                ClipItem::BoxShadow(..) => {
-                    true
-                }
-            };
-
-            if add_to_clip_deps {
-                clip_chain_uids.push(clip_chain_node.handle.uid());
-
-                // If the clip has the same spatial node, the relative transform
-                // will always be the same, so there's no need to depend on it.
-                if clip_chain_node.spatial_node_index != self.spatial_node_index {
-                    clip_spatial_nodes.insert(clip_chain_node.spatial_node_index);
-                }
-
-                let local_clip_rect = LayoutRect::new(
-                    clip_chain_node.local_pos,
-                    LayoutSize::zero(),
-                );
-                if let Some(world_clip_rect) = self.map_local_to_world.map(&local_clip_rect) {
-                    clip_vertices.push(world_clip_rect.origin);
+
+                    current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
                 }
             }
-
-            current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
         }
 
         if include_clip_rect {
             // Intersect the calculated prim bounds with the root clip rect, to save
             // having to process and transform the root clip rect in every primitive.
             if let Some(clipped_world_rect) = world_clip_rect.intersection(&self.root_clip_rect) {
                 self.world_bounding_rect = self.world_bounding_rect.union(&clipped_world_rect);
             }
@@ -1569,29 +1579,34 @@ pub struct PictureUpdateState<'a> {
 
 impl<'a> PictureUpdateState<'a> {
     pub fn update_all(
         surfaces: &'a mut Vec<SurfaceInfo>,
         pic_index: PictureIndex,
         picture_primitives: &mut [PicturePrimitive],
         frame_context: &FrameBuildingContext,
         gpu_cache: &mut GpuCache,
+        clip_store: &ClipStore,
+        clip_data_store: &ClipDataStore,
     ) {
         let mut state = PictureUpdateState {
             surfaces,
             surface_stack: vec![SurfaceIndex(0)],
             picture_stack: Vec::new(),
             are_raster_roots_assigned: true,
         };
 
         state.update(
             pic_index,
+            ClipChainId::NONE,
             picture_primitives,
             frame_context,
             gpu_cache,
+            clip_store,
+            clip_data_store,
         );
 
         if !state.are_raster_roots_assigned {
             state.assign_raster_roots(
                 pic_index,
                 picture_primitives,
                 ROOT_SPATIAL_NODE_INDEX,
             );
@@ -1645,35 +1660,44 @@ impl<'a> PictureUpdateState<'a> {
     }
 
     /// Update a picture, determining surface configuration,
     /// rasterization roots, and (in future) whether there
     /// are cached surfaces that can be used by this picture.
     fn update(
         &mut self,
         pic_index: PictureIndex,
+        clip_chain_id: ClipChainId,
         picture_primitives: &mut [PicturePrimitive],
         frame_context: &FrameBuildingContext,
         gpu_cache: &mut GpuCache,
+        clip_store: &ClipStore,
+        clip_data_store: &ClipDataStore,
     ) {
-        if let Some(children) = picture_primitives[pic_index.0].pre_update(
+        if let Some(prim_list) = picture_primitives[pic_index.0].pre_update(
+            clip_chain_id,
             self,
             frame_context,
+            clip_store,
+            clip_data_store,
         ) {
-            for child_pic_index in &children {
+            for (child_pic_index, clip_chain_id) in &prim_list.pictures {
                 self.update(
                     *child_pic_index,
+                    *clip_chain_id,
                     picture_primitives,
                     frame_context,
                     gpu_cache,
+                    clip_store,
+                    clip_data_store,
                 );
             }
 
             picture_primitives[pic_index.0].post_update(
-                children,
+                prim_list,
                 self,
                 frame_context,
                 gpu_cache,
             );
         }
     }
 
     /// Process the picture tree again in a depth-first order,
@@ -1696,17 +1720,17 @@ impl<'a> PictureUpdateState<'a> {
                 if !config.establishes_raster_root {
                     surface.raster_spatial_node_index = fallback_raster_spatial_node;
                 }
                 surface.raster_spatial_node_index
             }
             None => fallback_raster_spatial_node,
         };
 
-        for child_pic_index in &picture.prim_list.pictures {
+        for (child_pic_index, _) in &picture.prim_list.pictures {
             self.assign_raster_roots(*child_pic_index, picture_primitives, new_fallback);
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub struct SurfaceIndex(pub usize);
 
@@ -1787,28 +1811,40 @@ pub struct RasterConfig {
     pub composite_mode: PictureCompositeMode,
     /// Index to the surface descriptor for this
     /// picture.
     pub surface_index: SurfaceIndex,
     /// Whether this picture establishes a rasterization root.
     pub establishes_raster_root: bool,
 }
 
+bitflags! {
+    /// A set of flags describing why a picture may need a backing surface.
+    pub struct BlitReason: u32 {
+        /// Mix-blend-mode on a child that requires isolation.
+        const ISOLATE = 1;
+        /// Clip node that _might_ require a surface.
+        const CLIP = 2;
+        /// Preserve-3D requires a surface for plane-splitting.
+        const PRESERVE3D = 4;
+    }
+}
+
 /// Specifies how this Picture should be composited
 /// onto the target it belongs to.
 #[allow(dead_code)]
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum PictureCompositeMode {
     /// Apply CSS mix-blend-mode effect.
     MixBlend(MixBlendMode),
     /// Apply a CSS filter.
     Filter(FilterOp),
     /// Draw to intermediate surface, copy straight across. This
     /// is used for CSS isolation, and plane splitting.
-    Blit,
+    Blit(BlitReason),
     /// Used to cache a picture as a series of tiles.
     TileCache {
         clear_color: ColorF,
     },
 }
 
 // Stores the location of the picture if it is drawn to
 // an intermediate surface. This can be a render task if
@@ -1901,17 +1937,17 @@ pub struct PrimitiveClusterIndex(pub u32
 pub struct ClusterIndex(pub u16);
 
 impl ClusterIndex {
     pub const INVALID: ClusterIndex = ClusterIndex(u16::MAX);
 }
 
 /// A list of pictures, stored by the PrimitiveList to enable a
 /// fast traversal of just the pictures.
-pub type PictureList = SmallVec<[PictureIndex; 4]>;
+pub type PictureList = SmallVec<[(PictureIndex, ClipChainId); 4]>;
 
 /// A list of primitive instances that are added to a picture
 /// This ensures we can keep a list of primitives that
 /// are pictures, for a fast initial traversal of the picture
 /// tree without walking the instance list.
 pub struct PrimitiveList {
     /// The primitive instances, in render order.
     pub prim_instances: Vec<PrimitiveInstance>,
@@ -1949,17 +1985,17 @@ impl PrimitiveList {
 
         // Walk the list of primitive instances and extract any that
         // are pictures.
         for prim_instance in &mut prim_instances {
             // Check if this primitive is a picture. In future we should
             // remove this match and embed this info directly in the primitive instance.
             let is_pic = match prim_instance.kind {
                 PrimitiveInstanceKind::Picture { pic_index, .. } => {
-                    pictures.push(pic_index);
+                    pictures.push((pic_index, prim_instance.clip_chain_id));
                     true
                 }
                 _ => {
                     false
                 }
             };
 
             let prim_data = match prim_instance.kind {
@@ -2129,17 +2165,17 @@ impl PicturePrimitive {
         pt.add_item(format!("local_rect: {:?}", self.local_rect));
         if self.apply_local_clip_rect {
             pt.add_item(format!("local_clip_rect: {:?}", self.local_clip_rect));
         }
         pt.add_item(format!("spatial_node_index: {:?}", self.spatial_node_index));
         pt.add_item(format!("raster_config: {:?}", self.raster_config));
         pt.add_item(format!("requested_composite_mode: {:?}", self.requested_composite_mode));
 
-        for index in &self.prim_list.pictures {
+        for (index, _) in &self.prim_list.pictures {
             pictures[index.0].print(pictures, *index, pt);
         }
 
         pt.end_level();
     }
 
     fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool {
         match self.requested_composite_mode {
@@ -2489,19 +2525,22 @@ impl PicturePrimitive {
         }
     }
 
     /// Called during initial picture traversal, before we know the
     /// bounding rect of children. It is possible to determine the
     /// surface / raster config now though.
     fn pre_update(
         &mut self,
+        clip_chain_id: ClipChainId,
         state: &mut PictureUpdateState,
         frame_context: &FrameBuildingContext,
-    ) -> Option<PictureList> {
+        clip_store: &ClipStore,
+        clip_data_store: &ClipDataStore,
+    ) -> Option<PrimitiveList> {
         // Reset raster config in case we early out below.
         self.raster_config = None;
 
         // Resolve animation properties, and early out if the filter
         // properties make this picture invisible.
         if !self.resolve_scene_properties(frame_context.scene_properties) {
             return None;
         }
@@ -2509,16 +2548,62 @@ impl PicturePrimitive {
         // Push information about this pic on stack for children to read.
         state.push_picture(PictureInfo {
             spatial_node_index: self.spatial_node_index,
         });
 
         // See if this picture actually needs a surface for compositing.
         let actual_composite_mode = match self.requested_composite_mode {
             Some(PictureCompositeMode::Filter(filter)) if filter.is_noop() => None,
+            Some(PictureCompositeMode::Blit(reason)) if reason == BlitReason::CLIP => {
+                // If the only reason a picture has requested a surface is due to the clip
+                // chain node, we might choose to skip drawing a surface, and instead apply
+                // the clips to each individual primitive. The logic below works out which
+                // option to choose.
+
+                // Assume that we will apply clips to individual items
+                let mut apply_clip_to_picture = false;
+                let mut current_clip_chain_id = clip_chain_id;
+
+                // Walk each clip in this chain, to see whether to allocate a surface and clip
+                // that, or whether to apply clips to each primitive.
+                while current_clip_chain_id != ClipChainId::NONE {
+                    let clip_chain_node = &clip_store.clip_chain_nodes[current_clip_chain_id.0 as usize];
+                    let clip_node = &clip_data_store[clip_chain_node.handle];
+
+                    match clip_node.item {
+                        ClipItem::Rectangle(_, ClipMode::Clip) => {
+                            // Normal rectangle clips can be handled as per-item clips.
+                            // TODO(gw): In future, we might want to consider selecting
+                            //           a surface in some situations here (e.g. if the
+                            //           stacking context is in a different coord system
+                            //           from the clip, and there are enough primitives
+                            //           in the stacking context to justify a surface).
+                        }
+                        ClipItem::Rectangle(_, ClipMode::ClipOut) |
+                        ClipItem::RoundedRectangle(..) |
+                        ClipItem::Image { .. } |
+                        ClipItem::BoxShadow(..) => {
+                            // Any of these clip types will require a surface.
+                            apply_clip_to_picture = true;
+                            break;
+                        }
+                    }
+
+                    current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
+                }
+
+                // If we decided not to use a surfce for clipping, then skip and draw straight
+                // into the parent surface.
+                if apply_clip_to_picture {
+                    Some(PictureCompositeMode::Blit(reason))
+                } else {
+                    None
+                }
+            }
             mode => mode,
         };
 
         if let Some(composite_mode) = actual_composite_mode {
             // Retrieve the positioning node information for the parent surface.
             let parent_raster_node_index = state.current_surface().raster_spatial_node_index;
             let surface_spatial_node_index = self.spatial_node_index;
 
@@ -2561,28 +2646,31 @@ impl PicturePrimitive {
 
             self.raster_config = Some(RasterConfig {
                 composite_mode,
                 establishes_raster_root,
                 surface_index: state.push_surface(surface),
             });
         }
 
-        Some(mem::replace(&mut self.prim_list.pictures, SmallVec::new()))
+        Some(mem::replace(&mut self.prim_list, PrimitiveList::empty()))
     }
 
     /// Called after updating child pictures during the initial
     /// picture traversal.
     fn post_update(
         &mut self,
-        child_pictures: PictureList,
+        prim_list: PrimitiveList,
         state: &mut PictureUpdateState,
         frame_context: &FrameBuildingContext,
         gpu_cache: &mut GpuCache,
     ) {
+        // Restore the pictures list used during recursion.
+        self.prim_list = prim_list;
+
         // Pop the state information about this picture.
         state.pop_picture();
 
         for cluster in &mut self.prim_list.clusters {
             // Skip the cluster if backface culled.
             if !cluster.is_backface_visible {
                 let containing_block_index = match self.context_3d {
                     Picture3DContext::Out => {
@@ -2632,19 +2720,16 @@ impl PicturePrimitive {
             // which will allow the frame building code to skip most of the
             // current per-primitive culling code.
             cluster.is_visible = true;
             if let Some(cluster_rect) = surface.map_local_to_surface.map(&cluster.bounding_rect) {
                 surface.rect = surface.rect.union(&cluster_rect);
             }
         }
 
-        // Restore the pictures list used during recursion.
-        self.prim_list.pictures = child_pictures;
-
         // 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.
@@ -2968,17 +3053,17 @@ impl PicturePrimitive {
                     uv_rect_kind,
                     pic_context.raster_spatial_node_index,
                 );
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
                 surfaces[surface_index.0].tasks.push(render_task_id);
                 PictureSurface::RenderTask(render_task_id)
             }
-            PictureCompositeMode::Blit => {
+            PictureCompositeMode::Blit(_) => {
                 // The SplitComposite shader used for 3d contexts doesn't snap
                 // to pixels, so we shouldn't snap our uv coordinates either.
                 let supports_snapping = match self.context_3d {
                     Picture3DContext::In{ .. } => false,
                     _ => true,
                 };
 
                 let uv_rect_kind = calculate_uv_rect_kind(
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -1811,57 +1811,67 @@ impl PrimitiveStore {
                 prim_instance.spatial_node_index,
             );
 
             map_local_to_surface.set_target_spatial_node(
                 prim_instance.spatial_node_index,
                 frame_context.clip_scroll_tree,
             );
 
-            let (is_passthrough, prim_local_rect, clip_node_collector) = match prim_instance.kind {
+            let (is_passthrough, prim_local_rect) = match prim_instance.kind {
                 PrimitiveInstanceKind::Picture { pic_index, .. } => {
-                    if !self.pictures[pic_index.0].is_visible() {
-                        continue;
-                    }
-
-                    if let Some(ref raster_config) = self.pictures[pic_index.0].raster_config {
-                        if raster_config.establishes_raster_root {
-                            let surface = &frame_context.surfaces[raster_config.surface_index.0 as usize];
-                            frame_state.clip_store.push_raster_root(surface.surface_spatial_node_index);
+                    {
+                        let pic = &self.pictures[pic_index.0];
+                        if !pic.is_visible() {
+                            continue;
+                        }
+
+                        // If this picture has a surface, we will handle any active clips from parents
+                        // when compositing this surface. Otherwise, push the clip chain from this
+                        // picture on to the active stack for any child primitive(s) to include.
+                        match pic.raster_config {
+                            Some(_) => {
+                                frame_state.clip_chain_stack.push_surface();
+                            }
+                            None => {
+                                frame_state.clip_chain_stack.push_clip(prim_instance.clip_chain_id);
+                            }
                         }
                     }
 
                     self.update_visibility(
                         pic_index,
                         surface_index,
                         frame_context,
                         frame_state,
                     );
 
                     let pic = &self.pictures[pic_index.0];
 
-                    let clip_node_collector = pic.raster_config.as_ref().and_then(|rc| {
-                        if rc.establishes_raster_root {
-                            Some(frame_state.clip_store.pop_raster_root())
-                        } else {
-                            None
+                    // Similar to above, pop either the clip chain or root entry off the current clip stack.
+                    match pic.raster_config {
+                        Some(_) => {
+                            frame_state.clip_chain_stack.pop_surface();
                         }
-                    });
-
-                    (pic.raster_config.is_none(), pic.local_rect, clip_node_collector)
+                        None => {
+                            frame_state.clip_chain_stack.pop_clip();
+                        }
+                    }
+
+                    (pic.raster_config.is_none(), pic.local_rect)
                 }
                 _ => {
                     let prim_data = &frame_state.data_stores.as_common_data(&prim_instance);
 
                     let prim_rect = LayoutRect::new(
                         prim_instance.prim_origin,
                         prim_data.prim_size,
                     );
 
-                    (false, prim_rect, None)
+                    (false, prim_rect)
                 }
             };
 
             if is_passthrough {
                 let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32);
 
                 frame_state.scratch.prim_info.push(
                     PrimitiveVisibility {
@@ -1895,51 +1905,60 @@ impl PrimitiveStore {
                         if prim_instance.is_chased() {
                             println!("\tculled for being out of the local clip rectangle: {:?}",
                                 prim_instance.local_clip_rect);
                         }
                         continue;
                     }
                 };
 
+                // Include the clip chain for this primitive in the current stack.
+                frame_state.clip_chain_stack.push_clip(prim_instance.clip_chain_id);
+
                 if let Some(ref mut tile_cache) = frame_state.tile_cache {
                     if !tile_cache.update_prim_dependencies(
                         prim_instance,
+                        &frame_state.clip_chain_stack,
                         prim_local_rect,
                         frame_context.clip_scroll_tree,
                         frame_state.data_stores,
                         &frame_state.clip_store.clip_chain_nodes,
                         &self.pictures,
                         frame_state.resource_cache,
                         &self.opacity_bindings,
                         &self.images,
                     ) {
                         prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
+                        // Ensure the primitive clip is popped - perhaps we can use
+                        // some kind of scope to do this automatically in future.
+                        frame_state.clip_chain_stack.pop_clip();
                         continue;
                     }
                 }
 
                 let clip_chain = frame_state
                     .clip_store
                     .build_clip_chain_instance(
-                        prim_instance,
+                        frame_state.clip_chain_stack.current_clips(),
                         local_rect,
                         prim_instance.local_clip_rect,
                         prim_context.spatial_node_index,
                         &map_local_to_surface,
                         &map_surface_to_world,
                         &frame_context.clip_scroll_tree,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
                         frame_context.device_pixel_scale,
                         &frame_context.screen_world_rect,
-                        clip_node_collector.as_ref(),
                         &mut frame_state.data_stores.clip,
                     );
 
+                // Ensure the primitive clip is popped
+                frame_state.clip_chain_stack.pop_clip();
+
                 let clip_chain = match clip_chain {
                     Some(clip_chain) => clip_chain,
                     None => {
                         if prim_instance.is_chased() {
                             println!("\tunable to build the clip chain, skipping");
                         }
                         prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
                         continue;
@@ -2213,26 +2232,42 @@ impl PrimitiveStore {
             }
         };
 
         let is_passthrough = match pic_info {
             Some((pic_context_for_children, mut pic_state_for_children, mut prim_list)) => {
                 // Mark whether this picture has a complex coordinate system.
                 let is_passthrough = pic_context_for_children.is_passthrough;
 
+                // Similar to the logic in the visibility pass, push either the
+                // picture clip chain or a new root, depending on whether this
+                // picture is backed by a surface.
+                if is_passthrough {
+                    frame_state.clip_chain_stack.push_clip(prim_instance.clip_chain_id);
+                } else {
+                    frame_state.clip_chain_stack.push_surface();
+                }
+
                 self.prepare_primitives(
                     &mut prim_list,
                     &pic_context_for_children,
                     &mut pic_state_for_children,
                     frame_context,
                     frame_state,
                     data_stores,
                     scratch,
                 );
 
+                // And now undo the clip stack logic above.
+                if is_passthrough {
+                    frame_state.clip_chain_stack.pop_clip();
+                } else {
+                    frame_state.clip_chain_stack.pop_surface();
+                }
+
                 if !pic_state_for_children.is_cacheable {
                     pic_state.is_cacheable = false;
                 }
 
                 // Restore the dependencies (borrow check dance)
                 self.pictures[pic_context_for_children.pic_index.0]
                     .restore_context(
                         prim_list,
@@ -2244,28 +2279,34 @@ impl PrimitiveStore {
                 is_passthrough
             }
             None => {
                 false
             }
         };
 
         if !is_passthrough {
+            // Push the per-primitive clip chain onto the current active stack
+            frame_state.clip_chain_stack.push_clip(prim_instance.clip_chain_id);
+
             prim_instance.update_clip_task(
                 prim_context,
                 pic_context.raster_spatial_node_index,
                 pic_context,
                 pic_state,
                 frame_context,
                 frame_state,
                 self,
                 data_stores,
                 scratch,
             );
 
+            // Pop the primitive clip chain.
+            frame_state.clip_chain_stack.pop_clip();
+
             if prim_instance.is_chased() {
                 println!("\tconsidered visible and ready with local pos {:?}", prim_instance.prim_origin);
             }
         }
 
         #[cfg(debug_assertions)]
         {
             prim_instance.prepared_frame_id = frame_state.render_tasks.frame_id();
@@ -3315,31 +3356,30 @@ impl PrimitiveInstance {
             for segment in segments {
                 // Build a clip chain for the smaller segment rect. This will
                 // often manage to eliminate most/all clips, and sometimes
                 // clip the segment completely.
 
                 let segment_clip_chain = frame_state
                     .clip_store
                     .build_clip_chain_instance(
-                        self,
+                        frame_state.clip_chain_stack.current_clips(),
                         segment.local_rect.translate(&LayoutVector2D::new(
                             self.prim_origin.x,
                             self.prim_origin.y,
                         )),
                         self.local_clip_rect,
                         prim_context.spatial_node_index,
                         &pic_state.map_local_to_pic,
                         &pic_state.map_pic_to_world,
                         &frame_context.clip_scroll_tree,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
                         frame_context.device_pixel_scale,
                         &dirty_world_rect,
-                        None,
                         &mut data_stores.clip,
                     );
 
                 let clip_mask_kind = segment.update_clip_task(
                     segment_clip_chain.as_ref(),
                     prim_info.clipped_world_rect,
                     root_spatial_node_index,
                     pic_context.surface_index,
--- a/gfx/wr/webrender/src/prim_store/picture.rs
+++ b/gfx/wr/webrender/src/prim_store/picture.rs
@@ -112,17 +112,17 @@ impl From<Option<PictureCompositeMode>> 
                         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)
                     }
                 }
             }
-            Some(PictureCompositeMode::Blit) |
+            Some(PictureCompositeMode::Blit(_)) |
             Some(PictureCompositeMode::TileCache { .. }) |
             None => {
                 PictureCompositeKey::Identity
             }
         }
     }
 }
 
--- a/gfx/wr/wrench/reftests/clip/reftest.list
+++ b/gfx/wr/wrench/reftests/clip/reftest.list
@@ -4,10 +4,10 @@ platform(linux,mac) == border-with-round
 platform(linux,mac) == clip-45-degree-rotation.yaml clip-45-degree-rotation-ref.png
 == clip-3d-transform.yaml clip-3d-transform-ref.yaml
 fuzzy(1,3) == clip-corner-overlap.yaml clip-corner-overlap-ref.yaml
 == custom-clip-chains.yaml custom-clip-chains-ref.yaml
 == custom-clip-chain-node-ancestors.yaml custom-clip-chain-node-ancestors-ref.yaml
 == fixed-position-clipping.yaml fixed-position-clipping-ref.yaml
 platform(linux,mac) == segmentation-with-other-coordinate-system-clip.yaml segmentation-with-other-coordinate-system-clip.png
 == segmentation-across-rotation.yaml segmentation-across-rotation-ref.yaml
-== color_targets(3) alpha_targets(1) stacking-context-clip.yaml stacking-context-clip-ref.yaml
+== color_targets(2) alpha_targets(1) stacking-context-clip.yaml stacking-context-clip-ref.yaml
 == snapping.yaml snapping-ref.yaml
index 9233e527650d1f73946f89124b98125bd9dc8b3f..429e0c3d861237fdbec68dfde5b75a88aaad20dc
GIT binary patch
literal 16912
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
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -6069,16 +6069,18 @@ bool nsDisplayOpacity::CreateWebRenderCo
   wr::WrAnimationProperty prop{
       wr::WrAnimationType::Opacity,
       animationsId,
   };
 
   wr::StackingContextParams params;
   params.animation = animationsId ? &prop : nullptr;
   params.opacity = opacityForSC;
+  params.clip =
+      wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
   StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
                            params);
 
   aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
       &mList, this, aDisplayListBuilder, sc, aBuilder, aResources);
   return true;
 }
 
@@ -6109,16 +6111,18 @@ bool nsDisplayBlendMode::CreateWebRender
     mozilla::wr::DisplayListBuilder& aBuilder,
     mozilla::wr::IpcResourceUpdateQueue& aResources,
     const StackingContextHelper& aSc,
     mozilla::layers::RenderRootStateManager* aManager,
     nsDisplayListBuilder* aDisplayListBuilder) {
   wr::StackingContextParams params;
   params.mix_blend_mode =
       wr::ToMixBlendMode(nsCSSRendering::GetGFXBlendMode(mBlendMode));
+  params.clip =
+      wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
   StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
                            params);
 
   return nsDisplayWrapList::CreateWebRenderCommands(
       aBuilder, aResources, sc, aManager, aDisplayListBuilder);
 }
 
 // nsDisplayBlendMode uses layers for rendering
@@ -6228,18 +6232,21 @@ LayerState nsDisplayBlendContainer::GetL
 }
 
 bool nsDisplayBlendContainer::CreateWebRenderCommands(
     mozilla::wr::DisplayListBuilder& aBuilder,
     mozilla::wr::IpcResourceUpdateQueue& aResources,
     const StackingContextHelper& aSc,
     mozilla::layers::RenderRootStateManager* aManager,
     nsDisplayListBuilder* aDisplayListBuilder) {
-  StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this,
-                           aBuilder);
+  wr::StackingContextParams params;
+  params.clip =
+      wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
+  StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
+                           params);
 
   return nsDisplayWrapList::CreateWebRenderCommands(
       aBuilder, aResources, sc, aManager, aDisplayListBuilder);
 }
 
 /* static */ nsDisplayTableBlendContainer*
 nsDisplayTableBlendContainer::CreateForBackgroundBlendMode(
     nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
@@ -6317,39 +6324,37 @@ already_AddRefed<Layer> nsDisplayOwnLaye
   return layer.forget();
 }
 
 bool nsDisplayOwnLayer::CreateWebRenderCommands(
     mozilla::wr::DisplayListBuilder& aBuilder,
     mozilla::wr::IpcResourceUpdateQueue& aResources,
     const StackingContextHelper& aSc, RenderRootStateManager* aManager,
     nsDisplayListBuilder* aDisplayListBuilder) {
-  if (!aManager->LayerManager()->AsyncPanZoomEnabled() ||
-      !IsScrollThumbLayer()) {
-    return nsDisplayWrapList::CreateWebRenderCommands(
-        aBuilder, aResources, aSc, aManager, aDisplayListBuilder);
-  }
-
-  // APZ is enabled and this is a scroll thumb, so we need to create and
-  // set an animation id. That way APZ can move this scrollthumb around as
-  // needed.
-  RefPtr<WebRenderAnimationData> animationData =
-      aManager->CommandBuilder()
-          .CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(this);
-  AnimationInfo& animationInfo = animationData->GetAnimationInfo();
-  animationInfo.EnsureAnimationsId();
-  mWrAnimationId = animationInfo.GetCompositorAnimationsId();
-
-  wr::WrAnimationProperty prop;
-  prop.id = mWrAnimationId;
-  prop.effect_type = wr::WrAnimationType::Transform;
+  Maybe<wr::WrAnimationProperty> prop;
+  if (aManager->LayerManager()->AsyncPanZoomEnabled() && IsScrollThumbLayer()) {
+    // APZ is enabled and this is a scroll thumb, so we need to create and
+    // set an animation id. That way APZ can move this scrollthumb around as
+    // needed.
+    RefPtr<WebRenderAnimationData> animationData =
+        aManager->CommandBuilder()
+            .CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(this);
+    AnimationInfo& animationInfo = animationData->GetAnimationInfo();
+    animationInfo.EnsureAnimationsId();
+    mWrAnimationId = animationInfo.GetCompositorAnimationsId();
+
+    prop.emplace();
+    prop->id = mWrAnimationId;
+    prop->effect_type = wr::WrAnimationType::Transform;
+  }
 
   wr::StackingContextParams params;
-  params.animation = &prop;
-
+  params.animation = prop.ptrOr(nullptr);
+  params.clip =
+      wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
   StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
                            params);
 
   nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager,
                                              aDisplayListBuilder);
   return true;
 }
 
@@ -7019,18 +7024,21 @@ bool nsDisplayStickyPosition::CreateWebR
         rightMargin.ptrOr(nullptr), bottomMargin.ptrOr(nullptr),
         leftMargin.ptrOr(nullptr), vBounds, hBounds, applied);
 
     saccHelper.emplace(aBuilder, spatialId);
     aManager->CommandBuilder().PushOverrideForASR(mContainerASR, spatialId);
   }
 
   {
+    wr::StackingContextParams params;
+    params.clip =
+        wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
     StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this,
-                             aBuilder);
+                             aBuilder, params);
     nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc,
                                                aManager, aDisplayListBuilder);
   }
 
   if (stickyScrollContainer) {
     aManager->CommandBuilder().PopOverrideForASR(mContainerASR);
   }
 
@@ -7935,16 +7943,18 @@ bool nsDisplayTransform::CreateWebRender
   wr::StackingContextParams params;
   params.mBoundTransform = &newTransformMatrix;
   params.animation = animationsId ? &prop : nullptr;
   params.mTransformPtr = transformForSC;
   params.is_backface_visible = !BackfaceIsHidden();
   params.mDeferredTransformItem = deferredTransformItem;
   params.mAnimated = animated;
   params.SetPreserve3D(mFrame->Extend3DContext() && !mIsTransformSeparator);
+  params.clip =
+      wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
 
   StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
                            params,
                            LayoutDeviceRect(position, LayoutDeviceSize()));
 
   aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
       mStoredList.GetChildren(), &mStoredList, aDisplayListBuilder, sc,
       aBuilder, aResources, this);
@@ -8546,16 +8556,18 @@ bool nsDisplayPerspective::CreateWebRend
   bool preserve3D =
       mFrame->Extend3DContext() || perspectiveFrame->Extend3DContext();
 
   wr::StackingContextParams params;
   params.mTransformPtr = &perspectiveMatrix;
   params.reference_frame_kind = wr::WrReferenceFrameKind::Perspective;
   params.is_backface_visible = !BackfaceIsHidden();
   params.SetPreserve3D(preserve3D);
+  params.clip =
+      wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
 
   Maybe<uint64_t> scrollingRelativeTo;
   for (auto* asr = GetActiveScrolledRoot(); asr; asr = asr->mParent) {
     if (nsLayoutUtils::IsAncestorFrameCrossDoc(
             asr->mScrollableFrame->GetScrolledFrame(), perspectiveFrame)) {
       scrollingRelativeTo.emplace(asr->GetViewId());
       break;
     }
--- a/layout/reftests/async-scrolling/reftest.list
+++ b/layout/reftests/async-scrolling/reftest.list
@@ -1,16 +1,16 @@
 skip-if(!asyncPan) == bg-fixed-1.html bg-fixed-1-ref.html
 skip-if(!asyncPan) == bg-fixed-cover-1.html bg-fixed-cover-1-ref.html
 skip-if(!asyncPan) == bg-fixed-cover-2.html bg-fixed-cover-2-ref.html
 skip-if(!asyncPan) == bg-fixed-cover-3.html bg-fixed-cover-3-ref.html
 skip-if(!asyncPan) == bg-fixed-child.html bg-fixed-child-ref.html
 skip-if(!asyncPan) == bg-fixed-child-clip-1.html bg-fixed-child-clip-ref.html
 skip-if(!asyncPan) == bg-fixed-child-clip-2.html bg-fixed-child-clip-ref.html
-fuzzy(0-1,0-246) fuzzy-if(skiaContent,0-2,0-170) fuzzy-if(browserIsRemote&&d2d,0-59,0-187) skip-if(!asyncPan) == bg-fixed-child-mask.html bg-fixed-child-mask-ref.html
+fuzzy(0-1,0-246) fuzzy-if(skiaContent,0-2,0-170) fuzzy-if(browserIsRemote&&d2d,0-59,0-187) fuzzy-if(webrender,41-41,154-154) skip-if(!asyncPan) == bg-fixed-child-mask.html bg-fixed-child-mask-ref.html
 skip-if(!asyncPan) == bg-fixed-in-opacity.html bg-fixed-in-opacity-ref.html
 # Passing the test below without WebRender would require implementing CSS filters in the Gecko compositor.
 fails-if(!webrender) skip-if(!asyncPan) fuzzy-if(webrender&&gtkWidget,0-1,0-87) fuzzy-if(webrender&&!gtkWidget,0-1,0-8) == bg-fixed-in-css-filter.html bg-fixed-in-css-filter-ref.html # bug 1454794 for webrender fuzziness
 skip-if(!asyncPan) == bg-fixed-child-no-culling-1.html bg-fixed-child-no-culling-1-ref.html
 skip-if(!asyncPan) == bg-fixed-child-no-culling-2.html bg-fixed-child-no-culling-2-ref.html
 skip-if(!asyncPan) == bg-fixed-child-no-culling-3.html bg-fixed-child-no-culling-3-ref.html
 fuzzy-if(Android,0-2,0-4000) fuzzy-if(browserIsRemote&&cocoaWidget,0-2,0-179524) fuzzy-if(browserIsRemote&&winWidget,0-1,0-74590) fuzzy-if(gtkWidget&&layersGPUAccelerated,0-1,0-3528) skip-if(!asyncPan) == bg-fixed-transformed-image.html bg-fixed-transformed-image-ref.html
 test-pref(layout.css.contain.enabled,true) skip-if(!asyncPan) == contain-paint-scrollable-frame-1.html contain-paint-scrollable-frame-1-ref.html
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1875,17 +1875,17 @@ fails-if(webrender) == 1059498-2.html 10
 fails-if(webrender) == 1059498-3.html 1059498-1-ref.html # WebRender: see bug 1499113
 == 1062108-1.html 1062108-1-ref.html
 == 1062792-1.html 1062792-1-ref.html
 == 1062963-floatmanager-reflow.html 1062963-floatmanager-reflow-ref.html
 == 1066554-1.html 1066554-1-ref.html
 == 1069716-1.html 1069716-1-ref.html
 == 1078262-1.html about:blank
 test-pref(layout.testing.overlay-scrollbars.always-visible,false) == 1081072-1.html 1081072-1-ref.html
-fuzzy-if(webrender,64-64,485-486) == 1081185-1.html 1081185-1-ref.html
+fuzzy-if(webrender,64-64,622-633) == 1081185-1.html 1081185-1-ref.html
 == 1097437-1.html 1097437-1-ref.html
 == 1103258-1.html 1103258-1-ref.html # assertion crash test with layers culling test
 == 1105137-1.html 1105137-1-ref.html
 fuzzy-if(d2d,0-36,0-304) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)&&d2d,0-139,0-701) == 1116480-1-fakeitalic-overflow.html 1116480-1-fakeitalic-overflow-ref.html
 == 1111753-1.html about:blank
 == 1114526-1.html 1114526-1-ref.html
 fuzzy-if(skiaContent,0-1,0-800000) == 1119117-1a.html 1119117-1-ref.html
 fuzzy-if(skiaContent,0-1,0-800000) == 1119117-1b.html 1119117-1-ref.html
--- a/layout/reftests/table-background/reftest.list
+++ b/layout/reftests/table-background/reftest.list
@@ -42,18 +42,18 @@ asserts-if(gtkWidget,0-6) != backgr_bord
 == border-collapse-table.html border-collapse-table-ref.html
 fuzzy-if(d2d,0-1,0-1083) fuzzy-if(skiaContent,0-1,0-2200) == border-collapse-opacity-table-cell.html border-collapse-opacity-table-cell-ref.html
 fuzzy-if(d2d,0-1,0-33174) fuzzy-if(skiaContent,0-1,0-16863) == border-collapse-opacity-table-column-group.html border-collapse-opacity-table-column-group-ref.html
 fuzzy-if(d2d,0-1,0-11058) fuzzy-if(skiaContent,0-1,0-5625) == border-collapse-opacity-table-column.html border-collapse-opacity-table-column-ref.html
 fuzzy-if(d2d,0-1,0-24606) fuzzy-if(skiaContent,0-1,0-17000) == border-collapse-opacity-table-row-group.html border-collapse-opacity-table-row-group-ref.html
 fuzzy-if(d2d,0-1,0-11000) fuzzy-if(skiaContent,0-1,0-11000) == border-collapse-opacity-table-row.html border-collapse-opacity-table-row-ref.html
 fuzzy-if(d2d||skiaContent,0-1,0-60000) == border-collapse-opacity-table.html border-collapse-opacity-table-ref.html
 fuzzy-if(d2d,0-1,0-2478) fuzzy-if(skiaContent,0-1,0-2500) == border-separate-opacity-table-cell.html border-separate-opacity-table-cell-ref.html
-fuzzy-if(d2d,0-1,0-38000) fuzzy-if(webrender&&gtkWidget,1-1,4078-4078) == border-separate-opacity-table-column-group.html border-separate-opacity-table-column-group-ref.html
-fuzzy-if(d2d,0-1,0-13000) fuzzy-if(webrender&&gtkWidget,1-1,1329-1329) == border-separate-opacity-table-column.html border-separate-opacity-table-column-ref.html
+fuzzy-if(d2d,0-1,0-38000) == border-separate-opacity-table-column-group.html border-separate-opacity-table-column-group-ref.html
+fuzzy-if(d2d,0-1,0-13000) == border-separate-opacity-table-column.html border-separate-opacity-table-column-ref.html
 fuzzy-if(d2d,0-1,0-37170) fuzzy-if(skiaContent,0-1,0-38000) == border-separate-opacity-table-row-group.html border-separate-opacity-table-row-group-ref.html
 fuzzy-if(d2d,0-1,0-12390) fuzzy-if(skiaContent,0-1,0-13000) == border-separate-opacity-table-row.html border-separate-opacity-table-row-ref.html
 fuzzy-if(d2d||skiaContent,0-1,0-95000) == border-separate-opacity-table.html border-separate-opacity-table-ref.html
 != scrollable-rowgroup-collapse-background.html scrollable-rowgroup-collapse-notref.html
 != scrollable-rowgroup-collapse-border.html scrollable-rowgroup-collapse-notref.html
 != scrollable-rowgroup-separate-background.html scrollable-rowgroup-separate-notref.html
 == scrollable-rowgroup-separate-border.html scrollable-rowgroup-separate-notref.html # scrolling rowgroups were removed in bug 28800
 == empty-cells-default-1.html empty-cells-default-1-ref.html
--- a/layout/reftests/transform-3d/reftest.list
+++ b/layout/reftests/transform-3d/reftest.list
@@ -77,17 +77,17 @@ fuzzy-if(cocoaWidget,0-128,0-9) random-i
 == 1245450-1.html green-rect.html
 fuzzy(0-1,0-2000) == opacity-preserve3d-1.html opacity-preserve3d-1-ref.html
 fuzzy(0-1,0-15000) == opacity-preserve3d-2.html opacity-preserve3d-2-ref.html
 fuzzy(0-1,0-10000) == opacity-preserve3d-3.html opacity-preserve3d-3-ref.html
 fuzzy(0-1,0-10000) == opacity-preserve3d-4.html opacity-preserve3d-4-ref.html
 == opacity-preserve3d-5.html opacity-preserve3d-5-ref.html
 == snap-perspective-1.html snap-perspective-1-ref.html
 == mask-layer-1.html mask-layer-ref.html
-fuzzy-if(webrender&&gtkWidget,8-8,100-100) fuzzy-if(webrender&&cocoaWidget,1-1,16-16) == mask-layer-2.html mask-layer-ref.html
+== mask-layer-2.html mask-layer-ref.html
 fuzzy-if(webrender,0-16,0-100) == mask-layer-3.html mask-layer-ref.html
 == split-intersect1.html split-intersect1-ref.html
 fuzzy(0-255,0-150) == split-intersect2.html split-intersect2-ref.html
 fuzzy(0-255,0-100) == split-non-ortho1.html split-non-ortho1-ref.html
 fuzzy-if(winWidget,0-150,0-120) == component-alpha-1.html component-alpha-1-ref.html
 == nested-transform-1.html nested-transform-1-ref.html
 == transform-geometry-1.html transform-geometry-1-ref.html
 == intermediate-1.html intermediate-1-ref.html