Bug 1496670. Update webrender to 923ee495bd9b0fda8a4a94c5a6cf42e2f0548731
authorJeff Muizelaar <jmuizelaar@mozilla.com>
Tue, 09 Oct 2018 23:20:18 -0400
changeset 488708 7801a4fb37db335ce0f65a92668b3062d3ea2df0
parent 488707 4f55976a9e9115c9f41075843bc48955684364d9
child 488709 335625773207d54a933b5debf6f7b38788d77cc9
push id246
push userfmarier@mozilla.com
push dateSat, 13 Oct 2018 00:15:40 +0000
bugs1496670
milestone64.0a1
Bug 1496670. Update webrender to 923ee495bd9b0fda8a4a94c5a6cf42e2f0548731
gfx/webrender/src/batch.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/device/gl.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/glyph_cache.rs
gfx/webrender/src/glyph_rasterizer/mod.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/platform/macos/font.rs
gfx/webrender/src/platform/unix/font.rs
gfx/webrender/src/platform/windows/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/scene_builder.rs
gfx/webrender/src/shade.rs
gfx/webrender/src/spatial_node.rs
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/src/wrench.rs
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -14,17 +14,17 @@ use gpu_types::{BrushFlags, BrushInstanc
 use gpu_types::{ClipMaskInstance, SplitCompositeInstance};
 use gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
 use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, TextureSource};
 use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Clipper, Polygon, Splitter};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve};
 use prim_store::{EdgeAaSegmentMask, ImageSource};
-use prim_store::{PrimitiveMetadata, VisibleGradientTile, PrimitiveInstance};
+use prim_store::{VisibleGradientTile, PrimitiveInstance};
 use prim_store::{BorderSource, Primitive, PrimitiveDetails};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
 use scene::FilterOpHelpers;
 use std::{f32, i32};
 use tiling::{RenderTargetContext};
@@ -471,24 +471,24 @@ impl AlphaBatchBuilder {
         // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
         for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
             let prim_instance = &pic.prim_instances[poly.anchor];
             let prim_index = prim_instance.prim_index;
             let pic_metadata = &ctx.prim_store.primitives[prim_index.0].metadata;
             if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_index) {
                 println!("\t\tsplit polygon {:?}", poly.points);
             }
-            let transform = transforms.get_world_transform(pic_metadata.spatial_node_index).inverse().unwrap();
+            let transform = transforms.get_world_transform(prim_instance.spatial_node_index).inverse().unwrap();
             let transform_id = transforms.get_id(
-                pic_metadata.spatial_node_index,
+                prim_instance.spatial_node_index,
                 ROOT_SPATIAL_NODE_INDEX,
                 ctx.clip_scroll_tree,
             );
 
-            let clip_task_address = pic_metadata
+            let clip_task_address = prim_instance
                 .clip_task_id
                 .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
 
             let prim_header = PrimitiveHeader {
                 local_rect: pic_metadata.local_rect,
                 local_clip_rect: prim_instance.combined_local_clip_rect,
                 task_address,
                 specific_prim_address: GpuCacheAddress::invalid(),
@@ -533,17 +533,17 @@ impl AlphaBatchBuilder {
             let key = BatchKey::new(
                 BatchKind::SplitComposite,
                 BlendMode::PremultipliedAlpha,
                 BatchTextures::no_texture(),
             );
             let batch = self.batch_list
                             .get_suitable_batch(
                                 key,
-                                &pic_metadata.clipped_world_rect.as_ref().expect("bug"),
+                                &prim_instance.clipped_world_rect.as_ref().expect("bug"),
                             );
 
             let gpu_address = gpu_cache.get_address(&gpu_handle);
 
             let instance = SplitCompositeInstance::new(
                 prim_header_index,
                 gpu_address,
                 prim_headers.z_generator.next(),
@@ -570,35 +570,35 @@ impl AlphaBatchBuilder {
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
         plane_split_anchor: usize,
     ) {
         let prim = &ctx.prim_store.primitives[prim_instance.prim_index.0];
         let prim_metadata = &prim.metadata;
 
-        if prim_metadata.clipped_world_rect.is_none() {
+        if prim_instance.clipped_world_rect.is_none() {
             return;
         }
 
         #[cfg(debug_assertions)] //TODO: why is this needed?
-        debug_assert_eq!(prim_metadata.prepared_frame_id, render_tasks.frame_id());
+        debug_assert_eq!(prim_instance.prepared_frame_id, render_tasks.frame_id());
 
         let transform_id = transforms
             .get_id(
-                prim_metadata.spatial_node_index,
+                prim_instance.spatial_node_index,
                 root_spatial_node_index,
                 ctx.clip_scroll_tree,
             );
 
         // TODO(gw): Calculating this for every primitive is a bit
         //           wasteful. We should probably cache this in
         //           the scroll node...
         let transform_kind = transform_id.transform_kind();
-        let bounding_rect = prim_metadata.clipped_world_rect
+        let bounding_rect = prim_instance.clipped_world_rect
                                          .as_ref()
                                          .expect("bug");
 
         // If the primitive is internally decomposed into multiple sub-primitives we may not
         // use some of the per-primitive data typically stored in PrimitiveMetadata and get
         // it from each sub-primitive instead.
         let is_multiple_primitives = match prim.details {
             PrimitiveDetails::Brush(ref brush) => {
@@ -610,27 +610,27 @@ impl AlphaBatchBuilder {
                 }
             }
             _ => false,
         };
 
         let prim_cache_address = if is_multiple_primitives {
             GpuCacheAddress::invalid()
         } else {
-            gpu_cache.get_address(&prim_metadata.gpu_location)
+            gpu_cache.get_address(&prim_instance.gpu_location)
         };
 
-        let clip_task_address = prim_metadata
+        let clip_task_address = prim_instance
             .clip_task_id
             .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
 
         let specified_blend_mode = prim.get_blend_mode();
 
-        let non_segmented_blend_mode = if !prim_metadata.opacity.is_opaque ||
-            prim_metadata.clip_task_id.is_some() ||
+        let non_segmented_blend_mode = if !prim_instance.opacity.is_opaque ||
+            prim_instance.clip_task_id.is_some() ||
             transform_kind == TransformedRectKind::Complex {
             specified_blend_mode
         } else {
             BlendMode::None
         };
 
         let prim_header = PrimitiveHeader {
             local_rect: prim_metadata.local_rect,
@@ -651,31 +651,31 @@ impl AlphaBatchBuilder {
                 match brush.kind {
                     BrushKind::Picture(ref picture) => {
                         // If this picture is participating in a 3D rendering context,
                         // then don't add it to any batches here. Instead, create a polygon
                         // for it and add it to the current plane splitter.
                         if picture.is_in_3d_context {
                             // Push into parent plane splitter.
                             debug_assert!(picture.raster_config.is_some());
-                            let transform = transforms.get_world_transform(prim_metadata.spatial_node_index);
+                            let transform = transforms.get_world_transform(prim_instance.spatial_node_index);
 
                             // Apply the local clip rect here, before splitting. This is
                             // because the local clip rect can't be applied in the vertex
                             // shader for split composites, since we are drawing polygons
                             // rather that rectangles. The interpolation still works correctly
                             // since we determine the UVs by doing a bilerp with a factor
                             // from the original local rect.
                             let local_rect = prim_metadata.local_rect
                                                           .intersection(&prim_instance.combined_local_clip_rect);
 
                             if let Some(local_rect) = local_rect {
                                 match transform.transform_kind() {
                                     TransformedRectKind::AxisAligned => {
-                                        let inv_transform = transforms.get_world_inv_transform(prim_metadata.spatial_node_index);
+                                        let inv_transform = transforms.get_world_inv_transform(prim_instance.spatial_node_index);
                                         let polygon = Polygon::from_transformed_rect_with_inverse(
                                             local_rect.cast(),
                                             &transform.cast(),
                                             &inv_transform.cast(),
                                             plane_split_anchor,
                                         ).unwrap();
                                         splitter.add(polygon);
                                     }
@@ -1057,17 +1057,17 @@ impl AlphaBatchBuilder {
                             let prim_header_index = prim_headers.push(&prim_header, user_data);
                             if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_instance.prim_index) {
                                 println!("\t{:?} {:?}, task relative bounds {:?}",
                                     batch_kind, prim_header_index, bounding_rect);
                             }
 
                             self.add_brush_to_batch(
                                 brush,
-                                prim_metadata,
+                                prim_instance,
                                 batch_kind,
                                 specified_blend_mode,
                                 non_segmented_blend_mode,
                                 textures,
                                 prim_header_index,
                                 clip_task_address,
                                 bounding_rect,
                                 transform_kind,
@@ -1195,17 +1195,17 @@ impl AlphaBatchBuilder {
         };
         let batch = self.batch_list.get_suitable_batch(batch_key, bounding_rect);
         batch.push(PrimitiveInstanceData::from(base_instance));
     }
 
     fn add_brush_to_batch(
         &mut self,
         brush: &BrushPrimitive,
-        prim_metadata: &PrimitiveMetadata,
+        prim_instance: &PrimitiveInstance,
         batch_kind: BrushBatchKind,
         alpha_blend_mode: BlendMode,
         non_segmented_blend_mode: BlendMode,
         textures: BatchTextures,
         prim_header_index: PrimitiveHeaderIndex,
         clip_task_address: RenderTaskAddress,
         bounding_rect: &WorldRect,
         transform_kind: TransformedRectKind,
@@ -1240,17 +1240,17 @@ impl AlphaBatchBuilder {
 
                 let opaque_batch = self.batch_list.opaque_batch_list.get_suitable_batch(
                     opaque_batch_key,
                     bounding_rect,
                 );
 
                 for (i, segment) in segment_desc.segments.iter().enumerate() {
                     let is_inner = segment.edge_flags.is_empty();
-                    let needs_blending = !prim_metadata.opacity.is_opaque ||
+                    let needs_blending = !prim_instance.opacity.is_opaque ||
                                          segment.clip_task_id.needs_blending() ||
                                          (!is_inner && transform_kind == TransformedRectKind::Complex);
 
                     let clip_task_address = match segment.clip_task_id {
                         BrushSegmentTaskId::RenderTaskId(id) =>
                             render_tasks.get_task_address(id),
                         BrushSegmentTaskId::Opaque => OPAQUE_TASK_ADDRESS,
                         BrushSegmentTaskId::Empty => continue,
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
-use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, LocalClip};
+use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D};
 use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle, PicturePixel, WorldPixel};
 use api::{PictureRect, LayoutPixel, WorldPoint, WorldSize, WorldRect, LayoutToWorldTransform};
 use api::{VoidPtrToSizeFn, LayoutRectAu, ImageKey, AuHelpers};
 use app_units::Au;
 use border::{ensure_no_corner_overlap, BorderRadiusAu};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
 use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use ellipse::Ellipse;
@@ -708,34 +708,23 @@ impl<J> ClipRegion<ComplexTranslateIter<
                 offset: *reference_frame_relative_offset,
             },
         }
     }
 }
 
 impl ClipRegion<Option<ComplexClipRegion>> {
     pub fn create_for_clip_node_with_local_clip(
-        local_clip: &LocalClip,
+        local_clip: &LayoutRect,
         reference_frame_relative_offset: &LayoutVector2D
     ) -> Self {
         ClipRegion {
-            main: local_clip
-                .clip_rect()
-                .translate(reference_frame_relative_offset),
+            main: local_clip.translate(reference_frame_relative_offset),
             image_mask: None,
-            complex_clips: match *local_clip {
-                LocalClip::Rect(_) => None,
-                LocalClip::RoundedRect(_, ref region) => {
-                    Some(ComplexClipRegion {
-                        rect: region.rect.translate(reference_frame_relative_offset),
-                        radii: region.radii,
-                        mode: region.mode,
-                    })
-                },
-            }
+            complex_clips: None,
         }
     }
 }
 
 // The ClipItemKey is a hashable representation of the contents
 // of a clip item. It is used during interning to de-duplicate
 // clip nodes between frames and display lists. This allows quick
 // comparison of clip node equality by handle, and also allows
--- a/gfx/webrender/src/device/gl.rs
+++ b/gfx/webrender/src/device/gl.rs
@@ -757,17 +757,16 @@ pub struct Device {
     extensions: Vec<String>,
 }
 
 impl Device {
     pub fn new(
         gl: Rc<gl::Gl>,
         resource_override_path: Option<PathBuf>,
         upload_method: UploadMethod,
-        _file_changed_handler: Box<FileWatcherHandler>,
         cached_programs: Option<Rc<ProgramCache>>,
     ) -> Device {
         let mut max_texture_size = [0];
         unsafe {
             gl.get_integer_v(gl::MAX_TEXTURE_SIZE, &mut max_texture_size);
         }
         let max_texture_size = max_texture_size[0] as u32;
         let renderer_name = gl.get_string(gl::RENDERER);
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -4,41 +4,42 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, ClipAndScrollInfo};
 use api::{ClipId, ColorF, ComplexClipRegion, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DevicePixelScale, DeviceUintRect, DisplayItemRef, ExtendMode, ExternalScrollId};
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, RasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, ColorDepth};
 use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
-use api::{LineOrientation, LineStyle, LocalClip, NinePatchBorderSource, PipelineId};
+use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
 use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData};
 use clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
 use euclid::vec2;
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::{PictureCompositeMode, PictureIdGenerator, PicturePrimitive};
-use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor};
-use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveOpacity};
+use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, PrimitiveInstance};
+use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveOpacity, PrimitiveKey};
 use prim_store::{BorderSource, BrushSegment, BrushSegmentVec, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::DocumentResources;
 use spatial_node::{SpatialNodeType, StickyFrameInfo};
 use std::{f32, mem};
+use std::collections::vec_deque::VecDeque;
 use tiling::{CompositeOps};
 use util::{MaxRect, RectHelpers};
 
 #[derive(Debug, Copy, Clone)]
 struct ClipNode {
     id: ClipChainId,
     count: usize,
 }
@@ -127,18 +128,18 @@ pub struct DisplayListFlattener<'a> {
 
     /// The data structure that converting between ClipId and the various index
     /// types that the ClipScrollTree uses.
     id_to_index_mapper: ClipIdToIndexMapper,
 
     /// A stack of stacking context properties.
     sc_stack: Vec<FlattenedStackingContext>,
 
-    /// A stack of the currently active shadows
-    shadow_stack: Vec<(Shadow, PrimitiveIndex)>,
+    /// Maintains state for any currently active shadows
+    pending_shadow_items: VecDeque<ShadowItem>,
 
     /// The stack keeping track of the root clip chains associated with pipelines.
     pipeline_clip_chain_stack: Vec<ClipChainId>,
 
     /// The store of primitives.
     pub prim_store: PrimitiveStore,
 
     /// Information about all primitives involved in hit testing.
@@ -152,16 +153,20 @@ pub struct DisplayListFlattener<'a> {
     pub config: FrameBuilderConfig,
 
     /// Reference to the document resources, which contains
     /// shared (interned) data between display lists.
     resources: &'a mut DocumentResources,
 
     /// The estimated count of primtives we expect to encounter during flattening.
     prim_count_estimate: usize,
+
+    /// The root primitive index for this flattener. This is the primitive
+    /// to start the culling phase from.
+    pub root_prim_index: PrimitiveIndex,
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn create_frame_builder(
         scene: &Scene,
         clip_scroll_tree: &mut ClipScrollTree,
         font_instances: FontInstanceMap,
         view: &DocumentView,
@@ -183,24 +188,25 @@ impl<'a> DisplayListFlattener<'a> {
         let mut flattener = DisplayListFlattener {
             scene,
             clip_scroll_tree,
             font_instances,
             config: *frame_builder_config,
             output_pipelines,
             id_to_index_mapper: ClipIdToIndexMapper::default(),
             hit_testing_runs: Vec::new(),
-            shadow_stack: Vec::new(),
+            pending_shadow_items: VecDeque::new(),
             sc_stack: Vec::new(),
             pipeline_clip_chain_stack: vec![ClipChainId::NONE],
             prim_store: PrimitiveStore::new(),
             clip_store: ClipStore::new(),
             picture_id_generator,
             resources,
             prim_count_estimate: 0,
+            root_prim_index: PrimitiveIndex(0),
         };
 
         flattener.push_root(
             root_pipeline_id,
             &root_pipeline.viewport_size,
             &root_pipeline.content_size,
         );
         flattener.setup_viewport_offset(view.inner_rect, view.accumulated_scale_factor());
@@ -469,17 +475,17 @@ impl<'a> DisplayListFlattener<'a> {
             },
         };
 
         //TODO: use or assert on `clip_and_scroll_ids.clip_node_id` ?
         let clip_chain_index = self.add_clip_node(
             info.clip_id,
             clip_and_scroll_ids.scroll_node_id,
             ClipRegion::create_for_clip_node_with_local_clip(
-                &LocalClip::from(*item.clip_rect()),
+                item.clip_rect(),
                 reference_frame_relative_offset
             ),
         );
         self.pipeline_clip_chain_stack.push(clip_chain_index);
 
         let bounds = item.rect();
         let origin = *reference_frame_relative_offset + bounds.origin.to_vector();
         self.push_reference_frame(
@@ -816,27 +822,36 @@ impl<'a> DisplayListFlattener<'a> {
     /// add the primitive to the draw list, so can be used for creating
     /// sub-primitives.
     pub fn create_primitive(
         &mut self,
         info: &LayoutPrimitiveInfo,
         clip_chain_id: ClipChainId,
         spatial_node_index: SpatialNodeIndex,
         container: PrimitiveContainer,
-    ) -> PrimitiveIndex {
+    ) -> PrimitiveInstance {
         let stacking_context = self.sc_stack.last().expect("bug: no stacking context!");
 
-        self.prim_store.add_primitive(
+        let prim_key = PrimitiveKey::new(
+            info.is_backface_visible && stacking_context.is_backface_visible,
+        );
+
+        let prim_data_handle = self.resources.prim_interner.intern(&prim_key);
+
+        let prim_index = self.prim_store.add_primitive(
             &info.rect,
             &info.clip_rect,
-            info.is_backface_visible && stacking_context.is_backface_visible,
+            container,
+        );
+
+        PrimitiveInstance::new(
+            prim_index,
+            prim_data_handle,
             clip_chain_id,
             spatial_node_index,
-            info.tag,
-            container,
         )
     }
 
     pub fn add_primitive_to_hit_testing_list(
         &mut self,
         info: &LayoutPrimitiveInfo,
         clip_and_scroll: ScrollNodeAndClipChain
     ) {
@@ -856,92 +871,66 @@ impl<'a> DisplayListFlattener<'a> {
         }
 
         self.hit_testing_runs.push(HitTestingRun(vec![new_item], clip_and_scroll));
     }
 
     /// Add an already created primitive to the draw lists.
     pub fn add_primitive_to_draw_list(
         &mut self,
-        prim_index: PrimitiveIndex,
+        prim_instance: PrimitiveInstance,
     ) {
-        // Add primitive to the top-most Picture on the stack.
-        let pic_prim_index = self.sc_stack.last().unwrap().leaf_prim_index;
-        if cfg!(debug_assertions) && self.prim_store.chase_id == Some(prim_index) {
-            println!("\tadded to picture primitive {:?}", pic_prim_index);
+        // Add primitive to the top-most stacking context on the stack.
+        if cfg!(debug_assertions) && self.prim_store.chase_id == Some(prim_instance.prim_index) {
+            println!("\tadded to stacking context at {}", self.sc_stack.len());
         }
-        let pic = self.prim_store.get_pic_mut(pic_prim_index);
-        pic.add_primitive(prim_index);
+        let stacking_context = self.sc_stack.last_mut().unwrap();
+        stacking_context.normal_primitives.push(prim_instance);
     }
 
     /// Convenience interface that creates a primitive entry and adds it
     /// to the draw list.
     pub fn add_primitive(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         clip_items: Vec<ClipItemKey>,
         container: PrimitiveContainer,
     ) {
-        if !self.shadow_stack.is_empty() {
-            // TODO(gw): Restructure this so we don't need to move the shadow
-            //           stack out (borrowck due to create_primitive below).
-            let shadow_stack = mem::replace(&mut self.shadow_stack, Vec::new());
-            for &(ref shadow, shadow_pic_prim_index) in &shadow_stack {
-                // Offset the local rect and clip rect by the shadow offset.
-                let mut info = info.clone();
-                info.rect = info.rect.translate(&shadow.offset);
-                info.clip_rect = info.clip_rect.translate(&shadow.offset);
-
-                // Offset any local clip sources by the shadow offset.
-                let clip_items: Vec<ClipItemKey> = clip_items
-                    .iter()
-                    .map(|cs| cs.offset(&shadow.offset))
-                    .collect();
+        // If a shadow context is not active, then add the primitive
+        // directly to the parent picture.
+        if self.pending_shadow_items.is_empty() {
+            if container.is_visible() {
                 let clip_chain_id = self.build_clip_chain(
                     clip_items,
                     clip_and_scroll.spatial_node_index,
                     clip_and_scroll.clip_chain_id,
                 );
-
-                // Construct and add a primitive for the given shadow.
-                let shadow_prim_index = self.create_primitive(
-                    &info,
+                let prim_instance = self.create_primitive(
+                    info,
                     clip_chain_id,
                     clip_and_scroll.spatial_node_index,
-                    container.create_shadow(shadow),
+                    container,
                 );
-
-                // Add the new primitive to the shadow picture.
-                let shadow_pic = self.prim_store.get_pic_mut(shadow_pic_prim_index);
-                shadow_pic.add_primitive(shadow_prim_index);
+                if cfg!(debug_assertions) && ChasePrimitive::LocalRect(info.rect) == self.config.chase_primitive {
+                    println!("Chasing {:?} by local rect", prim_instance.prim_index);
+                    self.prim_store.chase_id = Some(prim_instance.prim_index);
+                }
+                self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
+                self.add_primitive_to_draw_list(prim_instance);
             }
-            self.shadow_stack = shadow_stack;
-        }
-
-        if container.is_visible() {
-            let clip_chain_id = self.build_clip_chain(
+        } else {
+            // There is an active shadow context. Store as a pending primitive
+            // for processing during pop_all_shadows.
+            self.pending_shadow_items.push_back(ShadowItem::Primitive(PendingPrimitive {
+                clip_and_scroll,
+                info: *info,
                 clip_items,
-                clip_and_scroll.spatial_node_index,
-                clip_and_scroll.clip_chain_id,
-            );
-            let prim_index = self.create_primitive(
-                info,
-                clip_chain_id,
-                clip_and_scroll.spatial_node_index,
                 container,
-            );
-            if cfg!(debug_assertions) && ChasePrimitive::LocalRect(info.rect) == self.config.chase_primitive {
-                println!("Chasing {:?} by local rect", prim_index);
-                self.prim_store.chase_id = Some(prim_index);
-            }
-            self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
-            self.add_primitive_to_draw_list(
-                prim_index,
-            );
+            }));
         }
     }
 
     pub fn push_stacking_context(
         &mut self,
         pipeline_id: PipelineId,
         composite_ops: CompositeOps,
         transform_style: TransformStyle,
@@ -952,23 +941,23 @@ impl<'a> DisplayListFlattener<'a> {
         requested_raster_space: RasterSpace,
     ) {
         let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(spatial_node);
         let clip_chain_id = match clipping_node {
             Some(ref clipping_node) => self.id_to_index_mapper.get_clip_chain_id(clipping_node),
             None => ClipChainId::NONE,
         };
 
-        // An arbitrary large clip rect. For now, we don't
-        // specify a clip specific to the stacking context.
-        // However, now that they are represented as Picture
-        // primitives, we can apply any kind of clip mask
-        // to them, as for a normal primitive. This is needed
-        // to correctly handle some CSS cases (see #1957).
-        let max_clip = LayoutRect::max_rect();
+        // Check if this stacking context is the root of a pipeline, and the caller
+        // has requested it as an output frame.
+        let frame_output_pipeline_id = if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) {
+            Some(pipeline_id)
+        } else {
+            None
+        };
 
         // Get the transform-style of the parent stacking context,
         // which determines if we *might* need to draw this on
         // an intermediate surface for plane splitting purposes.
         let parent_transform_style = match self.sc_stack.last() {
             Some(sc) => sc.transform_style,
             None => TransformStyle::Flat,
         };
@@ -985,153 +974,21 @@ impl<'a> DisplayListFlattener<'a> {
 
         // If this is participating in a 3d context *and* the
         // parent was not a 3d context, then this must be the
         // element that establishes a new 3d context.
         let establishes_3d_context =
             participating_in_3d_context &&
             parent_transform_style == TransformStyle::Flat;
 
-        // By default, this picture will be collapsed into
-        // the owning target.
-        let mut composite_mode = None;
-
-        // If this stacking context is the root of a pipeline, and the caller
-        // has requested it as an output frame, create a render task to isolate it.
-        let mut frame_output_pipeline_id = None;
-        if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) {
-            composite_mode = Some(PictureCompositeMode::Blit);
-            frame_output_pipeline_id = Some(pipeline_id);
-        }
-
         // 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.
-        if participating_in_3d_context || clipping_node.is_some() {
-            // TODO(gw): For now, as soon as this picture is in
-            //           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.
-            composite_mode = Some(PictureCompositeMode::Blit);
-        }
-
-        // Add picture for this actual stacking context contents to render into.
-        let leaf_picture = PicturePrimitive::new_image(
-            self.picture_id_generator.next(),
-            composite_mode,
-            participating_in_3d_context,
-            pipeline_id,
-            frame_output_pipeline_id,
-            true,
-            requested_raster_space,
-        );
-
-        // Create a brush primitive that draws this picture.
-        let leaf_prim = BrushPrimitive::new_picture(leaf_picture);
-
-        // Add the brush to the parent picture.
-        let leaf_prim_index = self.prim_store.add_primitive(
-            &LayoutRect::zero(),
-            &max_clip,
-            true,
-            clip_chain_id,
-            spatial_node_index,
-            None,
-            PrimitiveContainer::Brush(leaf_prim),
-        );
-
-        // Create a chain of pictures based on presence of filters,
-        // mix-blend-mode and/or 3d rendering context containers.
-        let mut current_prim_index = leaf_prim_index;
-
-        // For each filter, create a new image with that composite mode.
-        for filter in &composite_ops.filters {
-            let filter = filter.sanitize();
-
-            let mut filter_picture = PicturePrimitive::new_image(
-                self.picture_id_generator.next(),
-                Some(PictureCompositeMode::Filter(filter)),
-                false,
-                pipeline_id,
-                None,
-                true,
-                requested_raster_space,
-            );
-
-            filter_picture.add_primitive(current_prim_index);
-            let filter_prim = BrushPrimitive::new_picture(filter_picture);
-
-            current_prim_index = self.prim_store.add_primitive(
-                &LayoutRect::zero(),
-                &max_clip,
-                true,
-                clip_chain_id,
-                spatial_node_index,
-                None,
-                PrimitiveContainer::Brush(filter_prim),
-            );
-        }
-
-        // Same for mix-blend-mode.
-        if let Some(mix_blend_mode) = composite_ops.mix_blend_mode {
-            let mut blend_picture = PicturePrimitive::new_image(
-                self.picture_id_generator.next(),
-                Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
-                false,
-                pipeline_id,
-                None,
-                true,
-                requested_raster_space,
-            );
-
-            blend_picture.add_primitive(current_prim_index);
-            let blend_prim = BrushPrimitive::new_picture(blend_picture);
-
-            current_prim_index = self.prim_store.add_primitive(
-                &LayoutRect::zero(),
-                &max_clip,
-                true,
-                clip_chain_id,
-                spatial_node_index,
-                None,
-                PrimitiveContainer::Brush(blend_prim),
-            );
-        }
-
-        if establishes_3d_context {
-            // If establishing a 3d context, we need to add a picture
-            // that will be the container for all the planes and any
-            // un-transformed content.
-            let mut container_picture = PicturePrimitive::new_image(
-                self.picture_id_generator.next(),
-                None,
-                false,
-                pipeline_id,
-                None,
-                true,
-                requested_raster_space,
-            );
-
-            container_picture.add_primitive(current_prim_index);
-            let container_prim = BrushPrimitive::new_picture(container_picture);
-
-            current_prim_index = self.prim_store.add_primitive(
-                &LayoutRect::zero(),
-                &max_clip,
-                true,
-                clip_chain_id,
-                spatial_node_index,
-                None,
-                PrimitiveContainer::Brush(container_prim),
-            );
-        }
+        let should_isolate = clipping_node.is_some();
 
         // preserve-3d's semantics are to hoist all your children to be your siblings
         // when doing backface-visibility checking, so we need to grab the backface-visibility
         // of the lowest ancestor which *doesn't* preserve-3d, and AND it in with ours.
         //
         // No this isn't obvious or clear, it's just what we worked out over a day of testing.
         // There's probably a bug in here, but I couldn't find it with the examples and tests
         // at my disposal!
@@ -1141,79 +998,243 @@ impl<'a> DisplayListFlattener<'a> {
                 .rfind(|sc| sc.transform_style == TransformStyle::Flat)
                 .map(|sc| sc.is_backface_visible)
                 .unwrap_or(is_backface_visible);
 
         let is_backface_visible = is_backface_visible && ancestor_is_backface_visible;
 
         // Push the SC onto the stack, so we know how to handle things in
         // pop_stacking_context.
-        let sc = FlattenedStackingContext {
-            is_backface_visible,
+        self.sc_stack.push(FlattenedStackingContext {
+            preserve3d_primitives: Vec::new(),
+            normal_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,
             transform_style,
-            establishes_3d_context,
             participating_in_3d_context,
-            leaf_prim_index,
-            root_prim_index: current_prim_index,
-            has_mix_blend_mode: composite_ops.mix_blend_mode.is_some(),
-        };
-
-        self.sc_stack.push(sc);
+            establishes_3d_context,
+        });
     }
 
     pub fn pop_stacking_context(&mut self) {
-        let sc = self.sc_stack.pop().unwrap();
+        let stacking_context = self.sc_stack.pop().unwrap();
+
+        // An arbitrary large clip rect. For now, we don't
+        // specify a clip specific to the stacking context.
+        // However, now that they are represented as Picture
+        // primitives, we can apply any kind of clip mask
+        // to them, as for a normal primitive. This is needed
+        // to correctly handle some CSS cases (see #1957).
+        let max_clip = LayoutRect::max_rect();
+
+        // By default, this picture will be collapsed into
+        // the owning target.
+        let mut composite_mode = if stacking_context.should_isolate {
+            Some(PictureCompositeMode::Blit)
+        } else {
+            None
+        };
+
+        // 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.
+        if stacking_context.participating_in_3d_context {
+            // TODO(gw): For now, as soon as this picture is in
+            //           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.
+            composite_mode = Some(PictureCompositeMode::Blit);
+        }
+
+        let prim_key = PrimitiveKey::new(true);
+        let prim_data_handle = self.resources.prim_interner.intern(&prim_key);
+
+        // Add picture for this actual stacking context contents to render into.
+        let leaf_picture = PicturePrimitive::new_image(
+            self.picture_id_generator.next(),
+            composite_mode,
+            stacking_context.participating_in_3d_context,
+            stacking_context.pipeline_id,
+            stacking_context.frame_output_pipeline_id,
+            true,
+            stacking_context.requested_raster_space,
+            stacking_context.normal_primitives,
+        );
+
+        // Create a brush primitive that draws this picture.
+        let leaf_prim = BrushPrimitive::new_picture(leaf_picture);
+
+        // Add the brush to the parent picture.
+        let leaf_prim_index = self.prim_store.add_primitive(
+            &LayoutRect::zero(),
+            &max_clip,
+            PrimitiveContainer::Brush(leaf_prim),
+        );
+
+        // Create a chain of pictures based on presence of filters,
+        // mix-blend-mode and/or 3d rendering context containers.
+        let mut current_prim_index = leaf_prim_index;
+
+        // For each filter, create a new image with that composite mode.
+        for filter in &stacking_context.composite_ops.filters {
+            let filter = filter.sanitize();
 
-        // Run the optimize pass on each picture in the chain,
-        // to see if we can collapse opacity and avoid drawing
-        // to an off-screen surface.
-        for i in sc.leaf_prim_index.0 .. sc.root_prim_index.0 + 1 {
-            let prim_index = PrimitiveIndex(i);
-            self.prim_store.optimize_picture_if_possible(prim_index);
+            let filter_picture = PicturePrimitive::new_image(
+                self.picture_id_generator.next(),
+                Some(PictureCompositeMode::Filter(filter)),
+                false,
+                stacking_context.pipeline_id,
+                None,
+                true,
+                stacking_context.requested_raster_space,
+                vec![
+                    PrimitiveInstance::new(
+                        current_prim_index,
+                        prim_data_handle,
+                        stacking_context.clip_chain_id,
+                        stacking_context.spatial_node_index,
+                    ),
+                ],
+            );
+
+            let filter_prim = BrushPrimitive::new_picture(filter_picture);
+
+            current_prim_index = self.prim_store.add_primitive(
+                &LayoutRect::zero(),
+                &max_clip,
+                PrimitiveContainer::Brush(filter_prim),
+            );
+
+            // Run the optimize pass on this picture, to see if we can
+            // collapse opacity and avoid drawing to an off-screen surface.
+            self.prim_store.optimize_picture_if_possible(current_prim_index);
+        }
+
+        // Same for mix-blend-mode.
+        if let Some(mix_blend_mode) = stacking_context.composite_ops.mix_blend_mode {
+            let blend_picture = PicturePrimitive::new_image(
+                self.picture_id_generator.next(),
+                Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
+                false,
+                stacking_context.pipeline_id,
+                None,
+                true,
+                stacking_context.requested_raster_space,
+                vec![
+                    PrimitiveInstance::new(
+                        current_prim_index,
+                        prim_data_handle,
+                        stacking_context.clip_chain_id,
+                        stacking_context.spatial_node_index,
+                    ),
+                ],
+            );
+
+            let blend_prim = BrushPrimitive::new_picture(blend_picture);
+
+            current_prim_index = self.prim_store.add_primitive(
+                &LayoutRect::zero(),
+                &max_clip,
+                PrimitiveContainer::Brush(blend_prim),
+            );
+        }
+
+        if stacking_context.establishes_3d_context {
+            // If establishing a 3d context, we need to add a picture
+            // that will be the container for all the planes and any
+            // un-transformed content.
+            let mut prims = vec![
+                PrimitiveInstance::new(
+                    current_prim_index,
+                    prim_data_handle,
+                    stacking_context.clip_chain_id,
+                    stacking_context.spatial_node_index,
+                ),
+            ];
+            prims.extend(stacking_context.preserve3d_primitives);
+
+            let container_picture = PicturePrimitive::new_image(
+                self.picture_id_generator.next(),
+                None,
+                false,
+                stacking_context.pipeline_id,
+                None,
+                true,
+                stacking_context.requested_raster_space,
+                prims,
+            );
+
+            let container_prim = BrushPrimitive::new_picture(container_picture);
+
+            current_prim_index = self.prim_store.add_primitive(
+                &LayoutRect::zero(),
+                &max_clip,
+                PrimitiveContainer::Brush(container_prim),
+            );
+        } else {
+            debug_assert!(stacking_context.preserve3d_primitives.is_empty());
         }
 
         if self.sc_stack.is_empty() {
             // This must be the root stacking context
+            self.root_prim_index = current_prim_index;
             return;
         }
 
-        let parent_prim_index = if !sc.establishes_3d_context && sc.participating_in_3d_context {
+        let sc_count = self.sc_stack.len();
+        let prim_instance = PrimitiveInstance::new(
+            current_prim_index,
+            prim_data_handle,
+            stacking_context.clip_chain_id,
+            stacking_context.spatial_node_index,
+        );
+
+        if !stacking_context.establishes_3d_context && stacking_context.participating_in_3d_context {
             // If we're in a 3D context, we will parent the picture
             // to the first stacking context we find that is a
             // 3D rendering context container. This follows the spec
             // by hoisting these items out into the same 3D context
             // for plane splitting.
-            self.sc_stack
+            let parent_index = self.sc_stack
                 .iter()
-                .rev()
-                .find(|sc| sc.establishes_3d_context)
-                .map(|sc| sc.root_prim_index)
-                .unwrap()
+                .rposition(|sc| sc.establishes_3d_context)
+                .unwrap();
+
+            let parent_stacking_context = &mut self.sc_stack[parent_index];
+            parent_stacking_context.preserve3d_primitives.push(prim_instance);
+
         } else {
-            self.sc_stack.last().unwrap().leaf_prim_index
+            let parent_stacking_context = self.sc_stack.last_mut().unwrap();
+
+            // 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 stacking_context.composite_ops.mix_blend_mode.is_some() &&
+               sc_count > 2 {
+                parent_stacking_context.should_isolate = true;
+            }
+
+            parent_stacking_context.normal_primitives.push(prim_instance);
         };
 
-        let parent_pic = self.prim_store.get_pic_mut(parent_prim_index);
-        parent_pic.add_primitive(sc.root_prim_index);
-
-        // 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 sc.has_mix_blend_mode &&
-           self.sc_stack.len() > 2 &&
-           parent_pic.requested_composite_mode.is_none() {
-            parent_pic.requested_composite_mode = Some(PictureCompositeMode::Blit);
-        }
-
         assert!(
-            self.shadow_stack.is_empty(),
-            "Found unpopped text shadows when popping stacking context!"
+            self.pending_shadow_items.is_empty(),
+            "Found unpopped shadows when popping stacking context!"
         );
     }
 
     pub fn push_reference_frame(
         &mut self,
         reference_frame_id: ClipId,
         parent_id: Option<ClipId>,
         pipeline_id: PipelineId,
@@ -1395,79 +1416,172 @@ impl<'a> DisplayListFlattener<'a> {
         node_index
     }
 
     pub fn push_shadow(
         &mut self,
         shadow: Shadow,
         clip_and_scroll: ScrollNodeAndClipChain,
     ) {
-        let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
-        let max_clip = LayoutRect::max_rect();
-
-        // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
-        // "the image that would be generated by applying to the shadow a
-        // Gaussian blur with a standard deviation equal to half the blur radius."
-        let std_deviation = shadow.blur_radius * 0.5;
-
-        // If the shadow has no blur, any elements will get directly rendered
-        // into the parent picture surface, instead of allocating and drawing
-        // into an intermediate surface. In this case, we will need to apply
-        // the local clip rect to primitives.
-        let is_passthrough = shadow.blur_radius == 0.0;
-
-        // shadows always rasterize in local space.
-        // TODO(gw): expose API for clients to specify a raster scale
-        let raster_space = if is_passthrough {
-            let parent_pic_prim_index = self.sc_stack.last().unwrap().leaf_prim_index;
-            self.prim_store
-                .get_pic(parent_pic_prim_index)
-                .requested_raster_space
-        } else {
-            RasterSpace::Local(1.0)
-        };
-
-        // Create a picture that the shadow primitives will be added to. If the
-        // blur radius is 0, the code in Picture::prepare_for_render will
-        // detect this and mark the picture to be drawn directly into the
-        // parent picture, which avoids an intermediate surface and blur.
-        let blur_filter = FilterOp::Blur(std_deviation).sanitize();
-        let shadow_pic = PicturePrimitive::new_image(
-            self.picture_id_generator.next(),
-            Some(PictureCompositeMode::Filter(blur_filter)),
-            false,
-            pipeline_id,
-            None,
-            is_passthrough,
-            raster_space,
-        );
-
-        // Create the primitive to draw the shadow picture into the scene.
-        let shadow_prim = BrushPrimitive::new_picture(shadow_pic);
-        let shadow_prim_index = self.prim_store.add_primitive(
-            &LayoutRect::zero(),
-            &max_clip,
-            true,
-            clip_and_scroll.clip_chain_id,
-            clip_and_scroll.spatial_node_index,
-            None,
-            PrimitiveContainer::Brush(shadow_prim),
-        );
-
-        // Add the shadow primitive. This must be done before pushing this
-        // picture on to the shadow stack, to avoid infinite recursion!
-        self.add_primitive_to_draw_list(
-            shadow_prim_index,
-        );
-        self.shadow_stack.push((shadow, shadow_prim_index));
+        // Store this shadow in the pending list, for processing
+        // during pop_all_shadows.
+        self.pending_shadow_items.push_back(ShadowItem::Shadow(PendingShadow {
+            shadow,
+            clip_and_scroll,
+        }));
     }
 
     pub fn pop_all_shadows(&mut self) {
-        assert!(self.shadow_stack.len() > 0, "popped shadows, but none were present");
-        self.shadow_stack.clear();
+        assert!(!self.pending_shadow_items.is_empty(), "popped shadows, but none were present");
+
+        let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
+        let max_clip = LayoutRect::max_rect();
+        let mut items = mem::replace(&mut self.pending_shadow_items, VecDeque::new());
+
+        //
+        // The pending_shadow_items queue contains a list of shadows and primitives
+        // that were pushed during the active shadow context. To process these, we:
+        //
+        // Iterate the list, popping an item from the front each iteration.
+        //
+        // If the item is a shadow:
+        //      - Create a shadow picture primitive.
+        //      - Add *any* primitives that remain in the item list to this shadow.
+        // If the item is a primitive:
+        //      - Add that primitive as a normal item (if alpha > 0)
+        //
+
+        while let Some(item) = items.pop_front() {
+            match item {
+                ShadowItem::Shadow(pending_shadow) => {
+                    // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
+                    // "the image that would be generated by applying to the shadow a
+                    // Gaussian blur with a standard deviation equal to half the blur radius."
+                    let std_deviation = pending_shadow.shadow.blur_radius * 0.5;
+
+                    // If the shadow has no blur, any elements will get directly rendered
+                    // into the parent picture surface, instead of allocating and drawing
+                    // into an intermediate surface. In this case, we will need to apply
+                    // the local clip rect to primitives.
+                    let is_passthrough = pending_shadow.shadow.blur_radius == 0.0;
+
+                    // shadows always rasterize in local space.
+                    // TODO(gw): expose API for clients to specify a raster scale
+                    let raster_space = if is_passthrough {
+                        self.sc_stack.last().unwrap().requested_raster_space
+                    } else {
+                        RasterSpace::Local(1.0)
+                    };
+
+                    // Add any primitives that come after this shadow in the item
+                    // list to this shadow.
+                    let mut prims = Vec::new();
+
+                    for item in &items {
+                        if let ShadowItem::Primitive(ref pending_primitive) = item {
+                            // Offset the local rect and clip rect by the shadow offset.
+                            let mut info = pending_primitive.info.clone();
+                            info.rect = info.rect.translate(&pending_shadow.shadow.offset);
+                            info.clip_rect = info.clip_rect.translate(&pending_shadow.shadow.offset);
+
+                            // Offset any local clip sources by the shadow offset.
+                            let clip_items: Vec<ClipItemKey> = pending_primitive
+                                .clip_items
+                                .iter()
+                                .map(|cs| cs.offset(&pending_shadow.shadow.offset))
+                                .collect();
+                            let clip_chain_id = self.build_clip_chain(
+                                clip_items,
+                                pending_primitive.clip_and_scroll.spatial_node_index,
+                                pending_primitive.clip_and_scroll.clip_chain_id,
+                            );
+
+                            // Construct and add a primitive for the given shadow.
+                            let shadow_prim_instance = self.create_primitive(
+                                &info,
+                                clip_chain_id,
+                                pending_primitive.clip_and_scroll.spatial_node_index,
+                                pending_primitive.container.create_shadow(&pending_shadow.shadow),
+                            );
+
+                            // Add the new primitive to the shadow picture.
+                            prims.push(shadow_prim_instance);
+                        }
+                    }
+
+                    // No point in adding a shadow here if there were no primitives
+                    // added to the shadow.
+                    if !prims.is_empty() {
+                        // Create a picture that the shadow primitives will be added to. If the
+                        // blur radius is 0, the code in Picture::prepare_for_render will
+                        // detect this and mark the picture to be drawn directly into the
+                        // parent picture, which avoids an intermediate surface and blur.
+                        let blur_filter = FilterOp::Blur(std_deviation).sanitize();
+                        let mut shadow_pic = PicturePrimitive::new_image(
+                            self.picture_id_generator.next(),
+                            Some(PictureCompositeMode::Filter(blur_filter)),
+                            false,
+                            pipeline_id,
+                            None,
+                            is_passthrough,
+                            raster_space,
+                            prims,
+                        );
+
+                        // Create the primitive to draw the shadow picture into the scene.
+                        let shadow_prim = BrushPrimitive::new_picture(shadow_pic);
+                        let shadow_prim_index = self.prim_store.add_primitive(
+                            &LayoutRect::zero(),
+                            &max_clip,
+                            PrimitiveContainer::Brush(shadow_prim),
+                        );
+
+                        let shadow_prim_key = PrimitiveKey::new(true);
+                        let shadow_prim_data_handle = self.resources.prim_interner.intern(&shadow_prim_key);
+
+                        let shadow_prim_instance = PrimitiveInstance::new(
+                            shadow_prim_index,
+                            shadow_prim_data_handle,
+                            pending_shadow.clip_and_scroll.clip_chain_id,
+                            pending_shadow.clip_and_scroll.spatial_node_index,
+                        );
+
+                        // Add the shadow primitive. This must be done before pushing this
+                        // picture on to the shadow stack, to avoid infinite recursion!
+                        self.add_primitive_to_draw_list(shadow_prim_instance);
+                    }
+                }
+                ShadowItem::Primitive(pending_primitive) => {
+                    // For a normal primitive, if it has alpha > 0, then we add this
+                    // as a normal primitive to the parent picture.
+                    if pending_primitive.container.is_visible() {
+                        let clip_chain_id = self.build_clip_chain(
+                            pending_primitive.clip_items,
+                            pending_primitive.clip_and_scroll.spatial_node_index,
+                            pending_primitive.clip_and_scroll.clip_chain_id,
+                        );
+                        let prim_instance = self.create_primitive(
+                            &pending_primitive.info,
+                            clip_chain_id,
+                            pending_primitive.clip_and_scroll.spatial_node_index,
+                            pending_primitive.container,
+                        );
+                        if cfg!(debug_assertions) && ChasePrimitive::LocalRect(pending_primitive.info.rect) == self.config.chase_primitive {
+                            println!("Chasing {:?} by local rect", prim_instance.prim_index);
+                            self.prim_store.chase_id = Some(prim_instance.prim_index);
+                        }
+                        self.add_primitive_to_hit_testing_list(&pending_primitive.info, pending_primitive.clip_and_scroll);
+                        self.add_primitive_to_draw_list(prim_instance);
+                    }
+                }
+            }
+        }
+
+        debug_assert!(items.is_empty());
+        self.pending_shadow_items = items;
     }
 
     pub fn add_solid_rectangle(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         color: ColorF,
         segments: Option<BrushSegmentDescriptor>,
@@ -2043,26 +2157,78 @@ impl<'a> DisplayListFlattener<'a> {
         self.map_clip_and_scroll(&ClipAndScrollInfo::simple(*id))
     }
 }
 
 /// Properties of a stacking context that are maintained
 /// during creation of the scene. These structures are
 /// not persisted after the initial scene build.
 struct FlattenedStackingContext {
+    /// The list of un-transformed content being
+    /// added to this stacking context.
+    normal_primitives: Vec<PrimitiveInstance>,
+
+    /// The list of preserve-3d primitives that
+    /// are being hoisted to this stacking context
+    /// (implies establishes_3d_context).
+    preserve3d_primitives: Vec<PrimitiveInstance>,
+
+    /// Whether or not the caller wants this drawn in
+    /// screen space (quality) or local space (performance)
+    requested_raster_space: RasterSpace,
+
+    /// The positioning node for this stacking context
+    spatial_node_index: SpatialNodeIndex,
+
+    /// The clip chain for this stacking context
+    clip_chain_id: ClipChainId,
+
+    /// 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,
+
     /// Pipeline this stacking context belongs to.
     pipeline_id: PipelineId,
 
     /// If true, visible when backface is visible.
     is_backface_visible: bool,
 
     /// CSS transform-style property.
     transform_style: TransformStyle,
 
-    root_prim_index: PrimitiveIndex,
-    leaf_prim_index: PrimitiveIndex,
-
     /// If true, this stacking context establishes a new
     /// 3d rendering context.
     establishes_3d_context: bool,
+
+    /// If true, this stacking context is part of a
+    /// surrounding 3d rendering context.
     participating_in_3d_context: bool,
-    has_mix_blend_mode: bool,
+}
+
+/// A primitive that is added while a shadow context is
+/// active is stored as a pending primitive and only
+/// added to pictures during pop_all_shadows.
+struct PendingPrimitive {
+    clip_and_scroll: ScrollNodeAndClipChain,
+    info: LayoutPrimitiveInfo,
+    clip_items: Vec<ClipItemKey>,
+    container: PrimitiveContainer,
 }
+
+/// As shadows are pushed, they are stored as pending
+/// shadows, and handled at once during pop_all_shadows.
+struct PendingShadow {
+    shadow: Shadow,
+    clip_and_scroll: ScrollNodeAndClipChain,
+}
+
+enum ShadowItem {
+    Shadow(PendingShadow),
+    Primitive(PendingPrimitive),
+}
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -53,16 +53,17 @@ pub struct FrameBuilderConfig {
 }
 
 /// A builder structure for `tiling::Frame`
 pub struct FrameBuilder {
     screen_rect: DeviceUintRect,
     background_color: Option<ColorF>,
     window_size: DeviceUintSize,
     scene_id: u64,
+    root_prim_index: PrimitiveIndex,
     pub prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     pub hit_testing_runs: Vec<HitTestingRun>,
     pub config: FrameBuilderConfig,
 }
 
 pub struct FrameBuildingContext<'a> {
     pub scene_id: u64,
@@ -132,16 +133,17 @@ impl FrameBuilder {
         FrameBuilder {
             hit_testing_runs: Vec::new(),
             prim_store: PrimitiveStore::new(),
             clip_store: ClipStore::new(),
             screen_rect: DeviceUintRect::zero(),
             window_size: DeviceUintSize::zero(),
             background_color: None,
             scene_id: 0,
+            root_prim_index: PrimitiveIndex(0),
             config: FrameBuilderConfig {
                 default_font_render_mode: FontRenderMode::Mono,
                 dual_source_blending_is_enabled: true,
                 dual_source_blending_is_supported: false,
                 chase_primitive: ChasePrimitive::Nothing,
             },
         }
     }
@@ -152,16 +154,17 @@ impl FrameBuilder {
         window_size: DeviceUintSize,
         scene_id: u64,
         flattener: DisplayListFlattener,
     ) -> Self {
         FrameBuilder {
             hit_testing_runs: flattener.hit_testing_runs,
             prim_store: flattener.prim_store,
             clip_store: flattener.clip_store,
+            root_prim_index: flattener.root_prim_index,
             screen_rect,
             background_color,
             window_size,
             scene_id,
             config: flattener.config,
         }
     }
 
@@ -181,20 +184,17 @@ impl FrameBuilder {
         transform_palette: &mut TransformPalette,
         resources: &mut FrameResources,
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
         if self.prim_store.primitives.is_empty() {
             return None
         }
-        self.prim_store.reset_prim_visibility();
 
-        // The root picture is always the first one added.
-        let root_prim_index = PrimitiveIndex(0);
         let root_spatial_node_index = clip_scroll_tree.root_reference_frame_index();
 
         const MAX_CLIP_COORD: f32 = 1.0e9;
 
         let world_rect = (self.screen_rect.to_f32() / device_pixel_scale).round_out();
 
         let frame_context = FrameBuildingContext {
             scene_id: self.scene_id,
@@ -223,17 +223,17 @@ impl FrameBuilder {
 
         let prim_context = PrimitiveContext::new(
             &clip_scroll_tree.spatial_nodes[root_spatial_node_index.0],
             root_spatial_node_index,
         );
 
         let (pic_context, mut pic_state, mut instances) = self
             .prim_store
-            .get_pic_mut(root_prim_index)
+            .get_pic_mut(self.root_prim_index)
             .take_context(
                 &prim_context,
                 root_spatial_node_index,
                 root_spatial_node_index,
                 true,
                 &mut frame_state,
                 &frame_context,
                 false,
@@ -248,31 +248,31 @@ impl FrameBuilder {
             &mut pic_state,
             &frame_context,
             &mut frame_state,
             &mut pic_rect,
         );
 
         let pic = self
             .prim_store
-            .get_pic_mut(root_prim_index);
+            .get_pic_mut(self.root_prim_index);
         pic.restore_context(
             instances,
             pic_context,
             pic_state,
             Some(pic_rect),
             &mut frame_state,
         );
 
         let pic_state = pic.take_state();
 
         let root_render_task = RenderTask::new_picture(
             RenderTaskLocation::Fixed(self.screen_rect.to_i32()),
             self.screen_rect.size.to_f32(),
-            root_prim_index,
+            self.root_prim_index,
             DeviceIntPoint::zero(),
             pic_state.tasks,
             UvRectKind::Rect,
             root_spatial_node_index,
         );
 
         let render_task_id = frame_state.render_tasks.add(root_render_task);
         pic.raster_config = Some(RasterConfig {
--- a/gfx/webrender/src/glyph_cache.rs
+++ b/gfx/webrender/src/glyph_cache.rs
@@ -1,15 +1,15 @@
 /* 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/. */
 
 #[cfg(feature = "pathfinder")]
 use api::DeviceIntPoint;
-use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey};
+use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
 use internal_types::FastHashMap;
 use render_task::RenderTaskCache;
 #[cfg(feature = "pathfinder")]
 use render_task::RenderTaskCacheKey;
 use resource_cache::ResourceClassCache;
 use std::sync::Arc;
 use texture_cache::{EvictionNotice, TextureCache};
 #[cfg(not(feature = "pathfinder"))]
@@ -138,36 +138,43 @@ impl GlyphCache {
 
             cache.clear();
             false
         })
     }
 
     // Clear out evicted entries from glyph key caches and, if possible,
     // also remove entirely any subsequently empty glyph key caches.
-    fn clear_evicted(&mut self,
-                     texture_cache: &TextureCache,
-                     render_task_cache: &RenderTaskCache) {
-        self.glyph_key_caches.retain(|_, cache| {
+    fn clear_evicted(
+        &mut self,
+        texture_cache: &TextureCache,
+        render_task_cache: &RenderTaskCache,
+        glyph_rasterizer: &mut GlyphRasterizer,
+    ) {
+        self.glyph_key_caches.retain(|key, cache| {
             // Scan for any glyph key caches that have evictions.
             if cache.eviction_notice().check() {
                 // If there are evictions, filter out any glyphs evicted from the
                 // texture cache from the glyph key cache.
                 let mut keep_cache = false;
                 cache.retain(|_, entry| {
                     let keep_glyph = entry.is_allocated(texture_cache, render_task_cache);
                     keep_cache |= keep_glyph;
                     keep_glyph
                 });
+                if !keep_cache {
+                    glyph_rasterizer.delete_font_instance(key);
+                }
                 // Only keep the glyph key cache if it still has valid glyphs.
                 keep_cache
             } else {
                 true
             }
         });
     }
 
     pub fn begin_frame(&mut self,
                        texture_cache: &TextureCache,
-                       render_task_cache: &RenderTaskCache) {
-        self.clear_evicted(texture_cache, render_task_cache);
+                       render_task_cache: &RenderTaskCache,
+                       glyph_rasterizer: &mut GlyphRasterizer) {
+        self.clear_evicted(texture_cache, render_task_cache, glyph_rasterizer);
     }
 }
--- a/gfx/webrender/src/glyph_rasterizer/mod.rs
+++ b/gfx/webrender/src/glyph_rasterizer/mod.rs
@@ -540,16 +540,18 @@ pub struct GlyphRasterizer {
     #[allow(dead_code)]
     glyph_tx: Sender<GlyphRasterJobs>,
 
     // We defer removing fonts to the end of the frame so that:
     // - this work is done outside of the critical path,
     // - we don't have to worry about the ordering of events if a font is used on
     //   a frame where it is used (although it seems unlikely).
     fonts_to_remove: Vec<FontKey>,
+    // Defer removal of font instances, as for fonts.
+    font_instances_to_remove: Vec<FontInstance>,
 
     #[allow(dead_code)]
     next_gpu_glyph_cache_key: GpuGlyphCacheKey,
 }
 
 impl GlyphRasterizer {
     pub fn new(workers: Arc<ThreadPool>) -> Result<Self, ResourceCacheError> {
         let (glyph_tx, glyph_rx) = channel();
@@ -575,16 +577,17 @@ impl GlyphRasterizer {
 
         Ok(GlyphRasterizer {
             font_contexts: Arc::new(font_context),
             pending_glyphs: 0,
             glyph_rx,
             glyph_tx,
             workers,
             fonts_to_remove: Vec::new(),
+            font_instances_to_remove: Vec::new(),
             next_gpu_glyph_cache_key: GpuGlyphCacheKey(0),
         })
     }
 
     pub fn add_font(&mut self, font_key: FontKey, template: FontTemplate) {
         #[cfg(feature = "pathfinder")]
         self.add_font_to_pathfinder(&font_key, &template);
 
@@ -592,16 +595,20 @@ impl GlyphRasterizer {
             context.add_font(&font_key, &template);
         });
     }
 
     pub fn delete_font(&mut self, font_key: FontKey) {
         self.fonts_to_remove.push(font_key);
     }
 
+    pub fn delete_font_instance(&mut self, instance: &FontInstance) {
+        self.font_instances_to_remove.push(instance.clone());
+    }
+
     pub fn prepare_font(&self, font: &mut FontInstance) {
         FontContext::prepare_font(font);
     }
 
     pub fn get_glyph_dimensions(
         &mut self,
         font: &FontInstance,
         glyph_index: GlyphIndex,
@@ -619,33 +626,38 @@ impl GlyphRasterizer {
 
     pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
         self.font_contexts
             .lock_shared_context()
             .get_glyph_index(font_key, ch)
     }
 
     fn remove_dead_fonts(&mut self) {
-        if self.fonts_to_remove.is_empty() {
+        if self.fonts_to_remove.is_empty() && self.font_instances_to_remove.is_empty() {
             return
         }
 
         let fonts_to_remove = mem::replace(&mut self.fonts_to_remove, Vec::new());
+        let font_instances_to_remove = mem::replace(& mut self.font_instances_to_remove, Vec::new());
         self.font_contexts.for_each(move |mut context| {
             for font_key in &fonts_to_remove {
                 context.delete_font(font_key);
             }
+            for instance in &font_instances_to_remove {
+                context.delete_font_instance(instance);
+            }
         });
     }
 
     #[cfg(feature = "replay")]
     pub fn reset(&mut self) {
         //TODO: any signals need to be sent to the workers?
         self.pending_glyphs = 0;
         self.fonts_to_remove.clear();
+        self.font_instances_to_remove.clear();
     }
 }
 
 trait AddFont {
     fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate);
 }
 
 impl AddFont for FontContext {
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -145,16 +145,17 @@ pub enum DebugOutput {
     FetchDocuments(String),
     FetchClipScrollTree(String),
     #[cfg(feature = "capture")]
     SaveCapture(CaptureConfig, Vec<ExternalCaptureImage>),
     #[cfg(feature = "replay")]
     LoadCapture(PathBuf, Vec<PlainExternalImage>),
 }
 
+#[allow(dead_code)]
 pub enum ResultMsg {
     DebugCommand(DebugCommand),
     DebugOutput(DebugOutput),
     RefreshShader(PathBuf),
     UpdateGpuCache(GpuCacheUpdateList),
     UpdateResources {
         updates: TextureUpdateList,
         memory_pressure: bool,
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -177,16 +177,18 @@ extern crate base64;
 #[cfg(all(feature = "capture", feature = "png"))]
 extern crate png;
 
 pub extern crate webrender_api;
 
 #[doc(hidden)]
 pub use device::{build_shader_strings, ReadPixelsFormat, UploadMethod, VertexUsageHint};
 pub use device::{ProgramBinary, ProgramCache, ProgramCacheObserver, ProgramSources};
+pub use device::{Device};
 pub use frame_builder::ChasePrimitive;
 pub use renderer::{AsyncPropertySampler, CpuProfile, DebugFlags, OutputImageHandler, RendererKind};
 pub use renderer::{ExternalImage, ExternalImageHandler, ExternalImageSource, GpuProfile};
 pub use renderer::{GraphicsApi, GraphicsApiInfo, PipelineInfo, Renderer, RendererOptions};
 pub use renderer::{RendererStats, SceneBuilderHooks, ThreadListener, ShaderPrecacheFlags};
 pub use renderer::MAX_VERTEX_TEXTURE_WIDTH;
+pub use shade::{Shaders, WrShaders};
 pub use webrender_api as api;
 pub use resource_cache::intersect_for_tile;
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -9,17 +9,17 @@ use api::{PicturePixel, RasterPixel, Wor
 use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip::ClipNodeCollector;
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use euclid::TypedScale;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState};
 use frame_builder::{PictureContext, PrimitiveContext};
 use gpu_cache::{GpuCacheHandle};
 use gpu_types::UvRectKind;
-use prim_store::{PrimitiveIndex, PrimitiveInstance, SpaceMapper};
+use prim_store::{PrimitiveInstance, SpaceMapper};
 use prim_store::{PrimitiveMetadata, get_raster_rects};
 use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle};
 use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use scene::{FilterOpHelpers, SceneProperties};
 use std::mem;
 use tiling::RenderTargetKind;
 use util::{TransformedRectKind, MatrixHelpers, MaxRect};
 
@@ -217,19 +217,20 @@ impl PicturePrimitive {
     pub fn new_image(
         id: PictureId,
         requested_composite_mode: Option<PictureCompositeMode>,
         is_in_3d_context: bool,
         pipeline_id: PipelineId,
         frame_output_pipeline_id: Option<PipelineId>,
         apply_local_clip_rect: bool,
         requested_raster_space: RasterSpace,
+        prim_instances: Vec<PrimitiveInstance>,
     ) -> Self {
         PicturePrimitive {
-            prim_instances: Vec::new(),
+            prim_instances,
             state: None,
             secondary_render_task_id: None,
             requested_composite_mode,
             raster_config: None,
             is_in_3d_context,
             frame_output_pipeline_id,
             extra_gpu_data_handle: GpuCacheHandle::new(),
             apply_local_clip_rect,
@@ -373,26 +374,16 @@ impl PicturePrimitive {
             raster_space,
         };
 
         let instances = mem::replace(&mut self.prim_instances, Vec::new());
 
         Some((context, state, instances))
     }
 
-    pub fn add_primitive(
-        &mut self,
-        prim_index: PrimitiveIndex,
-    ) {
-        self.prim_instances.push(PrimitiveInstance {
-            prim_index,
-            combined_local_clip_rect: LayoutRect::zero(),
-        });
-    }
-
     pub fn restore_context(
         &mut self,
         prim_instances: Vec<PrimitiveInstance>,
         context: PictureContext,
         state: PictureState,
         local_rect: Option<PictureRect>,
         frame_state: &mut FrameBuildingState,
     ) -> (LayoutRect, Option<ClipNodeCollector>) {
@@ -443,39 +434,39 @@ impl PicturePrimitive {
     }
 
     pub fn take_state(&mut self) -> PictureState {
         self.state.take().expect("bug: no state present!")
     }
 
     pub fn prepare_for_render(
         &mut self,
-        prim_index: PrimitiveIndex,
-        prim_metadata: &mut PrimitiveMetadata,
+        prim_instance: &PrimitiveInstance,
+        prim_metadata: &PrimitiveMetadata,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) -> bool {
         let mut pic_state_for_children = self.take_state();
 
         match self.raster_config {
             Some(ref mut raster_config) => {
                 let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
-                    prim_metadata.spatial_node_index,
+                    prim_instance.spatial_node_index,
                     raster_config.raster_spatial_node_index,
                     frame_context,
                 );
 
                 let pic_rect = PictureRect::from_untyped(&prim_metadata.local_rect.to_untyped());
 
                 let (clipped, unclipped, transform) = match get_raster_rects(
                     pic_rect,
                     &map_pic_to_raster,
                     &map_raster_to_world,
-                    prim_metadata.clipped_world_rect.expect("bug1"),
+                    prim_instance.clipped_world_rect.expect("bug1"),
                     frame_context.device_pixel_scale,
                 ) {
                     Some(info) => info,
                     None => return false,
                 };
 
                 // TODO(gw): Almost all of the Picture types below use extra_gpu_cache_data
                 //           to store the same type of data. The exception is the filter
@@ -515,17 +506,17 @@ impl PicturePrimitive {
                         // anyway. In the future we should relax this a bit, so that we can
                         // cache tasks with complex coordinate systems if we detect the
                         // relevant transforms haven't changed from frame to frame.
                         let surface = if pic_state_for_children.has_non_root_coord_system ||
                                          !pic_state_for_children.is_cacheable {
                             let picture_task = RenderTask::new_picture(
                                 RenderTaskLocation::Dynamic(None, device_rect.size),
                                 unclipped.size,
-                                prim_index,
+                                prim_instance.prim_index,
                                 device_rect.origin,
                                 pic_state_for_children.tasks,
                                 uv_rect_kind,
                                 pic_state_for_children.raster_spatial_node_index,
                             );
 
                             let picture_task_id = frame_state.render_tasks.add(picture_task);
 
@@ -573,17 +564,17 @@ impl PicturePrimitive {
                                 None,
                                 false,
                                 |render_tasks| {
                                     let child_tasks = mem::replace(&mut pic_state_for_children.tasks, Vec::new());
 
                                     let picture_task = RenderTask::new_picture(
                                         RenderTaskLocation::Dynamic(None, device_rect.size),
                                         unclipped.size,
-                                        prim_index,
+                                        prim_instance.prim_index,
                                         device_rect.origin,
                                         child_tasks,
                                         uv_rect_kind,
                                         pic_state_for_children.raster_spatial_node_index,
                                     );
 
                                     let picture_task_id = render_tasks.add(picture_task);
 
@@ -630,17 +621,17 @@ impl PicturePrimitive {
                             &transform,
                             &device_rect,
                             frame_context.device_pixel_scale,
                         );
 
                         let mut picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, device_rect.size),
                             unclipped.size,
-                            prim_index,
+                            prim_instance.prim_index,
                             device_rect.origin,
                             pic_state_for_children.tasks,
                             uv_rect_kind,
                             pic_state_for_children.raster_spatial_node_index,
                         );
                         picture_task.mark_for_saving();
 
                         let picture_task_id = frame_state.render_tasks.add(picture_task);
@@ -701,17 +692,17 @@ impl PicturePrimitive {
                             &transform,
                             &clipped,
                             frame_context.device_pixel_scale,
                         );
 
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, clipped.size),
                             unclipped.size,
-                            prim_index,
+                            prim_instance.prim_index,
                             clipped.origin,
                             pic_state_for_children.tasks,
                             uv_rect_kind,
                             pic_state_for_children.raster_spatial_node_index,
                         );
 
                         let readback_task_id = frame_state.render_tasks.add(
                             RenderTask::new_readback(clipped)
@@ -738,17 +729,17 @@ impl PicturePrimitive {
                             &transform,
                             &clipped,
                             frame_context.device_pixel_scale,
                         );
 
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, clipped.size),
                             unclipped.size,
-                            prim_index,
+                            prim_instance.prim_index,
                             clipped.origin,
                             pic_state_for_children.tasks,
                             uv_rect_kind,
                             pic_state_for_children.raster_spatial_node_index,
                         );
 
                         let render_task_id = frame_state.render_tasks.add(picture_task);
                         pic_state.tasks.push(render_task_id);
@@ -760,17 +751,17 @@ impl PicturePrimitive {
                             &transform,
                             &clipped,
                             frame_context.device_pixel_scale,
                         );
 
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, clipped.size),
                             unclipped.size,
-                            prim_index,
+                            prim_instance.prim_index,
                             clipped.origin,
                             pic_state_for_children.tasks,
                             uv_rect_kind,
                             pic_state_for_children.raster_spatial_node_index,
                         );
 
                         let render_task_id = frame_state.render_tasks.add(picture_task);
                         pic_state.tasks.push(render_task_id);
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -320,16 +320,21 @@ impl FontContext {
     }
 
     pub fn delete_font(&mut self, font_key: &FontKey) {
         if let Some(_) = self.cg_fonts.remove(font_key) {
             self.ct_fonts.retain(|k, _| k.0 != *font_key);
         }
     }
 
+    pub fn delete_font_instance(&mut self, instance: &FontInstance) {
+        // Remove the CoreText font corresponding to this instance.
+        self.ct_fonts.remove(&(instance.font_key, instance.size, instance.variations.clone()));
+    }
+
     fn get_ct_font(
         &mut self,
         font_key: FontKey,
         size: Au,
         variations: &[FontVariation],
     ) -> Option<CTFont> {
         match self.ct_fonts.entry((font_key, size, variations.to_vec())) {
             Entry::Occupied(entry) => Some((*entry.get()).clone()),
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -235,16 +235,20 @@ impl FontContext {
 
     pub fn delete_font(&mut self, font_key: &FontKey) {
         if let Some(face) = self.faces.remove(font_key) {
             let result = unsafe { FT_Done_Face(face.face) };
             assert!(succeeded(result));
         }
     }
 
+    pub fn delete_font_instance(&mut self, _instance: &FontInstance) {
+        // This backend does not yet support variations, so there is nothing to do here.
+    }
+
     fn load_glyph(&self, font: &FontInstance, glyph: &GlyphKey) -> Option<(FT_GlyphSlot, f32)> {
         debug_assert!(self.faces.contains_key(&font.font_key));
         let face = self.faces.get(&font.font_key).unwrap();
 
         let mut load_flags = FT_LOAD_DEFAULT;
         let FontInstancePlatformOptions { mut hinting, .. } = font.platform_options.unwrap_or_default();
         // Disable hinting if there is a non-axis-aligned transform.
         if font.synthetic_italics.is_enabled() ||
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -140,16 +140,28 @@ impl FontContext {
     }
 
     pub fn delete_font(&mut self, font_key: &FontKey) {
         if let Some(_) = self.fonts.remove(font_key) {
             self.variations.retain(|k, _| k.0 != *font_key);
         }
     }
 
+    pub fn delete_font_instance(&mut self, instance: &FontInstance) {
+        // Ensure we don't keep around excessive amounts of stale variations.
+        if !instance.variations.is_empty() {
+            let sims = if instance.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) {
+                dwrote::DWRITE_FONT_SIMULATIONS_BOLD
+            } else {
+                dwrote::DWRITE_FONT_SIMULATIONS_NONE
+            };
+            self.variations.remove(&(instance.font_key, sims, instance.variations.clone()));
+        }
+    }
+
     // Assumes RGB format from dwrite, which is 3 bytes per pixel as dwrite
     // doesn't output an alpha value via GlyphRunAnalysis::CreateAlphaTexture
     #[allow(dead_code)]
     fn print_glyph_data(&self, data: &[u8], width: usize, height: usize) {
         // Rust doesn't have step_by support on stable :(
         for i in 0 .. height {
             let current_height = i * width * 3;
 
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,31 +1,32 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipMode, ColorF, PictureRect};
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode, DeviceRect, PictureToRasterTransform};
-use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, TileOffset};
+use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, TileOffset};
 use api::{RasterSpace, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize, LayoutToWorldTransform};
 use api::{LayoutVector2D, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat};
 use api::{DeviceIntSideOffsets, WorldPixel, BoxShadowClipMode, LayoutToWorldScale, NormalBorder, WorldRect};
 use api::{PicturePixel, RasterPixel, ColorDepth};
 use app_units::Au;
 use border::{BorderCacheKey, BorderRenderTaskInfo};
 use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, SpatialNodeIndex};
 use clip::{ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector};
 use euclid::{TypedTransform3D, TypedRect};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveContext;
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::BrushFlags;
 use image::{for_each_tile, for_each_repetition};
+use intern;
 use picture::{PictureCompositeMode, PicturePrimitive};
 #[cfg(debug_assertions)]
 use render_backend::FrameId;
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
 use scene::SceneProperties;
@@ -233,42 +234,63 @@ impl GpuCacheAddress {
     pub fn as_int(&self) -> i32 {
         // TODO(gw): Temporarily encode GPU Cache addresses as a single int.
         //           In the future, we can change the PrimitiveInstanceData struct
         //           to use 2x u16 for the vertex attribute instead of an i32.
         self.v as i32 * MAX_VERTEX_TEXTURE_WIDTH as i32 + self.u as i32
     }
 }
 
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct PrimitiveKey {
+    pub is_backface_visible: bool,
+}
+
+impl PrimitiveKey {
+    pub fn new(
+        is_backface_visible: bool,
+    ) -> Self {
+        PrimitiveKey {
+            is_backface_visible,
+        }
+    }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct PrimitiveTemplate {
+    pub is_backface_visible: bool,
+}
+
+impl From<PrimitiveKey> for PrimitiveTemplate {
+    fn from(item: PrimitiveKey) -> Self {
+        PrimitiveTemplate {
+            is_backface_visible: item.is_backface_visible,
+        }
+    }
+}
+
+// Type definitions for interning primitives.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone, Copy, Debug)]
+pub struct PrimitiveDataMarker;
+
+pub type PrimitiveDataStore = intern::DataStore<PrimitiveKey, PrimitiveTemplate, PrimitiveDataMarker>;
+pub type PrimitiveDataHandle = intern::Handle<PrimitiveDataMarker>;
+pub type PrimitiveDataUpdateList = intern::UpdateList<PrimitiveKey>;
+pub type PrimitiveDataInterner = intern::Interner<PrimitiveKey, PrimitiveDataMarker>;
+
 // TODO(gw): Pack the fields here better!
 #[derive(Debug)]
 pub struct PrimitiveMetadata {
-    pub opacity: PrimitiveOpacity,
-    pub clip_chain_id: ClipChainId,
-    pub spatial_node_index: SpatialNodeIndex,
-    pub gpu_location: GpuCacheHandle,
-    pub clip_task_id: Option<RenderTaskId>,
-
-    // TODO(gw): In the future, we should just pull these
-    //           directly from the DL item, instead of
-    //           storing them here.
     pub local_rect: LayoutRect,
     pub local_clip_rect: LayoutRect,
-
-    pub is_backface_visible: bool,
-    pub clipped_world_rect: Option<WorldRect>,
-
-    /// A tag used to identify this primitive outside of WebRender. This is
-    /// used for returning useful data during hit testing.
-    pub tag: Option<ItemTag>,
-
-    /// The last frame ID (of the `RenderTaskTree`) this primitive
-    /// was prepared for rendering in.
-    #[cfg(debug_assertions)]
-    pub prepared_frame_id: FrameId,
 }
 
 // Maintains a list of opacity bindings that have been collapsed into
 // the color of a single primitive. This is an important optimization
 // that avoids allocating an intermediate surface for most common
 // uses of opacity filters.
 #[derive(Debug)]
 pub struct OpacityBinding {
@@ -1411,21 +1433,80 @@ impl Primitive {
                 panic!("bug: not a picture!");
             }
         }
     }
 }
 
 #[derive(Debug)]
 pub struct PrimitiveInstance {
+    /// Index into the prim store containing information about
+    /// the specific primitive. This will be removed once all
+    /// primitive data is interned.
     pub prim_index: PrimitiveIndex,
 
-    // The current combined local clip for this primitive, from
-    // the primitive local clip above and the current clip chain.
+    /// Handle to the common interned data for this primitive.
+    pub prim_data_handle: PrimitiveDataHandle,
+
+    /// The current combined local clip for this primitive, from
+    /// the primitive local clip above and the current clip chain.
     pub combined_local_clip_rect: LayoutRect,
+
+    /// The last frame ID (of the `RenderTaskTree`) this primitive
+    /// was prepared for rendering in.
+    #[cfg(debug_assertions)]
+    pub prepared_frame_id: FrameId,
+
+    /// The current world rect of this primitive, clipped to the
+    /// world rect of the screen. None means the primitive is
+    /// completely off-screen.
+    pub clipped_world_rect: Option<WorldRect>,
+
+    /// If this primitive has a global clip mask, this identifies
+    /// the render task for it.
+    pub clip_task_id: Option<RenderTaskId>,
+
+    /// The main GPU cache handle that this primitive uses to
+    /// store data accessible to shaders. This should be moved
+    /// into the interned data in order to retain this between
+    /// display list changes, but needs to be split into shared
+    /// and per-instance data.
+    pub gpu_location: GpuCacheHandle,
+
+    /// The current opacity of the primitive contents.
+    pub opacity: PrimitiveOpacity,
+
+    /// ID of the clip chain that this primitive is clipped by.
+    pub clip_chain_id: ClipChainId,
+
+    /// ID of the spatial node that this primitive is positioned by.
+    pub spatial_node_index: SpatialNodeIndex,
+}
+
+impl PrimitiveInstance {
+    pub fn new(
+        prim_index: PrimitiveIndex,
+        prim_data_handle: PrimitiveDataHandle,
+        clip_chain_id: ClipChainId,
+        spatial_node_index: SpatialNodeIndex,
+    ) -> Self {
+        PrimitiveInstance {
+            prim_index,
+            prim_data_handle,
+            combined_local_clip_rect: LayoutRect::zero(),
+            clipped_world_rect: None,
+            #[cfg(debug_assertions)]
+            prepared_frame_id: FrameId(0),
+            clip_task_id: None,
+            gpu_location: GpuCacheHandle::new(),
+            opacity: PrimitiveOpacity::translucent(),
+            clip_chain_id,
+            spatial_node_index,
+        }
+    }
 }
 
 pub struct PrimitiveStore {
     pub primitives: Vec<Primitive>,
 
     /// A primitive index to chase through debugging.
     pub chase_id: Option<PrimitiveIndex>,
 }
@@ -1445,78 +1526,38 @@ impl PrimitiveStore {
     pub fn get_pic_mut(&mut self, index: PrimitiveIndex) -> &mut PicturePrimitive {
         self.primitives[index.0].as_pic_mut()
     }
 
     pub fn add_primitive(
         &mut self,
         local_rect: &LayoutRect,
         local_clip_rect: &LayoutRect,
-        is_backface_visible: bool,
-        clip_chain_id: ClipChainId,
-        spatial_node_index: SpatialNodeIndex,
-        tag: Option<ItemTag>,
         container: PrimitiveContainer,
     ) -> PrimitiveIndex {
         let prim_index = self.primitives.len();
 
         let base_metadata = PrimitiveMetadata {
-            clip_chain_id,
-            gpu_location: GpuCacheHandle::new(),
-            clip_task_id: None,
-            spatial_node_index,
             local_rect: *local_rect,
             local_clip_rect: *local_clip_rect,
-            is_backface_visible,
-            clipped_world_rect: None,
-            tag,
-            opacity: PrimitiveOpacity::translucent(),
-            #[cfg(debug_assertions)]
-            prepared_frame_id: FrameId(0),
         };
 
         let prim = match container {
             PrimitiveContainer::Brush(brush) => {
-                let opacity = match brush.kind {
-                    BrushKind::Clear => PrimitiveOpacity::translucent(),
-                    BrushKind::Solid { ref color, .. } => PrimitiveOpacity::from_alpha(color.a),
-                    BrushKind::Image { .. } => PrimitiveOpacity::translucent(),
-                    BrushKind::YuvImage { .. } => PrimitiveOpacity::opaque(),
-                    BrushKind::RadialGradient { .. } => PrimitiveOpacity::translucent(),
-                    BrushKind::LinearGradient { stretch_size, tile_spacing, stops_opacity, .. } => {
-                        // If the coverage of the gradient extends to or beyond
-                        // the primitive rect, then the opacity can be determined
-                        // by the colors of the stops. If we have tiling / spacing
-                        // then we just assume the gradient is translucent for now.
-                        // (In the future we could consider segmenting in some cases).
-                        let stride = stretch_size + tile_spacing;
-                        if stride.width >= local_rect.size.width &&
-                           stride.height >= local_rect.size.height {
-                            stops_opacity
-                        } else {
-                            PrimitiveOpacity::translucent()
-                        }
-                    }
-                    BrushKind::Picture { .. } => PrimitiveOpacity::translucent(),
-                    BrushKind::Border { .. } => PrimitiveOpacity::translucent(),
-                };
-
                 let metadata = PrimitiveMetadata {
-                    opacity,
                     ..base_metadata
                 };
 
                 Primitive {
                     metadata,
                     details: PrimitiveDetails::Brush(brush),
                 }
             }
             PrimitiveContainer::TextRun(text_cpu) => {
                 let metadata = PrimitiveMetadata {
-                    opacity: PrimitiveOpacity::translucent(),
                     ..base_metadata
                 };
 
                 Primitive {
                     metadata,
                     details: PrimitiveDetails::TextRun(text_cpu),
                 }
             }
@@ -1718,17 +1759,17 @@ impl PrimitiveStore {
                         pic_context_for_children,
                         pic_state_for_children,
                         pic_rect,
                         frame_state,
                     );
 
                 if new_local_rect != prim.metadata.local_rect {
                     prim.metadata.local_rect = new_local_rect;
-                    frame_state.gpu_cache.invalidate(&mut prim.metadata.gpu_location);
+                    frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
                     pic_state.local_rect_changed = true;
                 }
 
                 (is_passthrough, clip_node_collector)
             }
             None => {
                 (false, None)
             }
@@ -1736,17 +1777,17 @@ impl PrimitiveStore {
 
         let prim = &mut self.primitives[prim_instance.prim_index.0];
 
         if !prim.is_cacheable(frame_state.resource_cache) {
             pic_state.is_cacheable = false;
         }
 
         if is_passthrough {
-            prim.metadata.clipped_world_rect = Some(pic_state.map_pic_to_world.bounds);
+            prim_instance.clipped_world_rect = Some(pic_state.map_pic_to_world.bounds);
         } else {
             if prim.metadata.local_rect.size.width <= 0.0 ||
                prim.metadata.local_rect.size.height <= 0.0 {
                 if cfg!(debug_assertions) && is_chased {
                     println!("\tculled for zero local rectangle");
                 }
                 return false;
             }
@@ -1769,17 +1810,17 @@ impl PrimitiveStore {
                     }
                     return false;
                 }
             };
 
             let clip_chain = frame_state
                 .clip_store
                 .build_clip_chain_instance(
-                    prim.metadata.clip_chain_id,
+                    prim_instance.clip_chain_id,
                     local_rect,
                     prim.metadata.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,
@@ -1787,17 +1828,17 @@ impl PrimitiveStore {
                     &frame_context.world_rect,
                     &clip_node_collector,
                     &mut frame_state.resources.clip_data_store,
                 );
 
             let clip_chain = match clip_chain {
                 Some(clip_chain) => clip_chain,
                 None => {
-                    prim.metadata.clipped_world_rect = None;
+                    prim_instance.clipped_world_rect = None;
                     return false;
                 }
             };
 
             if cfg!(debug_assertions) && is_chased {
                 println!("\teffective clip chain from {:?} {}",
                     clip_chain.clips_range,
                     if pic_context.apply_local_clip_rect { "(applied)" } else { "" },
@@ -1830,25 +1871,27 @@ impl PrimitiveStore {
 
             let clipped_world_rect = match world_rect.intersection(&frame_context.world_rect) {
                 Some(rect) => rect,
                 None => {
                     return false;
                 }
             };
 
-            prim.metadata.clipped_world_rect = Some(clipped_world_rect);
+            prim_instance.clipped_world_rect = Some(clipped_world_rect);
 
             prim.build_prim_segments_if_needed(
+                prim_instance,
                 pic_state,
                 frame_state,
                 frame_context,
             );
 
             prim.update_clip_task(
+                prim_instance,
                 prim_context,
                 clipped_world_rect,
                 pic_state.raster_spatial_node_index,
                 &clip_chain,
                 pic_state,
                 frame_context,
                 frame_state,
                 is_chased,
@@ -1871,66 +1914,57 @@ impl PrimitiveStore {
             frame_state,
             display_list,
             is_chased,
         );
 
         true
     }
 
-    // TODO(gw): Make this simpler / more efficient by tidying
-    //           up the logic that early outs from prepare_prim_for_render.
-    pub fn reset_prim_visibility(&mut self) {
-        for prim in &mut self.primitives {
-            prim.metadata.clipped_world_rect = None;
-        }
-    }
-
     pub fn prepare_primitives(
         &mut self,
         prim_instances: &mut Vec<PrimitiveInstance>,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         current_pic_rect: &mut PictureRect,
     ) {
         let display_list = &frame_context
             .pipelines
             .get(&pic_context.pipeline_id)
             .expect("No display list?")
             .display_list;
 
         for prim_instance in prim_instances {
+            prim_instance.clipped_world_rect = None;
+
             let prim_index = prim_instance.prim_index;
             let is_chased = Some(prim_index) == self.chase_id;
 
             if is_chased {
                 println!("\tpreparing prim {:?} in pipeline {:?}",
                     prim_instance.prim_index, pic_context.pipeline_id);
             }
 
-            // TODO(gw): These workarounds for borrowck are unfortunate. We
-            //           should see if we can re-structure these to avoid so
-            //           many special borrow blocks.
-            let (spatial_node_index, is_backface_visible) = {
-                let prim = &self.primitives[prim_instance.prim_index.0];
-                (prim.metadata.spatial_node_index, prim.metadata.is_backface_visible)
-            };
+            let is_backface_visible = frame_state
+                .resources
+                .prim_data_store[prim_instance.prim_data_handle]
+                .is_backface_visible;
 
             let spatial_node = &frame_context
                 .clip_scroll_tree
-                .spatial_nodes[spatial_node_index.0];
+                .spatial_nodes[prim_instance.spatial_node_index.0];
 
             // TODO(gw): Although constructing these is cheap, they are often
             //           the same for many consecutive primitives, so it may
             //           be worth caching the most recent context.
             let prim_context = PrimitiveContext::new(
                 spatial_node,
-                spatial_node_index,
+                prim_instance.spatial_node_index,
             );
 
             // Do some basic checks first, that can early out
             // without even knowing the local rect.
             if !is_backface_visible && spatial_node.world_content_transform.is_backface_visible() {
                 if cfg!(debug_assertions) && is_chased {
                     println!("\tculled for not having visible back faces");
                 }
@@ -1945,17 +1979,17 @@ impl PrimitiveStore {
             }
 
             // Mark whether this picture contains any complex coordinate
             // systems, due to either the scroll node or the clip-chain.
             pic_state.has_non_root_coord_system |=
                 spatial_node.coordinate_system_id != CoordinateSystemId::root();
 
             pic_state.map_local_to_pic.set_target_spatial_node(
-                spatial_node_index,
+                prim_instance.spatial_node_index,
                 frame_context.clip_scroll_tree,
             );
 
             if self.prepare_prim_for_render(
                 prim_instance,
                 &prim_context,
                 pic_context,
                 pic_state,
@@ -1987,34 +2021,34 @@ fn build_gradient_stops_request(
             reverse_stops,
             &mut request,
         );
     }
 }
 
 fn decompose_repeated_primitive(
     visible_tiles: &mut Vec<VisibleGradientTile>,
-    instance: &PrimitiveInstance,
+    instance: &mut PrimitiveInstance,
     metadata: &mut PrimitiveMetadata,
     stretch_size: &LayoutSize,
     tile_spacing: &LayoutSize,
     prim_context: &PrimitiveContext,
     frame_state: &mut FrameBuildingState,
     callback: &mut FnMut(&LayoutRect, GpuDataRequest),
 ) {
     visible_tiles.clear();
 
     // Tighten the clip rect because decomposing the repeated image can
     // produce primitives that are partially covering the original image
     // rect and we want to clip these extra parts out.
     let tight_clip_rect = instance
         .combined_local_clip_rect
         .intersection(&metadata.local_rect).unwrap();
 
-    let clipped_world_rect = &metadata
+    let clipped_world_rect = &instance
         .clipped_world_rect
         .unwrap();
 
     let visible_rect = compute_conservative_visible_rect(
         prim_context,
         clipped_world_rect,
         &tight_clip_rect
     );
@@ -2044,17 +2078,17 @@ fn decompose_repeated_primitive(
     );
 
     if visible_tiles.is_empty() {
         // At this point if we don't have tiles to show it means we could probably
         // have done a better a job at culling during an earlier stage.
         // Clearing the screen rect has the effect of "culling out" the primitive
         // from the point of view of the batch builder, and ensures we don't hit
         // assertions later on because we didn't request any image.
-        metadata.clipped_world_rect = None;
+        instance.clipped_world_rect = None;
     }
 }
 
 fn compute_conservative_visible_rect(
     prim_context: &PrimitiveContext,
     clipped_world_rect: &WorldRect,
     local_clip_rect: &LayoutRect,
 ) -> LayoutRect {
@@ -2257,16 +2291,17 @@ fn write_brush_segment_description(
             }
         }
     }
 }
 
 impl Primitive {
     fn update_clip_task_for_brush(
         &mut self,
+        prim_instance: &PrimitiveInstance,
         root_spatial_node_index: SpatialNodeIndex,
         prim_bounding_rect: WorldRect,
         prim_context: &PrimitiveContext,
         prim_clip_chain: &ClipChainInstance,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         clip_node_collector: &Option<ClipNodeCollector>,
@@ -2304,17 +2339,17 @@ impl Primitive {
         } else {
             for segment in &mut segment_desc.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.metadata.clip_chain_id,
+                        prim_instance.clip_chain_id,
                         segment.local_rect,
                         self.metadata.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,
@@ -2336,18 +2371,21 @@ impl Primitive {
         }
 
         true
     }
 
     // Returns true if the primitive *might* need a clip mask. If
     // false, there is no need to even check for clip masks for
     // this primitive.
-    fn reset_clip_task(&mut self) -> bool {
-        self.metadata.clip_task_id = None;
+    fn reset_clip_task(
+        &mut self,
+        prim_instance: &mut PrimitiveInstance,
+    ) -> bool {
+        prim_instance.clip_task_id = None;
         match self.details {
             PrimitiveDetails::Brush(ref mut brush) => {
                 if let Some(ref mut desc) = brush.segment_desc {
                     for segment in &mut desc.segments {
                         segment.clip_task_id = BrushSegmentTaskId::Opaque;
                     }
                 }
                 brush.may_need_clip_mask()
@@ -2355,30 +2393,30 @@ impl Primitive {
             PrimitiveDetails::TextRun(..) => {
                 true
             }
         }
     }
 
     fn prepare_prim_for_render_inner(
         &mut self,
-        prim_instance: &PrimitiveInstance,
+        prim_instance: &mut PrimitiveInstance,
         prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         display_list: &BuiltDisplayList,
         is_chased: bool,
     ) {
         let mut is_tiled = false;
         let metadata = &mut self.metadata;
         #[cfg(debug_assertions)]
         {
-            metadata.prepared_frame_id = frame_state.render_tasks.frame_id();
+            prim_instance.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
         match self.details {
             PrimitiveDetails::TextRun(ref mut text) => {
                 // The transform only makes sense for screen space rasterization
                 let transform = prim_context.spatial_node.world_content_transform.to_transform();
                 text.prepare_for_render(
                     frame_context.device_pixel_scale,
@@ -2409,22 +2447,22 @@ impl Primitive {
 
                         // Set if we need to request the source image from the cache this frame.
                         if let Some(image_properties) = image_properties {
                             is_tiled = image_properties.tiling.is_some();
 
                             // If the opacity changed, invalidate the GPU cache so that
                             // the new color for this primitive gets uploaded.
                             if opacity_binding.update(frame_context.scene_properties) {
-                                frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
+                                frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
                             }
 
                             // Update opacity for this primitive to ensure the correct
                             // batching parameters are used.
-                            metadata.opacity.is_opaque =
+                            prim_instance.opacity.is_opaque =
                                 image_properties.descriptor.is_opaque &&
                                 opacity_binding.current == 1.0 &&
                                 color.a == 1.0;
 
                             if *tile_spacing != LayoutSize::zero() && !is_tiled {
                                 *source = ImageSource::Cache {
                                     // Size in device-pixels we need to allocate in render task cache.
                                     size: image_properties.descriptor.size.to_i32(),
@@ -2459,17 +2497,17 @@ impl Primitive {
                                         0,
                                     );
 
                                     let inner_size = *size;
                                     size.width += padding.horizontal();
                                     size.height += padding.vertical();
 
                                     if padding != DeviceIntSideOffsets::zero() {
-                                        metadata.opacity.is_opaque = false;
+                                        prim_instance.opacity.is_opaque = false;
                                     }
 
                                     let image_cache_key = ImageCacheKey {
                                         request,
                                         texel_rect: sub_rect,
                                     };
 
                                     // Request a pre-rendered image task.
@@ -2530,17 +2568,17 @@ impl Primitive {
                                 // produce primitives that are partially covering the original image
                                 // rect and we want to clip these extra parts out.
                                 let tight_clip_rect = prim_instance
                                     .combined_local_clip_rect
                                     .intersection(&metadata.local_rect).unwrap();
 
                                 let visible_rect = compute_conservative_visible_rect(
                                     prim_context,
-                                    &metadata.clipped_world_rect.unwrap(),
+                                    &prim_instance.clipped_world_rect.unwrap(),
                                     &tight_clip_rect
                                 );
 
                                 let base_edge_flags = edge_flags_for_tile_spacing(tile_spacing);
 
                                 let stride = stretch_size + *tile_spacing;
 
                                 visible_tiles.clear();
@@ -2590,27 +2628,29 @@ impl Primitive {
                                 );
 
                                 if visible_tiles.is_empty() {
                                     // At this point if we don't have tiles to show it means we could probably
                                     // have done a better a job at culling during an earlier stage.
                                     // Clearing the screen rect has the effect of "culling out" the primitive
                                     // from the point of view of the batch builder, and ensures we don't hit
                                     // assertions later on because we didn't request any image.
-                                    metadata.clipped_world_rect = None;
+                                    prim_instance.clipped_world_rect = None;
                                 }
                             } else if request_source_image {
                                 frame_state.resource_cache.request_image(
                                     request,
                                     frame_state.gpu_cache,
                                 );
                             }
                         }
                     }
                     BrushKind::YuvImage { format, yuv_key, image_rendering, .. } => {
+                        prim_instance.opacity = PrimitiveOpacity::opaque();
+
                         let channel_num = format.get_plane_num();
                         debug_assert!(channel_num <= 3);
                         for channel in 0 .. channel_num {
                             frame_state.resource_cache.request_image(
                                 ImageRequest {
                                     key: yuv_key[channel],
                                     rendering: image_rendering,
                                     tile: None,
@@ -2624,17 +2664,17 @@ impl Primitive {
                             BorderSource::Image(request) => {
                                 let image_properties = frame_state
                                     .resource_cache
                                     .get_image_properties(request.key);
 
                                 if let Some(image_properties) = image_properties {
                                     // Update opacity for this primitive to ensure the correct
                                     // batching parameters are used.
-                                    metadata.opacity.is_opaque =
+                                    prim_instance.opacity.is_opaque =
                                         image_properties.descriptor.is_opaque;
 
                                     frame_state.resource_cache.request_image(
                                         request,
                                         frame_state.gpu_cache,
                                     );
                                 }
                             }
@@ -2697,20 +2737,33 @@ impl Primitive {
                     BrushKind::LinearGradient {
                         stops_range,
                         reverse_stops,
                         start_point,
                         end_point,
                         extend_mode,
                         stretch_size,
                         tile_spacing,
+                        stops_opacity,
                         ref mut stops_handle,
                         ref mut visible_tiles,
                         ..
                     } => {
+                        // If the coverage of the gradient extends to or beyond
+                        // the primitive rect, then the opacity can be determined
+                        // by the colors of the stops. If we have tiling / spacing
+                        // then we just assume the gradient is translucent for now.
+                        // (In the future we could consider segmenting in some cases).
+                        let stride = stretch_size + tile_spacing;
+                        prim_instance.opacity = if stride.width >= metadata.local_rect.size.width &&
+                           stride.height >= metadata.local_rect.size.height {
+                            stops_opacity
+                        } else {
+                            PrimitiveOpacity::translucent()
+                        };
 
                         build_gradient_stops_request(
                             stops_handle,
                             stops_range,
                             reverse_stops,
                             frame_state,
                             display_list,
                         );
@@ -2741,47 +2794,47 @@ impl Primitive {
                                     ]);
                                     request.write_segment(*rect, [0.0; 4]);
                                 }
                             );
                         }
                     }
                     BrushKind::Picture(ref mut pic) => {
                         if !pic.prepare_for_render(
-                            prim_instance.prim_index,
+                            prim_instance,
                             metadata,
                             pic_state,
                             frame_context,
                             frame_state,
                         ) {
-                            metadata.clipped_world_rect = None;
+                            prim_instance.clipped_world_rect = None;
                         }
                     }
                     BrushKind::Solid { ref color, ref mut opacity_binding, .. } => {
                         // If the opacity changed, invalidate the GPU cache so that
                         // the new color for this primitive gets uploaded. Also update
                         // the opacity field that controls which batches this primitive
                         // will be added to.
                         if opacity_binding.update(frame_context.scene_properties) {
-                            metadata.opacity = PrimitiveOpacity::from_alpha(opacity_binding.current * color.a);
-                            frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
+                            frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
                         }
+                        prim_instance.opacity = PrimitiveOpacity::from_alpha(opacity_binding.current * color.a);
                     }
                     BrushKind::Clear => {}
                 }
             }
         }
 
         if is_tiled {
             // we already requested each tile's gpu data.
             return;
         }
 
         // Mark this GPU resource as required for this frame.
-        if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) {
+        if let Some(mut request) = frame_state.gpu_cache.request(&mut prim_instance.gpu_location) {
             match self.details {
                 PrimitiveDetails::TextRun(ref mut text) => {
                     text.write_gpu_blocks(&mut request);
                 }
                 PrimitiveDetails::Brush(ref mut brush) => {
                     brush.write_gpu_blocks(&mut request, metadata.local_rect);
 
                     match brush.segment_desc {
@@ -2806,37 +2859,39 @@ impl Primitive {
                     }
                 }
             }
         }
     }
 
     fn update_clip_task(
         &mut self,
+        prim_instance: &mut PrimitiveInstance,
         prim_context: &PrimitiveContext,
         prim_bounding_rect: WorldRect,
         root_spatial_node_index: SpatialNodeIndex,
         clip_chain: &ClipChainInstance,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         is_chased: bool,
         clip_node_collector: &Option<ClipNodeCollector>,
     ) {
         if cfg!(debug_assertions) && is_chased {
             println!("\tupdating clip task with pic rect {:?}", clip_chain.pic_clip_rect);
         }
         // Reset clips from previous frames since we may clip differently each frame.
         // If this primitive never needs clip masks, just return straight away.
-        if !self.reset_clip_task() {
+        if !self.reset_clip_task(prim_instance) {
             return;
         }
 
         // First try to  render this primitive's mask using optimized brush rendering.
         if self.update_clip_task_for_brush(
+            prim_instance,
             root_spatial_node_index,
             prim_bounding_rect,
             prim_context,
             &clip_chain,
             pic_state,
             frame_context,
             frame_state,
             clip_node_collector,
@@ -2863,27 +2918,28 @@ impl Primitive {
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_state.render_tasks,
                     &mut frame_state.resources.clip_data_store,
                 );
 
                 let clip_task_id = frame_state.render_tasks.add(clip_task);
                 if cfg!(debug_assertions) && is_chased {
-                    println!("\tcreated task {:?} with world rect {:?}",
-                        clip_task_id, self.metadata.clipped_world_rect);
+                    println!("\tcreated task {:?} with device rect {:?}",
+                        clip_task_id, device_rect);
                 }
-                self.metadata.clip_task_id = Some(clip_task_id);
+                prim_instance.clip_task_id = Some(clip_task_id);
                 pic_state.tasks.push(clip_task_id);
             }
         }
     }
 
     fn build_prim_segments_if_needed(
         &mut self,
+        prim_instance: &mut PrimitiveInstance,
         pic_state: &mut PictureState,
         frame_state: &mut FrameBuildingState,
         frame_context: &FrameBuildingContext,
     ) {
         let brush = match self.details {
             PrimitiveDetails::Brush(ref mut brush) => brush,
             PrimitiveDetails::TextRun(..) => return,
         };
@@ -2955,17 +3011,17 @@ impl Primitive {
 
             if needs_update {
                 brush.segment_desc = Some(BrushSegmentDescriptor {
                     segments: new_segments,
                 });
 
                 // The segments have changed, so force the GPU cache to
                 // re-upload the primitive information.
-                frame_state.gpu_cache.invalidate(&mut self.metadata.gpu_location);
+                frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
             }
         }
     }
 }
 
 pub fn get_raster_rects(
     pic_rect: PictureRect,
     map_to_raster: &SpaceMapper<PicturePixel, RasterPixel>,
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -25,16 +25,17 @@ use api::CapturedDocument;
 use clip::ClipDataStore;
 use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
 #[cfg(feature = "debugger")]
 use debug_server;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use hit_test::{HitTest, HitTester};
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
+use prim_store::PrimitiveDataStore;
 use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
 use record::ApiRecordingReceiver;
 use renderer::{AsyncPropertySampler, PipelineInfo};
 use resource_cache::ResourceCache;
 #[cfg(feature = "replay")]
 use resource_cache::PlainCacheOwn;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use resource_cache::PlainResources;
@@ -84,25 +85,30 @@ impl DocumentView {
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameId(pub u32);
 
 // A collection of resources that are shared by clips, primitives
 // between display lists.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameResources {
-    // The store of currently active / available clip nodes. This is kept
-    // in sync with the clip interner in the scene builder for each document.
+    /// The store of currently active / available clip nodes. This is kept
+    /// in sync with the clip interner in the scene builder for each document.
     pub clip_data_store: ClipDataStore,
+
+    /// Currently active / available primitives. Kept in sync with the
+    /// primitive interner in the scene builder, per document.
+    pub prim_data_store: PrimitiveDataStore,
 }
 
 impl FrameResources {
     fn new() -> Self {
         FrameResources {
             clip_data_store: ClipDataStore::new(),
+            prim_data_store: PrimitiveDataStore::new(),
         }
     }
 }
 
 struct Document {
     // The latest built scene, usable to build frames.
     // received from the scene builder thread.
     scene: Scene,
@@ -134,16 +140,17 @@ struct Document {
     /// Properties that are resolved during frame building and can be changed at any time
     /// without requiring the scene to be re-built.
     dynamic_properties: SceneProperties,
 
     /// Track whether the last built frame is up to date or if it will need to be re-built
     /// before rendering again.
     frame_is_valid: bool,
     hit_tester_is_valid: bool,
+    rendered_frame_is_valid: bool,
 
     resources: FrameResources,
 }
 
 impl Document {
     pub fn new(
         window_size: DeviceUintSize,
         layer: DocumentLayer,
@@ -164,16 +171,17 @@ impl Document {
             clip_scroll_tree: ClipScrollTree::new(),
             frame_id: FrameId(0),
             frame_builder: None,
             output_pipelines: FastHashSet::default(),
             hit_tester: None,
             dynamic_properties: SceneProperties::new(),
             frame_is_valid: false,
             hit_tester_is_valid: false,
+            rendered_frame_is_valid: false,
             resources: FrameResources::new(),
         }
     }
 
     fn can_render(&self) -> bool {
         self.frame_builder.is_some() && self.scene.has_root_pipeline()
     }
 
@@ -651,16 +659,17 @@ impl RenderBackend {
 
                         self.update_document(
                             txn.document_id,
                             replace(&mut txn.resource_updates, Vec::new()),
                             txn.doc_resource_updates.take(),
                             replace(&mut txn.frame_ops, Vec::new()),
                             replace(&mut txn.notifications, Vec::new()),
                             txn.render_frame,
+                            txn.invalidate_rendered_frame,
                             &mut frame_counter,
                             &mut profile_counters,
                             has_built_scene,
                         );
                     },
                     SceneBuilderResult::FlushComplete(tx) => {
                         tx.send(()).ok();
                     }
@@ -931,16 +940,17 @@ impl RenderBackend {
             blob_rasterizer: None,
             blob_requests: Vec::new(),
             resource_updates: transaction_msg.resource_updates,
             frame_ops: transaction_msg.frame_ops,
             rasterized_blobs: Vec::new(),
             notifications: transaction_msg.notifications,
             set_root_pipeline: None,
             render_frame: transaction_msg.generate_frame,
+            invalidate_rendered_frame: transaction_msg.invalidate_rendered_frame,
         });
 
         self.resource_cache.pre_scene_building_update(
             &mut txn.resource_updates,
             &mut profile_counters.resources,
         );
 
         for scene_msg in transaction_msg.scene_ops.drain(..) {
@@ -966,16 +976,17 @@ impl RenderBackend {
         if !transaction_msg.use_scene_builder_thread && txn.can_skip_scene_builder() {
             self.update_document(
                 txn.document_id,
                 replace(&mut txn.resource_updates, Vec::new()),
                 None,
                 replace(&mut txn.frame_ops, Vec::new()),
                 replace(&mut txn.notifications, Vec::new()),
                 txn.render_frame,
+                txn.invalidate_rendered_frame,
                 frame_counter,
                 profile_counters,
                 false
             );
 
             return;
         }
 
@@ -1003,16 +1014,17 @@ impl RenderBackend {
     fn update_document(
         &mut self,
         document_id: DocumentId,
         resource_updates: Vec<ResourceUpdate>,
         doc_resource_updates: Option<DocumentResourceUpdates>,
         mut frame_ops: Vec<FrameMsg>,
         mut notifications: Vec<NotificationRequest>,
         mut render_frame: bool,
+        invalidate_rendered_frame: bool,
         frame_counter: &mut u32,
         profile_counters: &mut BackendProfileCounters,
         has_built_scene: bool,
     ) {
         let requested_frame = render_frame;
 
         // If we have a sampler, get more frame ops from it and add them
         // to the transaction. This is a hook to allow the WR user code to
@@ -1026,16 +1038,17 @@ impl RenderBackend {
         }
 
         let doc = self.documents.get_mut(&document_id).unwrap();
 
         // If there are any additions or removals of clip modes
         // during the scene build, apply them to the data store now.
         if let Some(updates) = doc_resource_updates {
             doc.resources.clip_data_store.apply_updates(updates.clip_updates);
+            doc.resources.prim_data_store.apply_updates(updates.prim_updates);
         }
 
         // TODO: this scroll variable doesn't necessarily mean we scrolled. It is only used
         // for something wrench specific and we should remove it.
         let mut scroll = false;
         for frame_msg in frame_ops {
             let _timer = profile_counters.total_time.timer();
             let op = doc.process_frame_msg(frame_msg);
@@ -1063,21 +1076,29 @@ impl RenderBackend {
             // scroll at the same time. we should keep track of the fact that we skipped
             // composition here and do it as soon as we receive the scene.
             render_frame = false;
         }
 
         // Avoid re-building the frame if the current built frame is still valid.
         let build_frame = render_frame && !doc.frame_is_valid;
 
+        // Request composite is true when we want to composite frame even when
+        // there is no frame update. This happens when video frame is updated under
+        // external image with NativeTexture or when platform requested to composite frame.
+        if invalidate_rendered_frame {
+            doc.rendered_frame_is_valid = false;
+        }
+
         let mut frame_build_time = None;
         if build_frame && doc.has_pixels() {
             profile_scope!("generate frame");
 
             *frame_counter += 1;
+            doc.rendered_frame_is_valid = false;
 
             // borrow ck hack for profile_counters
             let (pending_update, rendered_document) = {
                 let _timer = profile_counters.total_time.timer();
                 let frame_build_start_time = precise_time_ns();
 
                 let rendered_document = doc.build_frame(
                     &mut self.resource_cache,
@@ -1128,16 +1149,22 @@ impl RenderBackend {
         if !notifications.is_empty() {
             self.result_tx.send(ResultMsg::AppendNotificationRequests(notifications)).unwrap();
         }
 
         // Always forward the transaction to the renderer if a frame was requested,
         // otherwise gecko can get into a state where it waits (forever) for the
         // transaction to complete before sending new work.
         if requested_frame {
+            // If rendered frame is already valid, there is no need to render frame.
+            if doc.rendered_frame_is_valid {
+                render_frame = false;
+            } else if render_frame {
+                doc.rendered_frame_is_valid = true;
+            }
             self.notifier.new_frame_ready(document_id, scroll, render_frame, frame_build_time);
         }
 
         if !doc.hit_tester_is_valid {
             doc.rebuild_hit_tester();
         }
     }
 
@@ -1449,16 +1476,17 @@ impl RenderBackend {
                 clip_scroll_tree: ClipScrollTree::new(),
                 frame_id: FrameId(0),
                 frame_builder: Some(FrameBuilder::empty()),
                 output_pipelines: FastHashSet::default(),
                 dynamic_properties: SceneProperties::new(),
                 hit_tester: None,
                 frame_is_valid: false,
                 hit_tester_is_valid: false,
+                rendered_frame_is_valid: false,
                 resources: frame_resources,
             };
 
             let frame_name = format!("frame-{}-{}", (id.0).0, id.1);
             let frame = CaptureConfig::deserialize::<Frame, _>(root, frame_name);
             let build_frame = match frame {
                 Some(frame) => {
                     info!("\tloaded a built frame with {} passes", frame.passes.len());
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -32,17 +32,17 @@ use api::{channel};
 use api::DebugCommand;
 use api::channel::PayloadReceiverHelperMethods;
 use batch::{BatchKind, BatchTextures, BrushBatchKind};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
 use debug_colors;
 use device::{DepthFunction, Device, FrameId, Program, UploadMethod, Texture, PBO};
 use device::{ExternalTexture, FBOId, TextureSlot};
-use device::{FileWatcherHandler, ShaderError, TextureFilter,
+use device::{ShaderError, TextureFilter,
              VertexUsageHint, VAO, VBO, CustomVAO};
 use device::{ProgramCache, ReadPixelsFormat};
 #[cfg(feature = "debug_renderer")]
 use euclid::rect;
 use euclid::Transform3D;
 use frame_builder::{ChasePrimitive, FrameBuilderConfig};
 use gleam::gl;
 use glyph_rasterizer::{GlyphFormat, GlyphRasterizer};
@@ -59,34 +59,35 @@ use internal_types::{RenderTargetInfo, S
 use prim_store::DeferredResolve;
 use profiler::{BackendProfileCounters, FrameProfileCounters,
                GpuProfileTag, RendererProfileCounters, RendererProfileTimers};
 use device::query::GpuProfiler;
 use rayon::{ThreadPool, ThreadPoolBuilder};
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
 use scene_builder::{SceneBuilder, LowPrioritySceneBuilder};
-use shade::Shaders;
+use shade::{Shaders, WrShaders};
 use smallvec::SmallVec;
 use render_task::{RenderTask, RenderTaskKind, RenderTaskTree};
 use resource_cache::ResourceCache;
 use util::drain_filter;
 
 use std;
 use std::cmp;
 use std::collections::VecDeque;
 use std::collections::hash_map::Entry;
 use std::f32;
 use std::mem;
 use std::os::raw::c_void;
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::Arc;
-use std::sync::mpsc::{channel, Receiver, Sender};
+use std::sync::mpsc::{channel, Receiver};
 use std::thread;
+use std::cell::RefCell;
 use texture_cache::TextureCache;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use tiling::{AlphaRenderTarget, ColorRenderTarget};
 use tiling::{BlitJob, BlitJobSource, RenderPass, RenderPassKind, RenderTargetList};
 use tiling::{Frame, RenderTarget, RenderTargetKind, TextureCacheRenderTarget};
 #[cfg(not(feature = "pathfinder"))]
 use tiling::GlyphJob;
 use time::precise_time_ns;
@@ -1336,28 +1337,16 @@ impl VertexDataTexture {
     }
 
     fn deinit(self, device: &mut Device) {
         device.delete_pbo(self.pbo);
         device.delete_texture(self.texture);
     }
 }
 
-struct FileWatcher {
-    notifier: Box<RenderNotifier>,
-    result_tx: Sender<ResultMsg>,
-}
-
-impl FileWatcherHandler for FileWatcher {
-    fn file_changed(&self, path: PathBuf) {
-        self.result_tx.send(ResultMsg::RefreshShader(path)).ok();
-        self.notifier.wake_up();
-    }
-}
-
 struct FrameOutput {
     last_access: FrameId,
     fbo_id: FBOId,
 }
 
 #[derive(PartialEq)]
 struct TargetSelector {
     size: DeviceUintSize,
@@ -1428,17 +1417,17 @@ pub struct Renderer {
     result_rx: Receiver<ResultMsg>,
     debug_server: DebugServer,
     pub device: Device,
     pending_texture_updates: Vec<TextureUpdateList>,
     pending_gpu_cache_updates: Vec<GpuCacheUpdateList>,
     pending_shader_updates: Vec<PathBuf>,
     active_documents: Vec<(DocumentId, RenderedDocument)>,
 
-    shaders: Shaders,
+    shaders: Rc<RefCell<Shaders>>,
 
     pub gpu_glyph_renderer: GpuGlyphRenderer,
 
     max_texture_size: u32,
     max_recorded_profiles: usize,
 
     clear_color: Option<ColorF>,
     enable_clear_scissor: bool,
@@ -1554,34 +1543,29 @@ impl Renderer {
     /// };
     /// let (renderer, sender) = Renderer::new(opts);
     /// ```
     /// [rendereroptions]: struct.RendererOptions.html
     pub fn new(
         gl: Rc<gl::Gl>,
         notifier: Box<RenderNotifier>,
         mut options: RendererOptions,
+        shaders: Option<&mut WrShaders>
     ) -> Result<(Self, RenderApiSender), RendererError> {
         let (api_tx, api_rx) = channel::msg_channel()?;
         let (payload_tx, payload_rx) = channel::payload_channel()?;
         let (result_tx, result_rx) = channel();
         let gl_type = gl.get_type();
 
         let debug_server = DebugServer::new(api_tx.clone());
 
-        let file_watch_handler = FileWatcher {
-            result_tx: result_tx.clone(),
-            notifier: notifier.clone(),
-        };
-
         let mut device = Device::new(
             gl,
             options.resource_override_path.clone(),
             options.upload_method.clone(),
-            Box::new(file_watch_handler),
             options.cached_programs.take(),
         );
 
         let ext_dual_source_blending = !options.disable_dual_source_blending &&
             device.supports_extension("GL_ARB_blend_func_extended") &&
             device.supports_extension("GL_ARB_explicit_attrib_location");
 
         let device_max_size = device.max_texture_size();
@@ -1603,17 +1587,20 @@ impl Renderer {
             ),
             min_texture_size,
         );
 
         register_thread_with_profiler("Compositor".to_owned());
 
         device.begin_frame();
 
-        let shaders = Shaders::new(&mut device, gl_type, &options)?;
+        let shaders = match shaders {
+            Some(shaders) => Rc::clone(&shaders.shaders),
+            None => Rc::new(RefCell::new(Shaders::new(&mut device, gl_type, &options)?)),
+        };
 
         let backend_profile_counters = BackendProfileCounters::new();
 
         let dither_matrix_texture = if options.enable_dithering {
             let dither_matrix: [u8; 64] = [
                 00,
                 48,
                 12,
@@ -2960,24 +2947,24 @@ impl Renderer {
         stats: &mut RendererStats,
     ) {
         if scalings.is_empty() {
             return
         }
 
         match source {
             TextureSource::PrevPassColor => {
-                self.shaders.cs_scale_rgba8.bind(&mut self.device,
-                                                 &projection,
-                                                 &mut self.renderer_errors);
+                self.shaders.borrow_mut().cs_scale_rgba8.bind(&mut self.device,
+                                                              &projection,
+                                                              &mut self.renderer_errors);
             }
             TextureSource::PrevPassAlpha => {
-                self.shaders.cs_scale_a8.bind(&mut self.device,
-                                              &projection,
-                                              &mut self.renderer_errors);
+                self.shaders.borrow_mut().cs_scale_a8.bind(&mut self.device,
+                                                           &projection,
+                                                           &mut self.renderer_errors);
             }
             _ => unreachable!(),
         }
 
         self.draw_instanced_batch(
             &scalings,
             VertexArrayKind::Scale,
             &BatchTextures::no_texture(),
@@ -3064,17 +3051,17 @@ impl Renderer {
         // separable implementation.
         // TODO(gw): In the future, consider having
         //           fast path blur shaders for common
         //           blur radii with fixed weights.
         if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
             self.set_blend(false, framebuffer_kind);
-            self.shaders.cs_blur_rgba8
+            self.shaders.borrow_mut().cs_blur_rgba8
                 .bind(&mut self.device, projection, &mut self.renderer_errors);
 
             if !target.vertical_blurs.is_empty() {
                 self.draw_instanced_batch(
                     &target.vertical_blurs,
                     VertexArrayKind::Blur,
                     &BatchTextures::no_texture(),
                     stats,
@@ -3122,17 +3109,17 @@ impl Renderer {
 
                 // Draw opaque batches front-to-back for maximum
                 // z-buffer efficiency!
                 for batch in alpha_batch_container
                     .opaque_batches
                     .iter()
                     .rev()
                 {
-                    self.shaders
+                    self.shaders.borrow_mut()
                         .get(&batch.key, self.debug_flags)
                         .bind(
                             &mut self.device, projection,
                             &mut self.renderer_errors,
                         );
 
                     let _timer = self.gpu_profile.start_timer(batch.key.kind.sampler_tag());
                     self.draw_instanced_batch(
@@ -3169,17 +3156,17 @@ impl Renderer {
                 } else {
                     target_rect
                 };
                 self.device.enable_scissor();
                 self.device.set_scissor_rect(rect);
             }
 
             for batch in &alpha_batch_container.alpha_batches {
-                self.shaders
+                self.shaders.borrow_mut()
                     .get(&batch.key, self.debug_flags)
                     .bind(
                         &mut self.device, projection,
                         &mut self.renderer_errors,
                     );
 
                 if batch.key.blend_mode != prev_blend_mode {
                     match batch.key.blend_mode {
@@ -3356,17 +3343,17 @@ impl Renderer {
         // separable implementation.
         // TODO(gw): In the future, consider having
         //           fast path blur shaders for common
         //           blur radii with fixed weights.
         if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
             self.set_blend(false, FramebufferKind::Other);
-            self.shaders.cs_blur_a8
+            self.shaders.borrow_mut().cs_blur_a8
                 .bind(&mut self.device, projection, &mut self.renderer_errors);
 
             if !target.vertical_blurs.is_empty() {
                 self.draw_instanced_batch(
                     &target.vertical_blurs,
                     VertexArrayKind::Blur,
                     &BatchTextures::no_texture(),
                     stats,
@@ -3391,17 +3378,17 @@ impl Renderer {
 
             // switch to multiplicative blending
             self.set_blend(true, FramebufferKind::Other);
             self.set_blend_mode_multiply(FramebufferKind::Other);
 
             // draw rounded cornered rectangles
             if !target.clip_batcher.rectangles.is_empty() {
                 let _gm2 = self.gpu_profile.start_marker("clip rectangles");
-                self.shaders.cs_clip_rectangle.bind(
+                self.shaders.borrow_mut().cs_clip_rectangle.bind(
                     &mut self.device,
                     projection,
                     &mut self.renderer_errors,
                 );
                 self.draw_instanced_batch(
                     &target.clip_batcher.rectangles,
                     VertexArrayKind::Clip,
                     &BatchTextures::no_texture(),
@@ -3413,30 +3400,30 @@ impl Renderer {
                 let _gm2 = self.gpu_profile.start_marker("box-shadows");
                 let textures = BatchTextures {
                     colors: [
                         mask_texture_id.clone(),
                         TextureSource::Invalid,
                         TextureSource::Invalid,
                     ],
                 };
-                self.shaders.cs_clip_box_shadow
+                self.shaders.borrow_mut().cs_clip_box_shadow
                     .bind(&mut self.device, projection, &mut self.renderer_errors);
                 self.draw_instanced_batch(
                     items,
                     VertexArrayKind::Clip,
                     &textures,
                     stats,
                 );
             }
 
             // draw line decoration clips
             if !target.clip_batcher.line_decorations.is_empty() {
                 let _gm2 = self.gpu_profile.start_marker("clip lines");
-                self.shaders.cs_clip_line.bind(
+                self.shaders.borrow_mut().cs_clip_line.bind(
                     &mut self.device,
                     projection,
                     &mut self.renderer_errors,
                 );
                 self.draw_instanced_batch(
                     &target.clip_batcher.line_decorations,
                     VertexArrayKind::Clip,
                     &BatchTextures::no_texture(),
@@ -3449,17 +3436,17 @@ impl Renderer {
                 let _gm2 = self.gpu_profile.start_marker("clip images");
                 let textures = BatchTextures {
                     colors: [
                         mask_texture_id.clone(),
                         TextureSource::Invalid,
                         TextureSource::Invalid,
                     ],
                 };
-                self.shaders.cs_clip_image
+                self.shaders.borrow_mut().cs_clip_image
                     .bind(&mut self.device, projection, &mut self.renderer_errors);
                 self.draw_instanced_batch(
                     items,
                     VertexArrayKind::Clip,
                     &textures,
                     stats,
                 );
             }
@@ -3525,32 +3512,32 @@ impl Renderer {
            !target.border_segments_complex.is_empty()
         {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_BORDER);
 
             self.set_blend(true, FramebufferKind::Other);
             self.set_blend_mode_premultiplied_alpha(FramebufferKind::Other);
 
             if !target.border_segments_solid.is_empty() {
-                self.shaders.cs_border_solid.bind(
+                self.shaders.borrow_mut().cs_border_solid.bind(
                     &mut self.device,
                     &projection,
                     &mut self.renderer_errors,
                 );
 
                 self.draw_instanced_batch(
                     &target.border_segments_solid,
                     VertexArrayKind::Border,
                     &BatchTextures::no_texture(),
                     stats,
                 );
             }
 
             if !target.border_segments_complex.is_empty() {
-                self.shaders.cs_border_segment.bind(
+                self.shaders.borrow_mut().cs_border_segment.bind(
                     &mut self.device,
                     &projection,
                     &mut self.renderer_errors,
                 );
 
                 self.draw_instanced_batch(
                     &target.border_segments_complex,
                     VertexArrayKind::Border,
@@ -3561,20 +3548,23 @@ impl Renderer {
 
             self.set_blend(false, FramebufferKind::Other);
         }
 
         // Draw any blurs for this target.
         if !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
-            match target.target_kind {
-                RenderTargetKind::Alpha => &mut self.shaders.cs_blur_a8,
-                RenderTargetKind::Color => &mut self.shaders.cs_blur_rgba8,
-            }.bind(&mut self.device, &projection, &mut self.renderer_errors);
+            {
+                let mut shaders = self.shaders.borrow_mut();
+                match target.target_kind {
+                    RenderTargetKind::Alpha => &mut shaders.cs_blur_a8,
+                    RenderTargetKind::Color => &mut shaders.cs_blur_rgba8,
+                }.bind(&mut self.device, &projection, &mut self.renderer_errors);
+            }
 
             self.draw_instanced_batch(
                 &target.horizontal_blurs,
                 VertexArrayKind::Blur,
                 &BatchTextures::no_texture(),
                 stats,
             );
         }
@@ -4238,17 +4228,19 @@ impl Renderer {
         #[cfg(feature = "debug_renderer")]
         {
             self.debug.deinit(&mut self.device);
         }
 
         for (_, target) in self.output_targets {
             self.device.delete_fbo(target.fbo_id);
         }
-        self.shaders.deinit(&mut self.device);
+        if let Ok(shaders) = Rc::try_unwrap(self.shaders) {
+            shaders.into_inner().deinit(&mut self.device);
+        }
         #[cfg(feature = "capture")]
         self.device.delete_fbo(self.read_fbo);
         #[cfg(feature = "replay")]
         for (_, ext) in self.owned_external_images {
             self.device.delete_external_texture(ext);
         }
         self.device.end_frame();
     }
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -1390,17 +1390,17 @@ impl ResourceCache {
             }
         })
     }
 
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         debug_assert_eq!(self.state, State::Idle);
         self.state = State::AddResources;
         self.texture_cache.begin_frame(frame_id);
-        self.cached_glyphs.begin_frame(&self.texture_cache, &self.cached_render_tasks);
+        self.cached_glyphs.begin_frame(&self.texture_cache, &self.cached_render_tasks, &mut self.glyph_rasterizer);
         self.cached_render_tasks.begin_frame(&mut self.texture_cache);
         self.current_frame_id = frame_id;
     }
 
     pub fn block_until_all_resources_added(
         &mut self,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -9,29 +9,31 @@ use api::channel::MsgSender;
 #[cfg(feature = "capture")]
 use capture::CaptureConfig;
 use frame_builder::{FrameBuilderConfig, FrameBuilder};
 use clip::{ClipDataInterner, ClipDataUpdateList};
 use clip_scroll_tree::ClipScrollTree;
 use display_list_flattener::DisplayListFlattener;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::PictureIdGenerator;
+use prim_store::{PrimitiveDataInterner, PrimitiveDataUpdateList};
 use resource_cache::FontInstanceMap;
 use render_backend::DocumentView;
 use renderer::{PipelineInfo, SceneBuilderHooks};
 use scene::Scene;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::mem::replace;
 use time::precise_time_ns;
 use util::drain_filter;
 use std::thread;
 use std::time::Duration;
 
 pub struct DocumentResourceUpdates {
     pub clip_updates: ClipDataUpdateList,
+    pub prim_updates: PrimitiveDataUpdateList,
 }
 
 /// Represents the work associated to a transaction before scene building.
 pub struct Transaction {
     pub document_id: DocumentId,
     pub display_list_updates: Vec<DisplayListUpdate>,
     pub removed_pipelines: Vec<PipelineId>,
     pub epoch_updates: Vec<(PipelineId, Epoch)>,
@@ -39,16 +41,17 @@ pub struct Transaction {
     pub blob_requests: Vec<BlobImageParams>,
     pub blob_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
     pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>,
     pub resource_updates: Vec<ResourceUpdate>,
     pub frame_ops: Vec<FrameMsg>,
     pub notifications: Vec<NotificationRequest>,
     pub set_root_pipeline: Option<PipelineId>,
     pub render_frame: bool,
+    pub invalidate_rendered_frame: bool,
 }
 
 impl Transaction {
     pub fn can_skip_scene_builder(&self) -> bool {
         self.request_scene_build.is_none() &&
             self.display_list_updates.is_empty() &&
             self.epoch_updates.is_empty() &&
             self.removed_pipelines.is_empty() &&
@@ -72,16 +75,17 @@ pub struct BuiltTransaction {
     pub blob_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
     pub frame_ops: Vec<FrameMsg>,
     pub removed_pipelines: Vec<PipelineId>,
     pub notifications: Vec<NotificationRequest>,
     pub doc_resource_updates: Option<DocumentResourceUpdates>,
     pub scene_build_start_time: u64,
     pub scene_build_end_time: u64,
     pub render_frame: bool,
+    pub invalidate_rendered_frame: bool,
 }
 
 pub struct DisplayListUpdate {
     pub pipeline_id: PipelineId,
     pub epoch: Epoch,
     pub built_display_list: BuiltDisplayList,
     pub background: Option<ColorF>,
     pub viewport_size: LayoutSize,
@@ -155,22 +159,24 @@ pub enum SceneSwapResult {
 // primitives and other things between display lists so that:
 // - GPU cache handles remain valid, reducing GPU cache updates.
 // - Comparison of primitives and pictures between two
 //   display lists is (a) fast (b) done during scene building.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct DocumentResources {
     pub clip_interner: ClipDataInterner,
+    pub prim_interner: PrimitiveDataInterner,
 }
 
 impl DocumentResources {
     fn new() -> Self {
         DocumentResources {
             clip_interner: ClipDataInterner::new(),
+            prim_interner: PrimitiveDataInterner::new(),
         }
     }
 }
 
 // A document in the scene builder contains the current scene,
 // as well as a persistent clip interner. This allows clips
 // to be de-duplicated, and persisted in the GPU cache between
 // display lists.
@@ -329,19 +335,25 @@ impl SceneBuilder {
                     &mut item.doc_resources,
                 );
 
                 let clip_updates = item
                     .doc_resources
                     .clip_interner
                     .end_frame_and_get_pending_updates();
 
+                let prim_updates = item
+                    .doc_resources
+                    .prim_interner
+                    .end_frame_and_get_pending_updates();
+
                 doc_resource_updates = Some(
                     DocumentResourceUpdates {
                         clip_updates,
+                        prim_updates,
                     }
                 );
 
                 built_scene = Some(BuiltScene {
                     scene: new_scene,
                     frame_builder,
                     clip_scroll_tree,
                 });
@@ -353,16 +365,17 @@ impl SceneBuilder {
                     scene: item.scene,
                     resources: item.doc_resources,
                 },
             );
 
             let txn = Box::new(BuiltTransaction {
                 document_id: item.document_id,
                 render_frame: item.build_frame,
+                invalidate_rendered_frame: false,
                 built_scene,
                 resource_updates: Vec::new(),
                 rasterized_blobs: Vec::new(),
                 blob_rasterizer: None,
                 frame_ops: Vec::new(),
                 removed_pipelines: Vec::new(),
                 notifications: Vec::new(),
                 scene_build_start_time,
@@ -428,19 +441,25 @@ impl SceneBuilder {
                 );
 
                 // Retrieve the list of updates from the clip interner.
                 let clip_updates = doc
                     .resources
                     .clip_interner
                     .end_frame_and_get_pending_updates();
 
+                let prim_updates = doc
+                    .resources
+                    .prim_interner
+                    .end_frame_and_get_pending_updates();
+
                 doc_resource_updates = Some(
                     DocumentResourceUpdates {
                         clip_updates,
+                        prim_updates,
                     }
                 );
 
                 built_scene = Some(BuiltScene {
                     scene: new_scene,
                     frame_builder,
                     clip_scroll_tree,
                 });
@@ -463,16 +482,17 @@ impl SceneBuilder {
 
         if self.simulate_slow_ms > 0 {
             thread::sleep(Duration::from_millis(self.simulate_slow_ms as u64));
         }
 
         Box::new(BuiltTransaction {
             document_id: txn.document_id,
             render_frame: txn.render_frame,
+            invalidate_rendered_frame: txn.invalidate_rendered_frame,
             built_scene,
             rasterized_blobs,
             resource_updates: replace(&mut txn.resource_updates, Vec::new()),
             blob_rasterizer: replace(&mut txn.blob_rasterizer, None),
             frame_ops: replace(&mut txn.frame_ops, Vec::new()),
             removed_pipelines: replace(&mut txn.removed_pipelines, Vec::new()),
             notifications: replace(&mut txn.notifications, Vec::new()),
             doc_resource_updates,
--- a/gfx/webrender/src/shade.rs
+++ b/gfx/webrender/src/shade.rs
@@ -15,16 +15,18 @@ use renderer::{
     MAX_VERTEX_TEXTURE_WIDTH,
     BlendMode, DebugFlags, ImageBufferKind, RendererError, RendererOptions,
     TextureSampler, VertexArrayKind, ShaderPrecacheFlags,
 };
 
 use gleam::gl::GlType;
 use time::precise_time_ns;
 
+use std::cell::RefCell;
+use std::rc::Rc;
 
 impl ImageBufferKind {
     pub(crate) fn get_feature_string(&self) -> &'static str {
         match *self {
             ImageBufferKind::Texture2D => "TEXTURE_2D",
             ImageBufferKind::Texture2DArray => "",
             ImageBufferKind::TextureRect => "TEXTURE_RECT",
             ImageBufferKind::TextureExternal => "TEXTURE_EXTERNAL",
@@ -812,8 +814,16 @@ impl Shaders {
                 shader.deinit(device);
             }
         }
         self.cs_border_solid.deinit(device);
         self.cs_border_segment.deinit(device);
         self.ps_split_composite.deinit(device);
     }
 }
+
+// A wrapper around a strong reference to a Shaders
+// object. We have this so that external (ffi)
+// consumers can own a reference to a shared Shaders
+// instance without understanding rust's refcounting.
+pub struct WrShaders {
+    pub shaders: Rc<RefCell<Shaders>>,
+}
--- a/gfx/webrender/src/spatial_node.rs
+++ b/gfx/webrender/src/spatial_node.rs
@@ -246,20 +246,25 @@ impl SpatialNode {
         state: &mut TransformUpdateState,
         coord_systems: &mut Vec<CoordinateSystem>,
         scene_properties: &SceneProperties,
     ) {
         match self.node_type {
             SpatialNodeType::ReferenceFrame(ref mut info) => {
                 // Resolve the transform against any property bindings.
                 let source_transform = scene_properties.resolve_layout_transform(&info.source_transform);
+                // Do a change-basis operation on the perspective matrix using
+                // the scroll offset.
+                let scrolled_perspective = info.source_perspective
+                    .pre_translate(&state.parent_accumulated_scroll_offset)
+                    .post_translate(-state.parent_accumulated_scroll_offset);
                 info.resolved_transform =
                     LayoutFastTransform::with_vector(info.origin_in_parent_reference_frame)
                     .pre_mul(&source_transform.into())
-                    .pre_mul(&info.source_perspective);
+                    .pre_mul(&scrolled_perspective);
 
                 // The transformation for this viewport in world coordinates is the transformation for
                 // our parent reference frame, plus any accumulated scrolling offsets from nodes
                 // between our reference frame and this node. Finally, we also include
                 // whatever local transformation this reference frame provides.
                 let relative_transform = info.resolved_transform
                     .post_translate(state.parent_accumulated_scroll_offset)
                     .to_transform()
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -55,29 +55,32 @@ pub struct Transaction {
     pub resource_updates: Vec<ResourceUpdate>,
 
     // If true the transaction is piped through the scene building thread, if false
     // it will be applied directly on the render backend.
     use_scene_builder_thread: bool,
 
     generate_frame: bool,
 
+    invalidate_rendered_frame: bool,
+
     low_priority: bool,
 }
 
 impl Transaction {
     pub fn new() -> Self {
         Transaction {
             scene_ops: Vec::new(),
             frame_ops: Vec::new(),
             resource_updates: Vec::new(),
             payloads: Vec::new(),
             notifications: Vec::new(),
             use_scene_builder_thread: true,
             generate_frame: false,
+            invalidate_rendered_frame: false,
             low_priority: false,
         }
     }
 
     // TODO: better name?
     pub fn skip_scene_builder(&mut self) {
         self.use_scene_builder_thread = false;
     }
@@ -85,16 +88,17 @@ impl Transaction {
     // TODO: this is temporary, using the scene builder thread is the default for
     // most transactions, and opt-in for specific cases like scrolling and async video.
     pub fn use_scene_builder_thread(&mut self) {
         self.use_scene_builder_thread = true;
     }
 
     pub fn is_empty(&self) -> bool {
         !self.generate_frame &&
+            !self.invalidate_rendered_frame &&
             self.scene_ops.is_empty() &&
             self.frame_ops.is_empty() &&
             self.resource_updates.is_empty() &&
             self.notifications.is_empty()
     }
 
     pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) {
         // We track epochs before and after scene building.
@@ -241,16 +245,26 @@ impl Transaction {
     /// no-op; the arguments passed to `new_frame_ready` will provide information
     /// as to when happened.
     ///
     /// [notifier]: trait.RenderNotifier.html#tymethod.new_frame_ready
     pub fn generate_frame(&mut self) {
         self.generate_frame = true;
     }
 
+    /// Invalidate rendered frame. It ensure that frame will be rendered during
+    /// next frame generation. WebRender could skip frame rendering if there
+    /// is no update.
+    /// But there are cases that needs to force rendering.
+    ///  - Content of image is updated by reusing same ExternalImageId.
+    ///  - Platform requests it if pixels become stale (like wakeup from standby).
+    pub fn invalidate_rendered_frame(&mut self) {
+        self.invalidate_rendered_frame = true;
+    }
+
     /// Supply a list of animated property bindings that should be used to resolve
     /// bindings in the current display list.
     pub fn update_dynamic_properties(&mut self, properties: DynamicProperties) {
         self.frame_ops.push(FrameMsg::UpdateDynamicProperties(properties));
     }
 
     /// Add to the list of animated property bindings that should be used to
     /// resolve bindings in the current display list. This is a convenience method
@@ -275,16 +289,17 @@ impl Transaction {
         (
             TransactionMsg {
                 scene_ops: self.scene_ops,
                 frame_ops: self.frame_ops,
                 resource_updates: self.resource_updates,
                 notifications: self.notifications,
                 use_scene_builder_thread: self.use_scene_builder_thread,
                 generate_frame: self.generate_frame,
+                invalidate_rendered_frame: self.invalidate_rendered_frame,
                 low_priority: self.low_priority,
             },
             self.payloads,
         )
     }
 
     pub fn add_image(
         &mut self,
@@ -385,52 +400,56 @@ impl Transaction {
 
 /// Represents a transaction in the format sent through the channel.
 #[derive(Clone, Deserialize, Serialize)]
 pub struct TransactionMsg {
     pub scene_ops: Vec<SceneMsg>,
     pub frame_ops: Vec<FrameMsg>,
     pub resource_updates: Vec<ResourceUpdate>,
     pub generate_frame: bool,
+    pub invalidate_rendered_frame: bool,
     pub use_scene_builder_thread: bool,
     pub low_priority: bool,
 
     #[serde(skip)]
     pub notifications: Vec<NotificationRequest>,
 }
 
 impl TransactionMsg {
     pub fn is_empty(&self) -> bool {
         !self.generate_frame &&
+            !self.invalidate_rendered_frame &&
             self.scene_ops.is_empty() &&
             self.frame_ops.is_empty() &&
             self.resource_updates.is_empty() &&
             self.notifications.is_empty()
     }
 
     // TODO: We only need this for a few RenderApi methods which we should remove.
     pub fn frame_message(msg: FrameMsg) -> Self {
         TransactionMsg {
             scene_ops: Vec::new(),
             frame_ops: vec![msg],
             resource_updates: Vec::new(),
             notifications: Vec::new(),
             generate_frame: false,
+            invalidate_rendered_frame: false,
             use_scene_builder_thread: false,
             low_priority: false,
         }
     }
 
     pub fn scene_message(msg: SceneMsg) -> Self {
         TransactionMsg {
             scene_ops: vec![msg],
             frame_ops: Vec::new(),
             resource_updates: Vec::new(),
             notifications: Vec::new(),
             generate_frame: false,
+            invalidate_rendered_frame: false,
             use_scene_builder_thread: false,
             low_priority: false,
         }
     }
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub struct AddImage {
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -738,67 +738,16 @@ impl YuvFormat {
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageMask {
     pub image: ImageKey,
     pub rect: LayoutRect,
     pub repeat: bool,
 }
 
-#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
-pub enum LocalClip {
-    Rect(LayoutRect),
-    RoundedRect(LayoutRect, ComplexClipRegion),
-}
-
-impl From<LayoutRect> for LocalClip {
-    fn from(rect: LayoutRect) -> Self {
-        LocalClip::Rect(rect)
-    }
-}
-
-impl LocalClip {
-    pub fn clip_rect(&self) -> &LayoutRect {
-        match *self {
-            LocalClip::Rect(ref rect) => rect,
-            LocalClip::RoundedRect(ref rect, _) => rect,
-        }
-    }
-
-    pub fn create_with_offset(&self, offset: &LayoutVector2D) -> LocalClip {
-        match *self {
-            LocalClip::Rect(rect) => LocalClip::from(rect.translate(offset)),
-            LocalClip::RoundedRect(rect, complex) => LocalClip::RoundedRect(
-                rect.translate(offset),
-                ComplexClipRegion {
-                    rect: complex.rect.translate(offset),
-                    radii: complex.radii,
-                    mode: complex.mode,
-                },
-            ),
-        }
-    }
-
-    pub fn clip_by(&self, rect: &LayoutRect) -> LocalClip {
-        match *self {
-            LocalClip::Rect(clip_rect) => {
-                LocalClip::Rect(
-                    clip_rect.intersection(rect).unwrap_or_else(LayoutRect::zero)
-                )
-            }
-            LocalClip::RoundedRect(clip_rect, complex) => {
-                LocalClip::RoundedRect(
-                    clip_rect.intersection(rect).unwrap_or_else(LayoutRect::zero),
-                    complex,
-                )
-            }
-        }
-    }
-}
-
 #[repr(C)]
 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
 pub enum ClipMode {
     Clip,    // Pixels inside the region are visible.
     ClipOut, // Pixels outside the region are visible.
 }
 
 impl Not for ClipMode {
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-3c3f9a4e919b81639f078d7bd101012de61b9396
+923ee495bd9b0fda8a4a94c5a6cf42e2f0548731
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -233,17 +233,17 @@ impl Wrench {
         }
 
         let (timing_sender, timing_receiver) = chase_lev::deque();
         let notifier = notifier.unwrap_or_else(|| {
             let data = Arc::new(Mutex::new(NotifierData::new(proxy, timing_receiver, verbose)));
             Box::new(Notifier(data))
         });
 
-        let (renderer, sender) = webrender::Renderer::new(window.clone_gl(), notifier, opts).unwrap();
+        let (renderer, sender) = webrender::Renderer::new(window.clone_gl(), notifier, opts, None).unwrap();
         let api = sender.create_api();
         let document_id = api.add_document(size, 0);
 
         let graphics_api = renderer.get_graphics_api_info();
         let zoom_factor = ZoomFactor::new(zoom_factor);
 
         let mut wrench = Wrench {
             window_size: size,