Bug 1434288 - Update webrender to commit e772c3cb8ea0a35e6477e9dc8dd2144e2de87b56. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 31 Jan 2018 16:03:25 -0500
changeset 749624 b5b508fe727d4685a41b922ff932058883ae7eee
parent 749555 205707b678d24ba268b97668b78dc605f701a5e0
child 749625 e2f537c7b350446fdefbb681fe7cb2f3a0643c25
push id97465
push userkgupta@mozilla.com
push dateWed, 31 Jan 2018 21:04:05 +0000
reviewersjrmuizel
bugs1434288
milestone60.0a1
Bug 1434288 - Update webrender to commit e772c3cb8ea0a35e6477e9dc8dd2144e2de87b56. r?jrmuizel MozReview-Commit-ID: 4VM5EAtkjNt
gfx/doc/README.webrender
gfx/webrender/examples/basic.rs
gfx/webrender/examples/blob.rs
gfx/webrender/examples/scrolling.rs
gfx/webrender/src/batch.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/device.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/platform/unix/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/scene.rs
gfx/webrender/src/tiling.rs
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/src/rawtest.rs
gfx/wrench/src/wrench.rs
gfx/wrench/src/yaml_frame_reader.rs
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -30,20 +30,17 @@ listed below. Both options will give you
 try pushes to verify the update. After that, continue with the steps below to
 actually land the update into the tree.
 
 Option A:
    Use a script to do the update for you. This will usually work, if you satisfy
    all the assumptions the script is making. The script can be found at
    https://github.com/staktrace/moz-scripts/blob/master/try-latest-webrender.sh
    and contains documentation on how to use it. Read the documentation carefully
-   before trying to use it. The only extra change you need to make with this
-   option is to manually update the revision in gfx/webrender_bindings/revision.txt
-   so that it points to the new WR version you are landing. The script doesn't
-   do that yet.
+   before trying to use it.
 
 Option B:
    Do the update manually. This is a little more cumbersome but may be required
    if the script doesn't work or the repos are in a state that violates hidden
    assumptions in the script (e.g. if the webrender_bindings/Cargo.toml file is
    no longer in the format expected by the script). The steps to do this are,
    roughly:
    - Update your mozilla-central checkout to the latest code on mozilla-central.
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -221,17 +221,17 @@ impl Example for App {
             rect: (75, 75).by(100, 100),
             repeat: false,
         };
         let complex = ComplexClipRegion::new(
             (50, 50).to(150, 150),
             BorderRadius::uniform(20.0),
             ClipMode::Clip
         );
-        let id = builder.define_clip(None, bounds, vec![complex], Some(mask));
+        let id = builder.define_clip(bounds, vec![complex], Some(mask));
         builder.push_clip_id(id);
 
         let info = LayoutPrimitiveInfo::new((100, 100).to(200, 200));
         builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
 
         let info = LayoutPrimitiveInfo::new((250, 100).to(350, 200));
         builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
         let border_side = BorderSide {
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -85,17 +85,17 @@ fn render_blob(
                     texels.push(color.r * checker + tc);
                     texels.push(color.a * checker + tc);
                 }
                 api::ImageFormat::R8 => {
                     texels.push(color.a * checker + tc);
                 }
                 _ => {
                     return Err(api::BlobImageError::Other(
-                        format!("Usupported image format {:?}", descriptor.format),
+                        format!("Usupported image format"),
                     ));
                 }
             }
         }
     }
 
     Ok(api::RasterizedBlobImage {
         data: texels,
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -104,17 +104,16 @@ impl Example for App {
             let info = LayoutPrimitiveInfo::new((0, 200).to(50, 250));
             builder.push_rect(&info, ColorF::new(0.0, 1.0, 1.0, 1.0));
 
             // Add a sticky frame. It will "stick" twice while scrolling, once
             // at a margin of 10px from the bottom, for 40 pixels of scrolling,
             // and once at a margin of 10px from the top, for 60 pixels of
             // scrolling.
             let sticky_id = builder.define_sticky_frame(
-                None,
                 (50, 350).by(50, 50),
                 SideOffsets2D::new(Some(10.0), None, Some(10.0), None),
                 StickyOffsetBounds::new(-40.0, 60.0),
                 StickyOffsetBounds::new(0.0, 0.0),
                 LayoutVector2D::new(0.0, 0.0)
             );
 
             builder.push_clip_id(sticky_id);
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -719,17 +719,18 @@ impl AlphaBatcher {
                         )
                     }
                     ImageSource::Cache { ref item, .. } => {
                         item.clone()
                     }
                 };
 
                 if cache_item.texture_id == SourceTexture::Invalid {
-                    warn!("Warnings: skip a PrimitiveKind::Image at {:?}.\n", item_bounding_rect);
+                    warn!("Warnings: skip a PrimitiveKind::Image");
+                    debug!("at {:?}.", item_bounding_rect);
                     return;
                 }
 
                 let batch_kind = TransformBatchKind::Image(Self::get_buffer_kind(cache_item.texture_id));
                 let key = BatchKey::new(
                     BatchKind::Transformable(transform_kind, batch_kind),
                     blend_mode,
                     BatchTextures {
@@ -1178,17 +1179,18 @@ impl AlphaBatcher {
                         image_yuv_cpu.image_rendering,
                         None,
                         ctx.resource_cache,
                         gpu_cache,
                         deferred_resolves,
                     );
 
                     if cache_item.texture_id == SourceTexture::Invalid {
-                        warn!("Warnings: skip a PrimitiveKind::YuvImage at {:?}.\n", item_bounding_rect);
+                        warn!("Warnings: skip a PrimitiveKind::YuvImage");
+                        debug!("at {:?}.", item_bounding_rect);
                         return;
                     }
 
                     textures.colors[channel] = cache_item.texture_id;
                     uv_rect_addresses[channel] = cache_item.uv_rect_handle.as_int(gpu_cache);
                 }
 
                 // All yuv textures should be the same type.
@@ -1518,17 +1520,18 @@ impl ClipBatcher {
                                 .entry(cache_item.texture_id)
                                 .or_insert(Vec::new())
                                 .push(ClipMaskInstance {
                                     clip_data_address: gpu_address,
                                     resource_address: gpu_cache.get_address(&cache_item.uv_rect_handle),
                                     ..instance
                                 });
                         } else {
-                            warn!("Warnings: skip a image mask. Key:{:?} Rect::{:?}.\n", mask.image, mask.rect);
+                            warn!("Warnings: skip a image mask");
+                            debug!("Key:{:?} Rect::{:?}", mask.image, mask.rect);
                             continue;
                         }
                     }
                     ClipSource::Rectangle(..) => {
                         if work_item.coordinate_system_id != coordinate_system_id {
                             self.rectangles.push(ClipMaskInstance {
                                 clip_data_address: gpu_address,
                                 ..instance
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ClipId, DevicePixelScale, LayerPixel, LayerPoint, LayerRect, LayerSize};
-use api::{LayerToWorldTransform, LayerTransform, LayerVector2D, LayoutTransform, LayoutVector2D};
-use api::{PipelineId, PropertyBinding, ScrollClamping, ScrollEventPhase, ScrollLocation};
-use api::{ScrollSensitivity, StickyOffsetBounds, WorldPoint};
+use api::{ClipId, DevicePixelScale, ExternalScrollId, IdType, LayerPixel, LayerPoint, LayerRect};
+use api::{LayerSize, LayerToWorldTransform, LayerTransform, LayerVector2D, LayoutTransform};
+use api::{LayoutVector2D, PipelineId, PropertyBinding, ScrollClamping, ScrollEventPhase};
+use api::{ScrollLocation, ScrollSensitivity, StickyOffsetBounds, WorldPoint};
 use clip::{ClipSourcesHandle, ClipStore};
 use clip_scroll_tree::{CoordinateSystemId, TransformUpdateState};
 use euclid::SideOffsets2D;
 use geometry::ray_intersects_rect;
 use gpu_cache::GpuCache;
 use gpu_types::{ClipScrollNodeIndex, ClipScrollNodeData};
 use render_task::{ClipChain, ClipWorkItem};
 use resource_cache::ResourceCache;
@@ -55,17 +55,17 @@ pub enum NodeType {
     /// A reference frame establishes a new coordinate space in the tree.
     ReferenceFrame(ReferenceFrameInfo),
 
     /// Other nodes just do clipping, but no transformation.
     Clip(ClipSourcesHandle),
 
     /// Transforms it's content, but doesn't clip it. Can also be adjusted
     /// by scroll events or setting scroll offsets.
-    ScrollFrame(ScrollingState),
+    ScrollFrame(ScrollFrameInfo),
 
     /// A special kind of node that adjusts its position based on the position
     /// of its parent node and a given set of sticky positioning offset bounds.
     /// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here:
     /// https://www.w3.org/TR/css-position-3/#sticky-pos
     StickyFrame(StickyFrameInfo),
 }
 
@@ -148,26 +148,28 @@ impl ClipScrollNode {
             coordinate_system_relative_transform: TransformOrOffset::zero(),
             node_data_index: ClipScrollNodeIndex(0),
         }
     }
 
     pub fn new_scroll_frame(
         pipeline_id: PipelineId,
         parent_id: ClipId,
+        external_id: Option<ExternalScrollId>,
         frame_rect: &LayerRect,
         content_size: &LayerSize,
         scroll_sensitivity: ScrollSensitivity,
     ) -> Self {
-        let node_type = NodeType::ScrollFrame(ScrollingState::new(
+        let node_type = NodeType::ScrollFrame(ScrollFrameInfo::new(
             scroll_sensitivity,
             LayerSize::new(
                 (content_size.width - frame_rect.size.width).max(0.0),
                 (content_size.height - frame_rect.size.height).max(0.0)
-            )
+            ),
+            external_id,
         ));
 
         Self::new(pipeline_id, Some(parent_id), frame_rect, node_type)
     }
 
     pub fn new_clip_node(
         pipeline_id: PipelineId,
         parent_id: ClipId,
@@ -206,17 +208,17 @@ impl ClipScrollNode {
         Self::new(pipeline_id, Some(parent_id), &frame_rect, node_type)
     }
 
 
     pub fn add_child(&mut self, child: ClipId) {
         self.children.push(child);
     }
 
-    pub fn apply_old_scrolling_state(&mut self, old_scrolling_state: &ScrollingState) {
+    pub fn apply_old_scrolling_state(&mut self, old_scrolling_state: &ScrollFrameInfo) {
         match self.node_type {
             NodeType::ScrollFrame(ref mut scrolling) => {
                 let scroll_sensitivity = scrolling.scroll_sensitivity;
                 let scrollable_size = scrolling.scrollable_size;
                 *scrolling = *old_scrolling_state;
                 scrolling.scroll_sensitivity = scroll_sensitivity;
                 scrolling.scrollable_size = scrollable_size;
             }
@@ -760,45 +762,65 @@ impl ClipScrollNode {
     }
 
     pub fn is_overscrolling(&self) -> bool {
         match self.node_type {
             NodeType::ScrollFrame(ref state) => state.overscroll_amount() != LayerVector2D::zero(),
             _ => false,
         }
     }
+
+    pub fn matches_id(&self, node_id: ClipId, id_to_match: IdType) -> bool {
+        let external_id = match id_to_match {
+            IdType::ExternalScrollId(id) => id,
+            IdType::ClipId(clip_id) => return node_id == clip_id,
+        };
+
+        match self.node_type {
+            NodeType::ScrollFrame(info) if info.external_id == Some(external_id) => true,
+            _ => false,
+        }
+    }
 }
 
 #[derive(Copy, Clone, Debug)]
-pub struct ScrollingState {
+pub struct ScrollFrameInfo {
     pub offset: LayerVector2D,
     pub spring: Spring,
     pub started_bouncing_back: bool,
     pub bouncing_back: bool,
     pub should_handoff_scroll: bool,
     pub scroll_sensitivity: ScrollSensitivity,
 
     /// Amount that this ScrollFrame can scroll in both directions.
     pub scrollable_size: LayerSize,
 
+    /// An external id to identify this scroll frame to API clients. This
+    /// allows setting scroll positions via the API without relying on ClipsIds
+    /// which may change between frames.
+    pub external_id: Option<ExternalScrollId>,
+
 }
 
 /// Manages scrolling offset, overscroll state, etc.
-impl ScrollingState {
-    pub fn new(scroll_sensitivity: ScrollSensitivity,
-               scrollable_size: LayerSize
-    ) -> ScrollingState {
-        ScrollingState {
+impl ScrollFrameInfo {
+    pub fn new(
+        scroll_sensitivity: ScrollSensitivity,
+        scrollable_size: LayerSize,
+        external_id: Option<ExternalScrollId>,
+    ) -> ScrollFrameInfo {
+        ScrollFrameInfo {
             offset: LayerVector2D::zero(),
             spring: Spring::at(LayerPoint::zero(), STIFFNESS, DAMPING),
             started_bouncing_back: false,
             bouncing_back: false,
             should_handoff_scroll: false,
             scroll_sensitivity,
             scrollable_size,
+            external_id,
         }
     }
 
     pub fn sensitive_to_input_events(&self) -> bool {
         match self.scroll_sensitivity {
             ScrollSensitivity::ScriptAndInputEvents => true,
             ScrollSensitivity::Script => false,
         }
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,27 +1,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ClipId, ClipChainId, DeviceIntRect, DevicePixelScale, LayerPoint, LayerRect};
-use api::{LayerToWorldTransform, LayerVector2D, LayoutTransform, PipelineId, PropertyBinding};
-use api::{ScrollClamping, ScrollEventPhase, ScrollLayerState, ScrollLocation, WorldPoint};
+use api::{ClipChainId, ClipId, DeviceIntRect, DevicePixelScale, ExternalScrollId, IdType};
+use api::{LayerPoint, LayerRect, LayerToWorldTransform, LayerVector2D, LayoutTransform};
+use api::{PipelineId, PropertyBinding, ScrollClamping, ScrollEventPhase, ScrollLocation};
+use api::{ScrollNodeState, WorldPoint};
 use clip::ClipStore;
-use clip_scroll_node::{ClipScrollNode, NodeType, ScrollingState, StickyFrameInfo};
+use clip_scroll_node::{ClipScrollNode, NodeType, ScrollFrameInfo, StickyFrameInfo};
 use gpu_cache::GpuCache;
 use gpu_types::{ClipScrollNodeIndex, ClipScrollNodeData};
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
 use render_task::ClipChain;
 use resource_cache::ResourceCache;
 use scene::SceneProperties;
 use util::TransformOrOffset;
 
-pub type ScrollStates = FastHashMap<ClipId, ScrollingState>;
+pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
 
 /// An id that identifies coordinate systems in the ClipScrollTree. Each
 /// coordinate system has an id and those ids will be shared when the coordinates
 /// system are the same or are in the same axis-aligned space. This allows
 /// for optimizing mask generation.
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -54,17 +55,17 @@ pub struct ClipScrollTree {
     /// A Vec of all descriptors that describe ClipChains in the order in which they are
     /// encountered during display list flattening. ClipChains are expected to never be
     /// the children of ClipChains later in the list.
     clip_chains_descriptors: Vec<ClipChainDescriptor>,
 
     /// A HashMap of built ClipChains that are described by `clip_chains_descriptors`.
     pub clip_chains: FastHashMap<ClipChainId, ClipChain>,
 
-    pub pending_scroll_offsets: FastHashMap<ClipId, (LayerPoint, ScrollClamping)>,
+    pub pending_scroll_offsets: FastHashMap<IdType, (LayerPoint, ScrollClamping)>,
 
     /// The ClipId of the currently scrolling node. Used to allow the same
     /// node to scroll even if a touch operation leaves the boundaries of that node.
     pub currently_scrolling_node_id: Option<ClipId>,
 
     /// The current frame id, used for giving a unique id to all new dynamically
     /// added frames and clips. The ClipScrollTree increments this by one every
     /// time a new dynamic frame is created.
@@ -229,57 +230,56 @@ impl ClipScrollTree {
             }
         }
 
         cache.insert(*node_id, Some(point_in_layer));
 
         true
     }
 
-    pub fn get_scroll_node_state(&self) -> Vec<ScrollLayerState> {
+    pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
         let mut result = vec![];
-        for (id, node) in self.nodes.iter() {
-            if let NodeType::ScrollFrame(scrolling) = node.node_type {
-                result.push(ScrollLayerState {
-                    id: *id,
-                    scroll_offset: scrolling.offset,
-                })
+        for node in self.nodes.values() {
+            if let NodeType::ScrollFrame(info) = node.node_type {
+                if let Some(id) = info.external_id {
+                    result.push(ScrollNodeState { id, scroll_offset: info.offset })
+                }
             }
         }
         result
     }
 
     pub fn drain(&mut self) -> ScrollStates {
         self.current_new_node_item = 1;
 
         let mut scroll_states = FastHashMap::default();
-        for (layer_id, old_node) in &mut self.nodes.drain() {
-            if self.pipelines_to_discard.contains(&layer_id.pipeline_id()) {
+        for (node_id, old_node) in &mut self.nodes.drain() {
+            if self.pipelines_to_discard.contains(&node_id.pipeline_id()) {
                 continue;
             }
 
-            if let NodeType::ScrollFrame(scrolling) = old_node.node_type {
-                scroll_states.insert(layer_id, scrolling);
+            match old_node.node_type {
+                NodeType::ScrollFrame(info) if info.external_id.is_some() => {
+                    scroll_states.insert(info.external_id.unwrap(), info);
+                }
+                _ => {}
             }
         }
 
         self.pipelines_to_discard.clear();
         self.clip_chains.clear();
         self.clip_chains_descriptors.clear();
         scroll_states
     }
 
-    pub fn scroll_node(&mut self, origin: LayerPoint, id: ClipId, clamp: ScrollClamping) -> bool {
-        if self.nodes.is_empty() {
-            self.pending_scroll_offsets.insert(id, (origin, clamp));
-            return false;
-        }
-
-        if let Some(node) = self.nodes.get_mut(&id) {
-            return node.set_scroll_origin(&origin, clamp);
+    pub fn scroll_node(&mut self, origin: LayerPoint, id: IdType, clamp: ScrollClamping) -> bool {
+        for (clip_id, node) in &mut self.nodes {
+            if node.matches_id(*clip_id, id) {
+                return node.set_scroll_origin(&origin, clamp);
+            }
         }
 
         self.pending_scroll_offsets.insert(id, (origin, clamp));
         false
     }
 
     pub fn scroll(
         &mut self,
@@ -486,26 +486,40 @@ impl ClipScrollTree {
 
     pub fn tick_scrolling_bounce_animations(&mut self) {
         for (_, node) in &mut self.nodes {
             node.tick_scrolling_bounce_animation()
         }
     }
 
     pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
-        // TODO(gw): These are all independent - can be run through thread pool if it shows up
-        // in the profile!
         for (clip_id, node) in &mut self.nodes {
-            if let Some(scrolling_state) = old_states.get(clip_id) {
-                node.apply_old_scrolling_state(scrolling_state);
+            let external_id = match node.node_type {
+                NodeType::ScrollFrame(info) if info.external_id.is_some() => info.external_id,
+                _ => None,
+            };
+
+            if let Some(external_id) = external_id {
+                if let Some(scrolling_state) = old_states.get(&external_id) {
+                    node.apply_old_scrolling_state(scrolling_state);
+                }
             }
 
-            if let Some((pending_offset, clamping)) = self.pending_scroll_offsets.remove(clip_id) {
-                node.set_scroll_origin(&pending_offset, clamping);
+            let id = IdType::ClipId(*clip_id);
+            if let Some((offset, clamping)) = self.pending_scroll_offsets.remove(&id) {
+                node.set_scroll_origin(&offset, clamping);
             }
+
+            if let Some(external_id) = external_id {
+                let id = IdType::ExternalScrollId(external_id);
+                if let Some((offset, clamping)) = self.pending_scroll_offsets.remove(&id) {
+                    node.set_scroll_origin(&offset, clamping);
+                }
+            }
+
         }
     }
 
     pub fn generate_new_clip_id(&mut self, pipeline_id: PipelineId) -> ClipId {
         let new_id = ClipId::DynamicallyAddedNode(self.current_new_node_item, pipeline_id);
         self.current_new_node_item += 1;
         new_id
     }
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -755,27 +755,27 @@ impl Device {
     }
 
     pub fn compile_shader(
         gl: &gl::Gl,
         name: &str,
         shader_type: gl::GLenum,
         source: &String,
     ) -> Result<gl::GLuint, ShaderError> {
-        debug!("compile {:?}", name);
+        debug!("compile {}", name);
         let id = gl.create_shader(shader_type);
         gl.shader_source(id, &[source.as_bytes()]);
         gl.compile_shader(id);
         let log = gl.get_shader_info_log(id);
         if gl.get_shader_iv(id, gl::COMPILE_STATUS) == (0 as gl::GLint) {
-            println!("Failed to compile shader: {:?}\n{}", name, log);
+            println!("Failed to compile shader: {}\n{}", name, log);
             Err(ShaderError::Compilation(name.to_string(), log))
         } else {
             if !log.is_empty() {
-                println!("Warnings detected on shader: {:?}\n{}", name, log);
+                println!("Warnings detected on shader: {}\n{}", name, log);
             }
             Ok(id)
         }
     }
 
     pub fn begin_frame(&mut self) -> FrameId {
         debug_assert!(!self.inside_frame);
         self.inside_frame = true;
@@ -1312,17 +1312,17 @@ impl Device {
         if let Some(ref cached_programs) = self.cached_programs {
             if let Some(binary) = cached_programs.binaries.borrow().get(&sources)
             {
                 self.gl.program_binary(pid, binary.format, &binary.binary);
 
                 if self.gl.get_program_iv(pid, gl::LINK_STATUS) == (0 as gl::GLint) {
                     let error_log = self.gl.get_program_info_log(pid);
                     println!(
-                      "Failed to load a program object with a program binary: {:?} renderer {}\n{}",
+                      "Failed to load a program object with a program binary: {} renderer {}\n{}",
                       base_filename,
                       self.renderer_name,
                       error_log
                     );
                 } else {
                     loaded = true;
                 }
             }
@@ -1374,17 +1374,17 @@ impl Device {
             self.gl.detach_shader(pid, vs_id);
             self.gl.detach_shader(pid, fs_id);
             self.gl.delete_shader(vs_id);
             self.gl.delete_shader(fs_id);
 
             if self.gl.get_program_iv(pid, gl::LINK_STATUS) == (0 as gl::GLint) {
                 let error_log = self.gl.get_program_info_log(pid);
                 println!(
-                    "Failed to link shader program: {:?}\n{}",
+                    "Failed to link shader program: {}\n{}",
                     base_filename,
                     error_log
                 );
                 self.gl.delete_program(pid);
                 return Err(ShaderError::Link(base_filename.to_string(), error_log));
             }
         }
 
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -1,21 +1,20 @@
 
 /* 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::{BuiltDisplayListIter, ClipAndScrollInfo, ClipId, ColorF, ComplexClipRegion};
-use api::{DevicePixelScale, DeviceUintRect, DeviceUintSize};
-use api::{DisplayItemRef, DocumentLayer, Epoch, FilterOp};
-use api::{ImageDisplayItem, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect};
-use api::{LayerSize, LayerVector2D, LayoutSize};
-use api::{LocalClip, PipelineId, ScrollClamping, ScrollEventPhase, ScrollLayerState};
-use api::{ScrollLocation, ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext};
-use api::{TileOffset, TransformStyle, WorldPoint};
+use api::{DevicePixelScale, DeviceUintRect, DeviceUintSize, DisplayItemRef, DocumentLayer, Epoch};
+use api::{ExternalScrollId, FilterOp, IdType, ImageDisplayItem, ItemRange, LayerPoint};
+use api::{LayerPrimitiveInfo, LayerRect, LayerSize, LayerVector2D, LayoutSize, LocalClip};
+use api::{PipelineId, ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollNodeState};
+use api::{ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext, TileOffset};
+use api::{TransformStyle, WorldPoint};
 use clip::ClipRegion;
 use clip_scroll_node::StickyFrameInfo;
 use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use euclid::rect;
 use frame_builder::{FrameBuilder, FrameBuilderConfig, ScrollbarInfo};
 use gpu_cache::GpuCache;
 use internal_types::{FastHashMap, FastHashSet, RenderedDocument};
 use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
@@ -200,16 +199,17 @@ impl<'a> FlattenContext<'a> {
         );
     }
 
     fn flatten_scroll_frame(
         &mut self,
         pipeline_id: PipelineId,
         parent_id: &ClipId,
         new_scroll_frame_id: &ClipId,
+        external_id: Option<ExternalScrollId>,
         frame_rect: &LayerRect,
         content_rect: &LayerRect,
         clip_region: ClipRegion,
         scroll_sensitivity: ScrollSensitivity,
     ) {
         let clip_id = self.clip_scroll_tree.generate_new_clip_id(pipeline_id);
         self.builder.add_clip_node(
             clip_id,
@@ -217,16 +217,17 @@ impl<'a> FlattenContext<'a> {
             pipeline_id,
             clip_region,
             self.clip_scroll_tree,
         );
 
         self.builder.add_scroll_frame(
             *new_scroll_frame_id,
             clip_id,
+            external_id,
             pipeline_id,
             &frame_rect,
             &content_rect.size,
             scroll_sensitivity,
             self.clip_scroll_tree,
         );
     }
 
@@ -366,16 +367,17 @@ impl<'a> FlattenContext<'a> {
             origin,
             true,
             self.clip_scroll_tree,
         );
 
         self.builder.add_scroll_frame(
             ClipId::root_scroll_node(pipeline_id),
             iframe_reference_frame_id,
+            Some(ExternalScrollId(0, pipeline_id)),
             pipeline_id,
             &iframe_rect,
             &pipeline.content_size,
             ScrollSensitivity::ScriptAndInputEvents,
             self.clip_scroll_tree,
         );
 
         self.flatten_root(
@@ -453,17 +455,18 @@ impl<'a> FlattenContext<'a> {
                             instance,
                             &text_info.color,
                             item.glyphs(),
                             item.display_list().get(item.glyphs()).count(),
                             text_info.glyph_options,
                         );
                     }
                     None => {
-                        warn!("Unknown font instance key: {:?}", text_info.font_key);
+                        warn!("Unknown font instance key");
+                        debug!("key={:?}", text_info.font_key);
                     }
                 }
             }
             SpecificDisplayItem::Rectangle(ref info) => {
                 self.builder.add_solid_rectangle(
                     clip_and_scroll,
                     &prim_info,
                     info.color,
@@ -598,16 +601,17 @@ impl<'a> FlattenContext<'a> {
                 let frame_rect = item.local_clip()
                     .clip_rect()
                     .translate(&reference_frame_relative_offset);
                 let content_rect = item.rect().translate(&reference_frame_relative_offset);
                 self.flatten_scroll_frame(
                     pipeline_id,
                     &clip_and_scroll.scroll_node_id,
                     &info.id,
+                    info.external_id,
                     &frame_rect,
                     &content_rect,
                     clip_region,
                     info.scroll_sensitivity,
                 );
             }
             SpecificDisplayItem::StickyFrame(ref info) => {
                 let frame_rect = item.rect().translate(&reference_frame_relative_offset);
@@ -976,22 +980,22 @@ impl FrameContext {
 
         self.clip_scroll_tree.drain()
     }
 
     pub fn get_clip_scroll_tree(&self) -> &ClipScrollTree {
         &self.clip_scroll_tree
     }
 
-    pub fn get_scroll_node_state(&self) -> Vec<ScrollLayerState> {
+    pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
         self.clip_scroll_tree.get_scroll_node_state()
     }
 
     /// Returns true if the node actually changed position or false otherwise.
-    pub fn scroll_node(&mut self, origin: LayerPoint, id: ClipId, clamp: ScrollClamping) -> bool {
+    pub fn scroll_node(&mut self, origin: LayerPoint, id: IdType, clamp: ScrollClamping) -> bool {
         self.clip_scroll_tree.scroll_node(origin, id, clamp)
     }
 
     /// Returns true if any nodes actually changed position or false otherwise.
     pub fn scroll(
         &mut self,
         scroll_location: ScrollLocation,
         cursor: WorldPoint,
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,38 +1,38 @@
 /* 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, BorderDetails, BorderDisplayItem, BuiltDisplayList, ClipAndScrollInfo, ClipId};
-use api::{ColorF, ColorU, DeviceIntPoint, DevicePixelScale, DeviceUintPoint, DeviceUintRect};
-use api::{DeviceUintSize, DocumentLayer, ExtendMode, FontRenderMode, GlyphInstance, GlyphOptions};
-use api::{GradientStop, HitTestFlags, HitTestItem, HitTestResult, ImageKey, ImageRendering};
-use api::{Epoch, ItemRange, ItemTag, LayerPoint, LayerPrimitiveInfo, LayerRect, LayerSize};
-use api::{LayerTransform, LayerVector2D, LayoutTransform, LayoutVector2D, LineOrientation};
-use api::{LineStyle, LocalClip, PipelineId, PremultipliedColorF, PropertyBinding, RepeatMode};
-use api::{ScrollSensitivity, Shadow, TexelRect, TileOffset, TransformStyle, WorldPoint};
-use api::{DeviceIntRect, DeviceIntSize, YuvColorSpace, YuvData};
+use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayList, ClipAndScrollInfo};
+use api::{ClipId, ColorF, ColorU, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
+use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, Epoch, ExtendMode};
+use api::{ExternalScrollId, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
+use api::{HitTestFlags, HitTestItem, HitTestResult, ImageKey, ImageRendering, ItemRange, ItemTag};
+use api::{LayerPoint, LayerPrimitiveInfo, LayerRect, LayerSize, LayerTransform, LayerVector2D};
+use api::{LayoutTransform, LayoutVector2D, LineOrientation, LineStyle, LocalClip, PipelineId};
+use api::{PremultipliedColorF, PropertyBinding, RepeatMode, ScrollSensitivity, Shadow, TexelRect};
+use api::{TileOffset, TransformStyle, WorldPoint, WorldToLayerTransform, YuvColorSpace, YuvData};
 use app_units::Au;
 use border::ImageBorderSegment;
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore, Contains};
 use clip_scroll_node::{ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use euclid::{SideOffsets2D, vec2};
 use frame::FrameId;
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCache;
 use gpu_types::{ClipChainRectIndex, ClipScrollNodeData, PictureType};
 use internal_types::{FastHashMap, FastHashSet, RenderPassIndex};
 use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive, PictureSurface};
 use prim_store::{BrushKind, BrushPrimitive, ImageCacheKey, YuvImagePrimitiveCpu};
 use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, ImageSource, PrimitiveKind};
-use prim_store::{PrimitiveContainer, PrimitiveIndex, SpecificPrimitiveIndex};
+use prim_store::{PrimitiveContainer, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu};
-use prim_store::{BrushSegmentDescriptor, TextRunPrimitiveCpu};
+use prim_store::{BrushSegmentDescriptor, PrimitiveRun, TextRunPrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_task::{ClearMode, ClipChain, RenderTask, RenderTaskId, RenderTaskTree};
 use resource_cache::ResourceCache;
 use scene::{ScenePipeline, SceneProperties};
 use std::{mem, usize, f32};
 use tiling::{CompositeOps, Frame, RenderPass, RenderTargetKind};
 use tiling::{RenderPassKind, RenderTargetContext, ScrollbarPrimitive};
 use util::{self, MaxRect, pack_as_float, RectHelpers, recycle_vec};
@@ -139,32 +139,51 @@ pub struct FrameState<'a> {
     pub render_tasks: &'a mut RenderTaskTree,
     pub profile_counters: &'a mut FrameProfileCounters,
     pub clip_store: &'a mut ClipStore,
     pub local_clip_rects: &'a mut Vec<LayerRect>,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
 }
 
+pub struct PictureContext<'a> {
+    pub pipeline_id: PipelineId,
+    pub perform_culling: bool,
+    pub prim_runs: Vec<PrimitiveRun>,
+    pub original_reference_frame_id: Option<ClipId>,
+    pub display_list: &'a BuiltDisplayList,
+    pub draw_text_transformed: bool,
+    pub inv_world_transform: Option<WorldToLayerTransform>,
+}
+
+pub struct PictureState {
+    pub tasks: Vec<RenderTaskId>,
+}
+
+impl PictureState {
+    pub fn new() -> PictureState {
+        PictureState {
+            tasks: Vec::new(),
+        }
+    }
+}
+
 pub struct PrimitiveRunContext<'a> {
-    pub display_list: &'a BuiltDisplayList,
     pub clip_chain: Option<&'a ClipChain>,
     pub scroll_node: &'a ClipScrollNode,
     pub clip_chain_rect_index: ClipChainRectIndex,
 }
 
 impl<'a> PrimitiveRunContext<'a> {
     pub fn new(
-        display_list: &'a BuiltDisplayList,
         clip_chain: Option<&'a ClipChain>,
         scroll_node: &'a ClipScrollNode,
         clip_chain_rect_index: ClipChainRectIndex,
     ) -> Self {
         PrimitiveRunContext {
-            display_list,
             clip_chain,
             scroll_node,
             clip_chain_rect_index,
         }
     }
 }
 
 impl FrameBuilder {
@@ -646,16 +665,17 @@ impl FrameBuilder {
         );
 
         let topmost_scrolling_node_id = ClipId::root_scroll_node(pipeline_id);
         clip_scroll_tree.topmost_scrolling_node_id = topmost_scrolling_node_id;
 
         self.add_scroll_frame(
             topmost_scrolling_node_id,
             clip_scroll_tree.root_reference_frame_id,
+            Some(ExternalScrollId(0, pipeline_id)),
             pipeline_id,
             &viewport_rect,
             content_size,
             ScrollSensitivity::ScriptAndInputEvents,
             clip_scroll_tree,
         );
 
         topmost_scrolling_node_id
@@ -678,25 +698,27 @@ impl FrameBuilder {
         let node = ClipScrollNode::new_clip_node(pipeline_id, parent_id, handle, clip_rect);
         clip_scroll_tree.add_node(node, new_node_id);
     }
 
     pub fn add_scroll_frame(
         &mut self,
         new_node_id: ClipId,
         parent_id: ClipId,
+        external_id: Option<ExternalScrollId>,
         pipeline_id: PipelineId,
         frame_rect: &LayerRect,
         content_size: &LayerSize,
         scroll_sensitivity: ScrollSensitivity,
         clip_scroll_tree: &mut ClipScrollTree,
     ) {
         let node = ClipScrollNode::new_scroll_frame(
             pipeline_id,
             parent_id,
+            external_id,
             frame_rect,
             content_size,
             scroll_sensitivity,
         );
 
         clip_scroll_tree.add_node(node, new_node_id);
     }
 
@@ -1607,17 +1629,16 @@ impl FrameBuilder {
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
         if self.prim_store.cpu_pictures.is_empty() {
             return None
         }
 
         // The root picture is always the first one added.
-        let prim_run_cmds = mem::replace(&mut self.prim_store.cpu_pictures[0].runs, Vec::new());
         let root_clip_scroll_node = &clip_scroll_tree.nodes[&clip_scroll_tree.root_reference_frame_id()];
 
         let display_list = &pipelines
             .get(&root_clip_scroll_node.pipeline_id)
             .expect("No display list?")
             .display_list;
 
         let frame_context = FrameContext {
@@ -1633,48 +1654,47 @@ impl FrameBuilder {
             render_tasks,
             profile_counters,
             clip_store: &mut self.clip_store,
             local_clip_rects,
             resource_cache,
             gpu_cache,
         };
 
-        let root_prim_run_context = PrimitiveRunContext::new(
+        let pic_context = PictureContext {
+            pipeline_id: root_clip_scroll_node.pipeline_id,
+            perform_culling: true,
+            prim_runs: mem::replace(&mut self.prim_store.cpu_pictures[0].runs, Vec::new()),
+            original_reference_frame_id: None,
             display_list,
-            root_clip_scroll_node.clip_chain.as_ref(),
-            root_clip_scroll_node,
-            ClipChainRectIndex(0),
-        );
+            draw_text_transformed: true,
+            inv_world_transform: None,
+        };
 
-        let mut child_tasks = Vec::new();
+        let mut pic_state = PictureState::new();
+
         self.prim_store.reset_prim_visibility();
         self.prim_store.prepare_prim_runs(
-            &prim_run_cmds,
-            root_clip_scroll_node.pipeline_id,
-            &root_prim_run_context,
-            true,
-            &mut child_tasks,
-            None,
-            SpecificPrimitiveIndex(0),
+            &pic_context,
+            &mut pic_state,
             &frame_context,
             &mut frame_state,
         );
 
         let pic = &mut self.prim_store.cpu_pictures[0];
-        pic.runs = prim_run_cmds;
+        pic.runs = pic_context.prim_runs;
 
         let root_render_task = RenderTask::new_picture(
             None,
             PrimitiveIndex(0),
             RenderTargetKind::Color,
             ContentOrigin::Screen(DeviceIntPoint::zero()),
             PremultipliedColorF::TRANSPARENT,
             ClearMode::Transparent,
-            child_tasks,
+            pic_state.tasks,
             PictureType::Image,
         );
 
         let render_task_id = frame_state.render_tasks.add(root_render_task);
         pic.surface = Some(PictureSurface::RenderTask(render_task_id));
         Some(render_task_id)
     }
 
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ColorF, ClipAndScrollInfo, FilterOp, MixBlendMode};
 use api::{DeviceIntPoint, DeviceIntRect, LayerToWorldScale, PipelineId};
 use api::{BoxShadowClipMode, LayerPoint, LayerRect, LayerVector2D, Shadow};
 use api::{ClipId, PremultipliedColorF};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowCacheKey};
-use frame_builder::{FrameContext, FrameState};
+use frame_builder::{FrameContext, FrameState, PictureState};
 use gpu_cache::GpuDataRequest;
 use gpu_types::{BrushImageKind, PictureType};
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
 use render_task::{ClearMode, RenderTask, RenderTaskCacheKey};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId};
 use resource_cache::CacheItem;
 use scene::{FilterOpHelpers, SceneProperties};
 use tiling::RenderTargetKind;
@@ -326,18 +326,18 @@ impl PicturePrimitive {
         }
     }
 
     pub fn prepare_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_screen_rect: &DeviceIntRect,
         prim_local_rect: &LayerRect,
-        child_tasks: Vec<RenderTaskId>,
-        parent_tasks: &mut Vec<RenderTaskId>,
+        pic_state_for_children: PictureState,
+        pic_state: &mut PictureState,
         frame_context: &FrameContext,
         frame_state: &mut FrameState,
     ) {
         let content_scale = LayerToWorldScale::new(1.0) * frame_context.device_pixel_scale;
 
         match self.kind {
             PictureKind::Image {
                 ref mut secondary_render_task_id,
@@ -349,46 +349,46 @@ impl PicturePrimitive {
                     Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
                         let picture_task = RenderTask::new_picture(
                             Some(prim_screen_rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             content_origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
-                            child_tasks,
+                            pic_state_for_children.tasks,
                             PictureType::Image,
                         );
 
                         let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
                         let picture_task_id = frame_state.render_tasks.add(picture_task);
 
                         let (blur_render_task, _) = RenderTask::new_blur(
                             blur_std_deviation,
                             picture_task_id,
                             frame_state.render_tasks,
                             RenderTargetKind::Color,
                             ClearMode::Transparent,
                             PremultipliedColorF::TRANSPARENT,
                         );
 
                         let render_task_id = frame_state.render_tasks.add(blur_render_task);
-                        parent_tasks.push(render_task_id);
+                        pic_state.tasks.push(render_task_id);
                         self.surface = Some(PictureSurface::RenderTask(render_task_id));
                     }
                     Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, color))) => {
                         let rect = (prim_local_rect.translate(&-offset) * content_scale).round().to_i32();
                         let picture_task = RenderTask::new_picture(
                             Some(rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             ContentOrigin::Screen(rect.origin),
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
-                            child_tasks,
+                            pic_state_for_children.tasks,
                             PictureType::Image,
                         );
 
                         let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
                         let picture_task_id = frame_state.render_tasks.add(picture_task);
 
                         let (blur_render_task, _) = RenderTask::new_blur(
                             blur_std_deviation.round(),
@@ -397,84 +397,84 @@ impl PicturePrimitive {
                             RenderTargetKind::Color,
                             ClearMode::Transparent,
                             color.premultiplied(),
                         );
 
                         *secondary_render_task_id = Some(picture_task_id);
 
                         let render_task_id = frame_state.render_tasks.add(blur_render_task);
-                        parent_tasks.push(render_task_id);
+                        pic_state.tasks.push(render_task_id);
                         self.surface = Some(PictureSurface::RenderTask(render_task_id));
                     }
                     Some(PictureCompositeMode::MixBlend(..)) => {
                         let picture_task = RenderTask::new_picture(
                             Some(prim_screen_rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             content_origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
-                            child_tasks,
+                            pic_state_for_children.tasks,
                             PictureType::Image,
                         );
 
                         let readback_task_id = frame_state.render_tasks.add(RenderTask::new_readback(*prim_screen_rect));
 
                         *secondary_render_task_id = Some(readback_task_id);
-                        parent_tasks.push(readback_task_id);
+                        pic_state.tasks.push(readback_task_id);
 
                         let render_task_id = frame_state.render_tasks.add(picture_task);
-                        parent_tasks.push(render_task_id);
+                        pic_state.tasks.push(render_task_id);
                         self.surface = Some(PictureSurface::RenderTask(render_task_id));
                     }
                     Some(PictureCompositeMode::Filter(filter)) => {
                         // If this filter is not currently going to affect
                         // the picture, just collapse this picture into the
                         // current render task. This most commonly occurs
                         // when opacity == 1.0, but can also occur on other
                         // filters and be a significant performance win.
                         if filter.is_noop() {
-                            parent_tasks.extend(child_tasks);
+                            pic_state.tasks.extend(pic_state_for_children.tasks);
                             self.surface = None;
                         } else {
                             let picture_task = RenderTask::new_picture(
                                 Some(prim_screen_rect.size),
                                 prim_index,
                                 RenderTargetKind::Color,
                                 content_origin,
                                 PremultipliedColorF::TRANSPARENT,
                                 ClearMode::Transparent,
-                                child_tasks,
+                                pic_state_for_children.tasks,
                                 PictureType::Image,
                             );
 
                             let render_task_id = frame_state.render_tasks.add(picture_task);
-                            parent_tasks.push(render_task_id);
+                            pic_state.tasks.push(render_task_id);
                             self.surface = Some(PictureSurface::RenderTask(render_task_id));
                         }
                     }
                     Some(PictureCompositeMode::Blit) => {
                         let picture_task = RenderTask::new_picture(
                             Some(prim_screen_rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             content_origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
-                            child_tasks,
+                            pic_state_for_children.tasks,
                             PictureType::Image,
                         );
 
                         let render_task_id = frame_state.render_tasks.add(picture_task);
-                        parent_tasks.push(render_task_id);
+                        pic_state.tasks.push(render_task_id);
                         self.surface = Some(PictureSurface::RenderTask(render_task_id));
                     }
                     None => {
-                        parent_tasks.extend(child_tasks);
+                        pic_state.tasks.extend(pic_state_for_children.tasks);
                         self.surface = None;
                     }
                 }
             }
             PictureKind::TextShadow { blur_radius, color, content_rect, .. } => {
                 // This is a shadow element. Create a render task that will
                 // render the text run to a target, and then apply a gaussian
                 // blur to that text run in order to build the actual primitive
@@ -511,17 +511,17 @@ impl PicturePrimitive {
                     picture_task_id,
                     frame_state.render_tasks,
                     RenderTargetKind::Color,
                     ClearMode::Transparent,
                     color.premultiplied(),
                 );
 
                 let render_task_id = frame_state.render_tasks.add(blur_render_task);
-                parent_tasks.push(render_task_id);
+                pic_state.tasks.push(render_task_id);
                 self.surface = Some(PictureSurface::RenderTask(render_task_id));
             }
             PictureKind::BoxShadow { blur_radius, clip_mode, color, content_rect, cache_key, .. } => {
                 // TODO(gw): Rounding the content rect here to device pixels is not
                 // technically correct. Ideally we should ceil() here, and ensure that
                 // the extra part pixel in the case of fractional sizes is correctly
                 // handled. For now, just use rounding which passes the existing
                 // Gecko tests.
@@ -571,17 +571,17 @@ impl PicturePrimitive {
                             picture_task_id,
                             render_tasks,
                             RenderTargetKind::Alpha,
                             blur_clear_mode,
                             color.premultiplied(),
                         );
 
                         let root_task_id = render_tasks.add(blur_render_task);
-                        parent_tasks.push(root_task_id);
+                        pic_state.tasks.push(root_task_id);
 
                         // TODO(gw): Remove the nastiness with having to pass
                         //           the scale factor through the texture cache
                         //           item user data. This will disappear once
                         //           the brush_picture shader is updated to draw
                         //           segments, since the scale factor will not
                         //           be used at all then during drawing.
                         (root_task_id, [scale_factor, 0.0, 0.0], false)
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -186,17 +186,18 @@ impl FontContext {
                 self.faces.insert(
                     *font_key,
                     Face {
                         face,
                         _bytes: Some(bytes),
                     },
                 );
             } else {
-                println!("WARN: webrender failed to load font {:?}", font_key);
+                println!("WARN: webrender failed to load font");
+                debug!("font={:?}", font_key);
             }
         }
     }
 
     pub fn add_native_font(&mut self, font_key: &FontKey, native_font_handle: NativeFontHandle) {
         if !self.faces.contains_key(&font_key) {
             let mut face: FT_Face = ptr::null_mut();
             let pathname = CString::new(native_font_handle.pathname).unwrap();
@@ -212,17 +213,18 @@ impl FontContext {
                 self.faces.insert(
                     *font_key,
                     Face {
                         face,
                         _bytes: None,
                     },
                 );
             } else {
-                println!("WARN: webrender failed to load font {:?} from path {:?}", font_key, pathname);
+                println!("WARN: webrender failed to load font");
+                debug!("font={:?}, path={:?}", font_key, pathname);
             }
         }
     }
 
     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!(result.succeeded());
@@ -326,23 +328,25 @@ impl FontContext {
                 unsafe { FT_GlyphSlot_Embolden(slot) };
             }
 
             let format = unsafe { (*slot).format };
             match format {
                 FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE |
                 FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => Some(slot),
                 _ => {
-                    error!("Unsupported {:?}", format);
+                    error!("Unsupported format");
+                    debug!("format={:?}", format);
                     None
                 }
             }
         } else {
-            error!(
-                "Unable to load glyph for {} of size {:?} from font {:?}, {:?}",
+            error!("Unable to load glyph");
+            debug!(
+                "{} of size {:?} from font {:?}, {:?}",
                 glyph.index,
                 font.size,
                 font.font_key,
                 result
             );
             None
         }
     }
@@ -559,18 +563,19 @@ impl FontContext {
         let render_mode = match (font.render_mode, font.subpx_dir) {
             (FontRenderMode::Mono, _) => FT_Render_Mode::FT_RENDER_MODE_MONO,
             (FontRenderMode::Alpha, _) => FT_Render_Mode::FT_RENDER_MODE_NORMAL,
             (FontRenderMode::Subpixel, SubpixelDirection::Vertical) => FT_Render_Mode::FT_RENDER_MODE_LCD_V,
             (FontRenderMode::Subpixel, _) => FT_Render_Mode::FT_RENDER_MODE_LCD,
         };
         let result = unsafe { FT_Render_Glyph(slot, render_mode) };
         if !result.succeeded() {
-            error!(
-                "Unable to rasterize {:?} with {:?}, {:?}",
+            error!("Unable to rasterize");
+            debug!(
+                "{:?} with {:?}, {:?}",
                 key,
                 render_mode,
                 result
             );
             false
         } else {
             true
         }
@@ -606,22 +611,23 @@ impl FontContext {
                 scale = font.size.to_f32_px() / y_size as f32;
             }
             FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => {
                 if !self.rasterize_glyph_outline(slot, font, key) {
                     return None;
                 }
             }
             _ => {
-                error!("Unsupported {:?}", format);
+                error!("Unsupported format");
+                debug!("format={:?}", format);
                 return None;
             }
         };
 
-        info!(
+        debug!(
             "Rasterizing {:?} as {:?} with dimensions {:?}",
             key,
             font.render_mode,
             dimensions
         );
 
         let bitmap = unsafe { &(*slot).bitmap };
         let pixel_mode = unsafe { mem::transmute(bitmap.pixel_mode as u32) };
@@ -634,17 +640,17 @@ impl FontContext {
                 assert!(bitmap.rows % 3 == 0);
                 (bitmap.width as usize, (bitmap.rows / 3) as usize)
             }
             FT_Pixel_Mode::FT_PIXEL_MODE_MONO |
             FT_Pixel_Mode::FT_PIXEL_MODE_GRAY |
             FT_Pixel_Mode::FT_PIXEL_MODE_BGRA => {
                 (bitmap.width as usize, bitmap.rows as usize)
             }
-            _ => panic!("Unsupported {:?}", pixel_mode),
+            _ => panic!("Unsupported mode"),
         };
         let mut final_buffer = vec![0u8; actual_width * actual_height * 4];
 
         // Extract the final glyph from FT format into BGRA8 format, which is
         // what WR expects.
         let subpixel_bgr = font.flags.contains(FontInstanceFlags::SUBPIXEL_BGR);
         let mut src_row = bitmap.buffer;
         let mut dest: usize = 0;
@@ -712,17 +718,17 @@ impl FontContext {
                     src_row = unsafe { src_row.offset((2 * bitmap.pitch) as isize) };
                 }
                 FT_Pixel_Mode::FT_PIXEL_MODE_BGRA => {
                     // The source is premultiplied BGRA data.
                     let dest_slice = &mut final_buffer[dest .. row_end];
                     let src_slice = unsafe { slice::from_raw_parts(src, dest_slice.len()) };
                     dest_slice.copy_from_slice(src_slice);
                 }
-                _ => panic!("Unsupported {:?}", pixel_mode),
+                _ => panic!("Unsupported mode"),
             }
             src_row = unsafe { src_row.offset(bitmap.pitch as isize) };
             dest = row_end;
         }
 
         match format {
             FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => {
                 if font.flags.contains(FontInstanceFlags::SYNTHETIC_ITALICS) {
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,24 +1,24 @@
 /* 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, ClipAndScrollInfo, ClipId, ClipMode};
+use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipAndScrollInfo, ClipMode};
 use api::{ColorF, ColorU, DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch};
 use api::{ComplexClipRegion, ExtendMode, FontRenderMode};
 use api::{GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag};
 use api::{LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LayerVector2D, LineOrientation};
-use api::{LineStyle, PipelineId, PremultipliedColorF, TileOffset};
+use api::{LineStyle, PremultipliedColorF, TileOffset};
 use api::{WorldToLayerTransform, YuvColorSpace, YuvFormat};
 use border::{BorderCornerInstance, BorderEdgeKind};
 use clip_scroll_tree::{CoordinateSystemId};
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipSource, ClipSourcesHandle};
-use frame_builder::{FrameContext, FrameState, PrimitiveRunContext};
+use frame_builder::{FrameContext, FrameState, PictureContext, PictureState, PrimitiveRunContext};
 use glyph_rasterizer::{FontInstance, FontTransform};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::{ClipChainRectIndex};
 use picture::{PictureKind, PicturePrimitive};
 use render_task::{BlitSource, ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipWorkItem};
 use render_task::{RenderTask, RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
@@ -1140,53 +1140,50 @@ impl PrimitiveStore {
     pub fn prim_count(&self) -> usize {
         self.cpu_metadata.len()
     }
 
     fn prepare_prim_for_render_inner(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_run_context: &PrimitiveRunContext,
-        child_tasks: Vec<RenderTaskId>,
-        parent_tasks: &mut Vec<RenderTaskId>,
-        pic_index: SpecificPrimitiveIndex,
+        pic_state_for_children: PictureState,
+        pic_context: &PictureContext,
+        pic_state: &mut PictureState,
         frame_context: &FrameContext,
         frame_state: &mut FrameState,
     ) {
         let metadata = &mut self.cpu_metadata[prim_index.0];
         match metadata.prim_kind {
             PrimitiveKind::Border => {}
             PrimitiveKind::Picture => {
                 self.cpu_pictures[metadata.cpu_prim_index.0]
                     .prepare_for_render(
                         prim_index,
                         metadata.screen_rect.as_ref().expect("bug: trying to draw an off-screen picture!?"),
                         &metadata.local_rect,
-                        child_tasks,
-                        parent_tasks,
+                        pic_state_for_children,
+                        pic_state,
                         frame_context,
                         frame_state,
                     );
             }
             PrimitiveKind::TextRun => {
-                let pic = &self.cpu_pictures[pic_index.0];
                 let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
                 // The transform only makes sense for screen space rasterization
-                let transform = match pic.kind {
-                    PictureKind::BoxShadow { .. } => None,
-                    PictureKind::TextShadow { .. } => None,
-                    PictureKind::Image { .. } => {
-                        Some(&prim_run_context.scroll_node.world_content_transform)
-                    },
+                let transform = if pic_context.draw_text_transformed {
+                    Some(&prim_run_context.scroll_node.world_content_transform)
+                } else {
+                    None
                 };
                 text.prepare_for_render(
                     frame_state.resource_cache,
                     frame_context.device_pixel_scale,
                     transform,
-                    prim_run_context.display_list,
+                    pic_context.display_list,
                     frame_state.gpu_cache,
                 );
             }
             PrimitiveKind::Image => {
                 let image_cpu = &mut self.cpu_images[metadata.cpu_prim_index.0];
                 let image_properties = frame_state
                     .resource_cache
                     .get_image_properties(image_cpu.key.image_key);
@@ -1267,17 +1264,17 @@ impl PrimitiveStore {
                                     size,
                                     BlitSource::RenderTask {
                                         task_id: cache_to_target_task_id,
                                     },
                                 );
                                 let target_to_cache_task_id = render_tasks.add(target_to_cache_task);
 
                                 // Hook this into the render task tree at the right spot.
-                                parent_tasks.push(target_to_cache_task_id);
+                                pic_state.tasks.push(target_to_cache_task_id);
 
                                 // Pass the image opacity, so that the cached render task
                                 // item inherits the same opacity properties.
                                 (target_to_cache_task_id, [0.0; 3], image_properties.descriptor.is_opaque)
                             }
                         );
                     }
                 }
@@ -1318,25 +1315,34 @@ impl PrimitiveStore {
                     image.write_gpu_blocks(request);
                 }
                 PrimitiveKind::YuvImage => {
                     let yuv_image = &self.cpu_yuv_images[metadata.cpu_prim_index.0];
                     yuv_image.write_gpu_blocks(request);
                 }
                 PrimitiveKind::AlignedGradient => {
                     let gradient = &self.cpu_gradients[metadata.cpu_prim_index.0];
-                    metadata.opacity = gradient.build_gpu_blocks_for_aligned(prim_run_context.display_list, request);
+                    metadata.opacity = gradient.build_gpu_blocks_for_aligned(
+                        pic_context.display_list,
+                        request,
+                    );
                 }
                 PrimitiveKind::AngleGradient => {
                     let gradient = &self.cpu_gradients[metadata.cpu_prim_index.0];
-                    gradient.build_gpu_blocks_for_angle_radial(prim_run_context.display_list, request);
+                    gradient.build_gpu_blocks_for_angle_radial(
+                        pic_context.display_list,
+                        request,
+                    );
                 }
                 PrimitiveKind::RadialGradient => {
                     let gradient = &self.cpu_radial_gradients[metadata.cpu_prim_index.0];
-                    gradient.build_gpu_blocks_for_angle_radial(prim_run_context.display_list, request);
+                    gradient.build_gpu_blocks_for_angle_radial(
+                        pic_context.display_list,
+                        request,
+                    );
                 }
                 PrimitiveKind::TextRun => {
                     let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
                     text.write_gpu_blocks(&mut request);
                 }
                 PrimitiveKind::Picture => {
                     let pic = &self.cpu_pictures[metadata.cpu_prim_index.0];
                     pic.write_gpu_blocks(&mut request);
@@ -1493,20 +1499,20 @@ impl PrimitiveStore {
             }
         }
     }
 
     fn update_clip_task_for_brush(
         &mut self,
         prim_run_context: &PrimitiveRunContext,
         prim_index: PrimitiveIndex,
-        tasks: &mut Vec<RenderTaskId>,
         clips: &Vec<ClipWorkItem>,
         combined_outer_rect: &DeviceIntRect,
         has_clips_from_other_coordinate_systems: bool,
+        pic_state: &mut PictureState,
         frame_context: &FrameContext,
         frame_state: &mut FrameState,
     ) -> bool {
         let metadata = &self.cpu_metadata[prim_index.0];
         let brush = match metadata.prim_kind {
             PrimitiveKind::Brush => {
                 &mut self.cpu_brushes[metadata.cpu_prim_index.0]
             }
@@ -1550,31 +1556,31 @@ impl PrimitiveStore {
             segment.clip_task_id = intersected_rect.map(|bounds| {
                 let clip_task = RenderTask::new_mask(
                     bounds,
                     clips.clone(),
                     prim_run_context.scroll_node.coordinate_system_id,
                 );
 
                 let clip_task_id = frame_state.render_tasks.add(clip_task);
-                tasks.push(clip_task_id);
+                pic_state.tasks.push(clip_task_id);
 
                 clip_task_id
             })
         }
 
         true
     }
 
     fn update_clip_task(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_run_context: &PrimitiveRunContext,
         prim_screen_rect: &DeviceIntRect,
-        tasks: &mut Vec<RenderTaskId>,
+        pic_state: &mut PictureState,
         frame_context: &FrameContext,
         frame_state: &mut FrameState,
     ) -> bool {
         self.cpu_metadata[prim_index.0].clip_task_id = None;
 
         let prim_screen_rect = match prim_screen_rect.intersection(&frame_context.screen_rect) {
             Some(rect) => rect,
             None => {
@@ -1670,115 +1676,123 @@ impl PrimitiveStore {
         if combined_inner_rect.contains_rect(&prim_screen_rect) {
            return true;
         }
 
         // First try to  render this primitive's mask using optimized brush rendering.
         if self.update_clip_task_for_brush(
             prim_run_context,
             prim_index,
-            tasks,
             &clips,
             &combined_outer_rect,
             has_clips_from_other_coordinate_systems,
+            pic_state,
             frame_context,
             frame_state,
         ) {
             return true;
         }
 
         let clip_task = RenderTask::new_mask(
             combined_outer_rect,
             clips,
             prim_coordinate_system_id,
         );
 
         let clip_task_id = frame_state.render_tasks.add(clip_task);
         self.cpu_metadata[prim_index.0].clip_task_id = Some(clip_task_id);
-        tasks.push(clip_task_id);
+        pic_state.tasks.push(clip_task_id);
 
         true
     }
 
     pub fn prepare_prim_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_run_context: &PrimitiveRunContext,
-        perform_culling: bool,
-        parent_tasks: &mut Vec<RenderTaskId>,
-        pic_index: SpecificPrimitiveIndex,
+        pic_context: &PictureContext,
+        pic_state: &mut PictureState,
         frame_context: &FrameContext,
         frame_state: &mut FrameState,
     ) -> Option<LayerRect> {
-        // Reset the visibility of this primitive.
+        let mut may_need_clip_mask = true;
+        let mut pic_state_for_children = PictureState::new();
+
         // Do some basic checks first, that can early out
         // without even knowing the local rect.
-        let (cpu_prim_index, dependencies, cull_children, may_need_clip_mask) = {
-            let metadata = &mut self.cpu_metadata[prim_index.0];
-            metadata.screen_rect = None;
+        let (prim_kind, cpu_prim_index) = {
+            let metadata = &self.cpu_metadata[prim_index.0];
 
-            if perform_culling &&
+            if pic_context.perform_culling &&
                !metadata.is_backface_visible &&
                prim_run_context.scroll_node.world_content_transform.is_backface_visible() {
                 return None;
             }
 
-            let (dependencies, cull_children, may_need_clip_mask) = match metadata.prim_kind {
-                PrimitiveKind::Picture => {
-                    let pic = &mut self.cpu_pictures[metadata.cpu_prim_index.0];
-
-                    if !pic.resolve_scene_properties(frame_context.scene_properties) {
-                        return None;
-                    }
-
-                    let (rfid, may_need_clip_mask) = match pic.kind {
-                        PictureKind::Image { reference_frame_id, .. } => {
-                            (Some(reference_frame_id), false)
-                        }
-                        _ => {
-                            (None, true)
-                        }
-                    };
-                    (Some((pic.pipeline_id, mem::replace(&mut pic.runs, Vec::new()), rfid)),
-                     pic.cull_children,
-                     may_need_clip_mask)
-                }
-                _ => {
-                    (None, true, true)
-                }
-            };
-
-            (metadata.cpu_prim_index, dependencies, cull_children, may_need_clip_mask)
+            (metadata.prim_kind, metadata.cpu_prim_index)
         };
 
         // If we have dependencies, we need to prepare them first, in order
         // to know the actual rect of this primitive.
         // For example, scrolling may affect the location of an item in
         // local space, which may force us to render this item on a larger
         // picture target, if being composited.
-        let mut child_tasks = Vec::new();
-        if let Some((pipeline_id, dependencies, rfid)) = dependencies {
+        if let PrimitiveKind::Picture = prim_kind {
+            let pic_context_for_children = {
+                let pic = &mut self.cpu_pictures[cpu_prim_index.0];
+
+                if !pic.resolve_scene_properties(frame_context.scene_properties) {
+                    return None;
+                }
+
+                let (draw_text_transformed, original_reference_frame_id) = match pic.kind {
+                    PictureKind::Image { reference_frame_id, .. } => {
+                        may_need_clip_mask = false;
+                        (true, Some(reference_frame_id))
+                    }
+                    PictureKind::BoxShadow { .. } |
+                    PictureKind::TextShadow { .. } => {
+                        (false, None)
+                    }
+                };
+
+                let display_list = &frame_context
+                    .pipelines
+                    .get(&pic.pipeline_id)
+                    .expect("No display list?")
+                    .display_list;
+
+                let inv_world_transform = prim_run_context
+                    .scroll_node
+                    .world_content_transform
+                    .inverse();
+
+                PictureContext {
+                    pipeline_id: pic.pipeline_id,
+                    perform_culling: pic.cull_children,
+                    prim_runs: mem::replace(&mut pic.runs, Vec::new()),
+                    original_reference_frame_id,
+                    display_list,
+                    draw_text_transformed,
+                    inv_world_transform,
+                }
+            };
+
             let result = self.prepare_prim_runs(
-                &dependencies,
-                pipeline_id,
-                prim_run_context,
-                cull_children,
-                &mut child_tasks,
-                rfid,
-                cpu_prim_index,
+                &pic_context_for_children,
+                &mut pic_state_for_children,
                 frame_context,
                 frame_state,
             );
 
-            let metadata = &mut self.cpu_metadata[prim_index.0];
-
             // Restore the dependencies (borrow check dance)
             let pic = &mut self.cpu_pictures[cpu_prim_index.0];
-            pic.runs = dependencies;
+            pic.runs = pic_context_for_children.prim_runs;
 
+            let metadata = &mut self.cpu_metadata[prim_index.0];
             metadata.local_rect = pic.update_local_rect(
                 metadata.local_rect,
                 result,
             );
         }
 
         let (local_rect, unclipped_device_rect) = {
             let metadata = &mut self.cpu_metadata[prim_index.0];
@@ -1786,58 +1800,58 @@ impl PrimitiveStore {
                metadata.local_rect.size.height <= 0.0 {
                 //warn!("invalid primitive rect {:?}", metadata.local_rect);
                 return None;
             }
 
             let local_rect = metadata.local_clip_rect.intersection(&metadata.local_rect);
             let local_rect = match local_rect {
                 Some(local_rect) => local_rect,
-                None if perform_culling => return None,
+                None if pic_context.perform_culling => return None,
                 None => LayerRect::zero(),
             };
 
             let screen_bounding_rect = calculate_screen_bounding_rect(
                 &prim_run_context.scroll_node.world_content_transform,
                 &local_rect,
                 frame_context.device_pixel_scale,
             );
 
             let clip_bounds = match prim_run_context.clip_chain {
                 Some(ref node) => node.combined_outer_screen_rect,
                 None => frame_context.screen_rect,
             };
             metadata.screen_rect = screen_bounding_rect.intersection(&clip_bounds);
 
-            if metadata.screen_rect.is_none() && perform_culling {
+            if metadata.screen_rect.is_none() && pic_context.perform_culling {
                 return None;
             }
 
             metadata.clip_chain_rect_index = prim_run_context.clip_chain_rect_index;
 
             (local_rect, screen_bounding_rect)
         };
 
-        if perform_culling && may_need_clip_mask && !self.update_clip_task(
+        if pic_context.perform_culling && may_need_clip_mask && !self.update_clip_task(
             prim_index,
             prim_run_context,
             &unclipped_device_rect,
-            parent_tasks,
+            pic_state,
             frame_context,
             frame_state,
         ) {
             return None;
         }
 
         self.prepare_prim_for_render_inner(
             prim_index,
             prim_run_context,
-            child_tasks,
-            parent_tasks,
-            pic_index,
+            pic_state_for_children,
+            pic_context,
+            pic_state,
             frame_context,
             frame_state,
         );
 
         Some(local_rect)
     }
 
     // TODO(gw): Make this simpler / more efficient by tidying
@@ -1845,113 +1859,99 @@ impl PrimitiveStore {
     pub fn reset_prim_visibility(&mut self) {
         for md in &mut self.cpu_metadata {
             md.screen_rect = None;
         }
     }
 
     pub fn prepare_prim_runs(
         &mut self,
-        runs: &[PrimitiveRun],
-        pipeline_id: PipelineId,
-        parent_prim_run_context: &PrimitiveRunContext,
-        perform_culling: bool,
-        parent_tasks: &mut Vec<RenderTaskId>,
-        original_reference_frame_id: Option<ClipId>,
-        pic_index: SpecificPrimitiveIndex,
+        pic_context: &PictureContext,
+        pic_state: &mut PictureState,
         frame_context: &FrameContext,
         frame_state: &mut FrameState,
     ) -> PrimitiveRunLocalRect {
         let mut result = PrimitiveRunLocalRect {
             local_rect_in_actual_parent_space: LayerRect::zero(),
             local_rect_in_original_parent_space: LayerRect::zero(),
         };
 
-        for run in runs {
+        for run in &pic_context.prim_runs {
             // TODO(gw): Perhaps we can restructure this to not need to create
             //           a new primitive context for every run (if the hash
             //           lookups ever show up in a profile).
             let scroll_node = &frame_context
                 .clip_scroll_tree
                 .nodes[&run.clip_and_scroll.scroll_node_id];
             let clip_chain = frame_context
                 .clip_scroll_tree
                 .get_clip_chain(&run.clip_and_scroll.clip_node_id());
 
-            if perform_culling {
+            if pic_context.perform_culling {
                 if !scroll_node.invertible {
-                    debug!("{:?} {:?}: position not invertible", run.base_prim_index, pipeline_id);
+                    debug!("{:?} {:?}: position not invertible", run.base_prim_index, pic_context.pipeline_id);
                     continue;
                 }
 
                 match clip_chain {
                      Some(ref chain) if chain.combined_outer_screen_rect.is_empty() => {
-                        debug!("{:?} {:?}: clipped out", run.base_prim_index, pipeline_id);
+                        debug!("{:?} {:?}: clipped out", run.base_prim_index, pic_context.pipeline_id);
                         continue;
                     }
                     _ => {},
                 }
             }
 
 
-            let parent_relative_transform = parent_prim_run_context
-                .scroll_node
-                .world_content_transform
-                .inverse()
+            let parent_relative_transform = pic_context
+                .inv_world_transform
                 .map(|inv_parent| {
                     inv_parent.pre_mul(&scroll_node.world_content_transform)
                 });
 
-            let original_relative_transform = original_reference_frame_id
+            let original_relative_transform = pic_context.original_reference_frame_id
                 .and_then(|original_reference_frame_id| {
                     let parent = frame_context
                         .clip_scroll_tree
                         .nodes[&original_reference_frame_id]
                         .world_content_transform;
                     parent.inverse()
                         .map(|inv_parent| {
                             inv_parent.pre_mul(&scroll_node.world_content_transform)
                         })
                 });
 
-            let display_list = &frame_context.pipelines
-                .get(&pipeline_id)
-                .expect("No display list?")
-                .display_list;
-
-            let clip_chain_rect = match perform_culling {
+            let clip_chain_rect = match pic_context.perform_culling {
                 true => get_local_clip_rect_for_nodes(scroll_node, clip_chain),
                 false => None,
             };
 
             let clip_chain_rect_index = match clip_chain_rect {
                 Some(rect) if rect.is_empty() => continue,
                 Some(rect) => {
                     frame_state.local_clip_rects.push(rect);
                     ClipChainRectIndex(frame_state.local_clip_rects.len() - 1)
                 }
                 None => ClipChainRectIndex(0), // This is no clipping.
             };
 
             let child_prim_run_context = PrimitiveRunContext::new(
-                display_list,
                 clip_chain,
                 scroll_node,
                 clip_chain_rect_index,
             );
 
             for i in 0 .. run.count {
                 let prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
 
                 if let Some(prim_local_rect) = self.prepare_prim_for_render(
                     prim_index,
                     &child_prim_run_context,
-                    perform_culling,
-                    parent_tasks,
-                    pic_index,
+                    pic_context,
+                    pic_state,
                     frame_context,
                     frame_state,
                 ) {
                     frame_state.profile_counters.visible_primitives.inc();
 
                     if let Some(ref matrix) = original_relative_transform {
                         let bounds = matrix.transform_rect(&prim_local_rect);
                         result.local_rect_in_original_parent_space =
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ApiMsg, BuiltDisplayList, DebugCommand, DeviceIntPoint};
+use api::{ApiMsg, BuiltDisplayList, ClearCache, DebugCommand};
 #[cfg(feature = "debugger")]
 use api::{BuiltDisplayListIter, SpecificDisplayItem};
-use api::{DevicePixelScale, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
+use api::{DeviceIntPoint, DevicePixelScale, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
 use api::{DocumentId, DocumentLayer, DocumentMsg, HitTestFlags, HitTestResult};
 use api::{IdNamespace, PipelineId, RenderNotifier, WorldPoint};
 use api::channel::{MsgReceiver, MsgSender, PayloadReceiver, PayloadReceiverHelperMethods};
 use api::channel::{PayloadSender, PayloadSenderHelperMethods};
 #[cfg(feature = "capture")]
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
@@ -558,28 +558,52 @@ impl RenderBackend {
                         .filter(|did| did.0 == namespace_id)
                         .cloned()
                         .collect::<Vec<_>>();
                     for document in document_ids {
                         self.documents.remove(&document);
                     }
                 }
                 ApiMsg::MemoryPressure => {
-                    self.resource_cache.on_memory_pressure();
+                    // This is drastic. It will basically flush everything out of the cache,
+                    // and the next frame will have to rebuild all of its resources.
+                    // We may want to look into something less extreme, but on the other hand this
+                    // should only be used in situations where are running low enough on memory
+                    // that we risk crashing if we don't do something about it.
+                    // The advantage of clearing the cache completely is that it gets rid of any
+                    // remaining fragmentation that could have persisted if we kept around the most
+                    // recently used resources.
+                    self.resource_cache.clear(ClearCache::all());
 
                     let pending_update = self.resource_cache.pending_updates();
                     let msg = ResultMsg::UpdateResources {
                         updates: pending_update,
                         cancel_rendering: true,
                     };
                     self.result_tx.send(msg).unwrap();
                     self.notifier.wake_up();
                 }
                 ApiMsg::DebugCommand(option) => {
                     let msg = match option {
+                        DebugCommand::EnableDualSourceBlending(enable) => {
+                            // Set in the config used for any future documents
+                            // that are created.
+                            self.frame_config
+                                .dual_source_blending_is_enabled = enable;
+
+                            // Set for any existing documents.
+                            for (_, doc) in &mut self.documents {
+                                doc.frame_ctx
+                                   .frame_builder_config
+                                   .dual_source_blending_is_enabled = enable;
+                            }
+
+                            // We don't want to forward this message to the renderer.
+                            continue;
+                        }
                         DebugCommand::FetchDocuments => {
                             let json = self.get_docs_for_debugger();
                             ResultMsg::DebugOutput(DebugOutput::FetchDocuments(json))
                         }
                         DebugCommand::FetchClipScrollTree => {
                             let json = self.get_clip_scroll_tree_for_debugger();
                             ResultMsg::DebugOutput(DebugOutput::FetchClipScrollTree(json))
                         }
@@ -601,32 +625,20 @@ impl RenderBackend {
                                     root_pipeline_id: doc.scene.root_pipeline_id,
                                     window_size: doc.view.window_size,
                                 };
                                 tx.send(captured).unwrap();
                             }
                             // Note: we can't pass `LoadCapture` here since it needs to arrive
                             // before the `PublishDocument` messages sent by `load_capture`.
                             continue
-                        },
-                        DebugCommand::EnableDualSourceBlending(enable) => {
-                            // Set in the config used for any future documents
-                            // that are created.
-                            self.frame_config
-                                .dual_source_blending_is_enabled = enable;
-
-                            // Set for any existing documents.
-                            for (_, doc) in &mut self.documents {
-                                doc.frame_ctx
-                                   .frame_builder_config
-                                   .dual_source_blending_is_enabled = enable;
-                            }
-
-                            // We don't want to forward this message to the renderer.
-                            continue;
+                        }
+                        DebugCommand::ClearCaches(mask) => {
+                            self.resource_cache.clear(mask);
+                            continue
                         }
                         _ => ResultMsg::DebugCommand(option),
                     };
                     self.result_tx.send(msg).unwrap();
                     self.notifier.wake_up();
                 }
                 ApiMsg::ShutDown => {
                     self.notifier.shut_down();
@@ -686,17 +698,17 @@ impl RenderBackend {
                 let _timer = profile_counters.total_time.timer();
 
                 let rendered_document = doc.render(
                     &mut self.resource_cache,
                     &mut self.gpu_cache,
                     &mut profile_counters.resources,
                 );
 
-                info!("generated frame for document {:?} with {} passes",
+                debug!("generated frame for document {:?} with {} passes",
                     document_id, rendered_document.frame.passes.len());
 
                 let msg = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates());
                 self.result_tx.send(msg).unwrap();
 
                 let pending_update = self.resource_cache.pending_updates();
                 (pending_update, rendered_document)
             };
@@ -858,22 +870,22 @@ impl RenderBackend {
     fn save_capture(
         &mut self,
         root: PathBuf,
         bits: CaptureBits,
         profile_counters: &mut BackendProfileCounters,
     ) -> DebugOutput {
         use capture::CaptureConfig;
 
-        info!("capture: saving {:?}", root);
+        debug!("capture: saving {:?}", root);
         let (resources, deferred) = self.resource_cache.save_capture(&root);
         let config = CaptureConfig::new(root, bits);
 
         for (&id, doc) in &mut self.documents {
-            info!("\tdocument {:?}", id);
+            debug!("\tdocument {:?}", id);
             if config.bits.contains(CaptureBits::SCENE) {
                 let file_name = format!("scene-{}-{}", (id.0).0, id.1);
                 config.serialize(&doc.scene, file_name);
             }
             if config.bits.contains(CaptureBits::FRAME) {
                 let rendered_document = doc.render(
                     &mut self.resource_cache,
                     &mut self.gpu_cache,
@@ -927,17 +939,17 @@ impl RenderBackend {
     fn load_capture(
         &mut self,
         root: &PathBuf,
         profile_counters: &mut BackendProfileCounters,
     ) {
         use capture::CaptureConfig;
         use tiling::Frame;
 
-        info!("capture: loading {:?}", root);
+        debug!("capture: loading {:?}", root);
         let backend = CaptureConfig::deserialize::<PlainRenderBackend, _>(root, "backend")
             .expect("Unable to open backend.ron");
         let caches_maybe = CaptureConfig::deserialize::<PlainCacheOwn, _>(root, "resource_cache");
 
         // Note: it would be great to have `RenderBackend` to be split
         // rather explicitly on what's used before and after scene building
         // so that, for example, we never miss anything in the code below:
 
@@ -953,17 +965,17 @@ impl RenderBackend {
         };
 
         self.documents.clear();
         self.default_device_pixel_ratio = backend.default_device_pixel_ratio;
         self.frame_config = backend.frame_config;
         self.enable_render_on_scroll = backend.enable_render_on_scroll;
 
         for (id, view) in backend.documents {
-            info!("\tdocument {:?}", id);
+            debug!("\tdocument {:?}", id);
             let scene_name = format!("scene-{}-{}", (id.0).0, id.1);
             let scene = CaptureConfig::deserialize::<Scene, _>(root, &scene_name)
                 .expect(&format!("Unable to open {}.ron", scene_name));
 
             let mut doc = Document {
                 scene,
                 view,
                 frame_ctx: FrameContext::new(self.frame_config.clone()),
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -726,39 +726,47 @@ impl RenderTask {
         }
 
         pt.end_level();
         true
     }
 }
 
 #[derive(Debug, Hash, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskCacheKeyKind {
     BoxShadow(BoxShadowCacheKey),
     Image(ImageCacheKey),
 }
 
 #[derive(Debug, Hash, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskCacheKey {
     pub size: DeviceIntSize,
     pub kind: RenderTaskCacheKeyKind,
 }
 
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct RenderTaskCacheEntry {
     handle: TextureCacheHandle,
 }
 
 // A cache of render tasks that are stored in the texture
 // cache for usage across frames.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskCache {
     entries: FastHashMap<RenderTaskCacheKey, RenderTaskCacheEntry>,
 }
 
 impl RenderTaskCache {
-    pub fn new() -> RenderTaskCache {
+    pub fn new() -> Self {
         RenderTaskCache {
             entries: FastHashMap::default(),
         }
     }
 
     pub fn clear(&mut self) {
         self.entries.clear();
     }
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -715,17 +715,17 @@ impl SourceTextureResolver {
                 let texture = self.cache_rgba8_texture
                     .as_ref()
                     .unwrap_or(&self.dummy_cache_texture);
                 device.bind_texture(sampler, texture);
             }
             SourceTexture::External(external_image) => {
                 let texture = self.external_images
                     .get(&(external_image.id, external_image.channel_index))
-                    .expect(&format!("BUG: External image should be resolved by now: {:?}", external_image));
+                    .expect(&format!("BUG: External image should be resolved by now"));
                 device.bind_external_texture(sampler, texture);
             }
             SourceTexture::TextureCache(index) => {
                 let texture = &self.cache_texture_map[index.0];
                 device.bind_texture(sampler, texture);
             }
             SourceTexture::RenderTaskCacheRGBA8(pass_index) => {
                 let pool_index = self.pass_rgba8_textures
@@ -1187,17 +1187,17 @@ impl LazilyCompiledShader {
 
         if precache {
             let t0 = precise_time_ns();
             let program = try!{ shader.get(device) };
             let t1 = precise_time_ns();
             device.bind_program(program);
             device.draw_triangles_u16(0, 3);
             let t2 = precise_time_ns();
-            println!("[C: {:.1} ms D: {:.1} ms] Precache {} {:?}",
+            debug!("[C: {:.1} ms D: {:.1} ms] Precache {} {:?}",
                 (t1 - t0) as f64 / 1000000.0,
                 (t2 - t1) as f64 / 1000000.0,
                 name,
                 features
             );
         }
 
         Ok(shader)
@@ -2679,16 +2679,19 @@ impl Renderer {
                 self.set_debug_flag(DebugFlags::ALPHA_PRIM_DBG, enable);
             }
             DebugCommand::EnableGpuTimeQueries(enable) => {
                 self.set_debug_flag(DebugFlags::GPU_TIME_QUERIES, enable);
             }
             DebugCommand::EnableGpuSampleQueries(enable) => {
                 self.set_debug_flag(DebugFlags::GPU_SAMPLE_QUERIES, enable);
             }
+            DebugCommand::EnableDualSourceBlending(_) => {
+                panic!("Should be handled by render backend");
+            }
             DebugCommand::FetchDocuments |
             DebugCommand::FetchClipScrollTree => {}
             DebugCommand::FetchRenderTasks => {
                 let json = self.get_render_tasks_for_debugger();
                 self.debug_server.send(json);
             }
             DebugCommand::FetchPasses => {
                 let json = self.get_passes_for_debugger();
@@ -2697,18 +2700,29 @@ impl Renderer {
             DebugCommand::FetchScreenshot => {
                 let json = self.get_screenshot_for_debugger();
                 self.debug_server.send(json);
             }
             DebugCommand::SaveCapture(..) |
             DebugCommand::LoadCapture(..) => {
                 panic!("Capture commands are not welcome here! Did you build with 'capture' feature?")
             }
-            DebugCommand::EnableDualSourceBlending(_) => {
-                panic!("Should be handled by render backend");
+            DebugCommand::ClearCaches(_) => {}
+            DebugCommand::InvalidateGpuCache => {
+                match self.gpu_cache_texture.bus {
+                    CacheBus::PixelBuffer { ref mut rows, .. } => {
+                        info!("Invalidating GPU caches");
+                        for row in rows {
+                            row.is_dirty = true;
+                        }
+                    }
+                    CacheBus::Scatter { .. } => {
+                        warn!("Unable to invalidate scattered GPU cache");
+                    }
+                }
             }
         }
     }
 
     /// Set a callback for handling external images.
     pub fn set_external_image_handler(&mut self, handler: Box<ExternalImageHandler>) {
         self.external_image_handler = Some(handler);
     }
@@ -4127,31 +4141,32 @@ impl Renderer {
             let props = &deferred_resolve.image_properties;
             let ext_image = props
                 .external_image
                 .expect("BUG: Deferred resolves must be external images!");
             let image = handler.lock(ext_image.id, ext_image.channel_index);
             let texture_target = match ext_image.image_type {
                 ExternalImageType::TextureHandle(target) => target,
                 ExternalImageType::Buffer => {
-                    panic!("{:?} is not a suitable image type in update_deferred_resolves()", ext_image.image_type);
+                    panic!("not a suitable image type in update_deferred_resolves()");
                 }
             };
 
             // In order to produce the handle, the external image handler may call into
             // the GL context and change some states.
             self.device.reset_state();
 
             let texture = match image.source {
                 ExternalImageSource::NativeTexture(texture_id) => {
                     ExternalTexture::new(texture_id, texture_target)
                 }
                 ExternalImageSource::Invalid => {
-                    warn!(
-                        "Invalid ext-image for ext_id:{:?}, channel:{}.",
+                    warn!("Invalid ext-image");
+                    debug!(
+                        "For ext_id:{:?}, channel:{}.",
                         ext_image.id,
                         ext_image.channel_index
                     );
                     // Just use 0 as the gl handle for this failed case.
                     ExternalTexture::new(0, texture_target)
                 }
                 ExternalImageSource::RawData(_) => {
                     panic!("Raw external data is not expected for deferred resolves!");
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_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/. */
 
 use api::{AddFont, BlobImageData, BlobImageResources, ResourceUpdate, ResourceUpdates};
 use api::{BlobImageDescriptor, BlobImageError, BlobImageRenderer, BlobImageRequest};
-use api::{ColorF, DevicePoint, DeviceUintRect, DeviceUintSize};
+use api::{ClearCache, ColorF, DevicePoint, DeviceUintRect, DeviceUintSize};
 use api::{Epoch, FontInstanceKey, FontKey, FontTemplate};
 use api::{ExternalImageData, ExternalImageType};
 use api::{FontInstanceOptions, FontInstancePlatformOptions, FontVariation};
 use api::{GlyphDimensions, GlyphKey, IdNamespace};
 use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering};
 use api::{TileOffset, TileSize};
 use app_units::Au;
 #[cfg(feature = "capture")]
@@ -108,27 +108,22 @@ struct ImageResource {
 #[derive(Clone, Debug)]
 pub struct ImageTiling {
     pub image_size: DeviceUintSize,
     pub tile_size: TileSize,
 }
 
 pub type TiledImageMap = FastHashMap<ImageKey, ImageTiling>;
 
+#[derive(Default)]
 struct ImageTemplates {
     images: FastHashMap<ImageKey, ImageResource>,
 }
 
 impl ImageTemplates {
-    fn new() -> Self {
-        ImageTemplates {
-            images: FastHashMap::default(),
-        }
-    }
-
     fn insert(&mut self, key: ImageKey, resource: ImageResource) {
         self.images.insert(key, resource);
     }
 
     fn remove(&mut self, key: ImageKey) -> Option<ImageResource> {
         self.images.remove(&key)
     }
 
@@ -227,16 +222,17 @@ impl Into<BlobImageRequest> for ImageReq
             tile: self.tile,
         }
     }
 }
 
 type ImageCache = ResourceClassCache<ImageRequest, CachedImageInfo>;
 pub type FontInstanceMap = Arc<RwLock<FastHashMap<FontInstanceKey, FontInstance>>>;
 
+#[derive(Default)]
 struct Resources {
     font_templates: FastHashMap<FontKey, FontTemplate>,
     font_instances: FontInstanceMap,
     image_templates: ImageTemplates,
 }
 
 impl BlobImageResources for Resources {
     fn get_font_data(&self, key: FontKey) -> &FontTemplate {
@@ -281,21 +277,17 @@ impl ResourceCache {
         blob_image_renderer: Option<Box<BlobImageRenderer>>,
     ) -> Result<Self, ResourceCacheError> {
         let glyph_rasterizer = GlyphRasterizer::new(workers)?;
 
         Ok(ResourceCache {
             cached_glyphs: GlyphCache::new(),
             cached_images: ResourceClassCache::new(),
             cached_render_tasks: RenderTaskCache::new(),
-            resources: Resources {
-                font_templates: FastHashMap::default(),
-                font_instances: Arc::new(RwLock::new(FastHashMap::default())),
-                image_templates: ImageTemplates::new(),
-            },
+            resources: Resources::default(),
             cached_glyph_dimensions: FastHashMap::default(),
             texture_cache,
             state: State::Idle,
             current_frame_id: FrameId(0),
             pending_image_requests: FastHashSet::default(),
             glyph_rasterizer,
             blob_image_renderer,
         })
@@ -494,20 +486,17 @@ impl ResourceCache {
         image_key: ImageKey,
         descriptor: ImageDescriptor,
         mut data: ImageData,
         dirty_rect: Option<DeviceUintRect>,
     ) {
         let max_texture_size = self.max_texture_size();
         let image = match self.resources.image_templates.get_mut(image_key) {
             Some(res) => res,
-            None => panic!(
-                "Attempt to update non-existent image (key {:?}).",
-                image_key
-            ),
+            None => panic!("Attempt to update non-existent image"),
         };
 
         let mut tiling = image.tiling;
         if tiling.is_none() && Self::should_tile(max_texture_size, &descriptor, &data) {
             tiling = Some(DEFAULT_TILE_SIZE);
         }
 
         if let ImageData::Blob(ref mut blob) = data {
@@ -536,17 +525,18 @@ impl ResourceCache {
         self.cached_images
             .clear_keys(|request| request.key == image_key);
 
         match value {
             Some(image) => if image.data.is_blob() {
                 self.blob_image_renderer.as_mut().unwrap().delete(image_key);
             },
             None => {
-                println!("Delete the non-exist key:{:?}", image_key);
+                warn!("Delete the non-exist key");
+                debug!("key={:?}", image_key);
             }
         }
     }
 
     pub fn request_image(
         &mut self,
         key: ImageKey,
         rendering: ImageRendering,
@@ -558,20 +548,18 @@ impl ResourceCache {
             key,
             rendering,
             tile,
         };
 
         let template = match self.resources.image_templates.get(key) {
             Some(template) => template,
             None => {
-                warn!(
-                    "ERROR: Trying to render deleted / non-existent key {:?}",
-                    key
-                );
+                warn!("ERROR: Trying to render deleted / non-existent key");
+                debug!("key={:?}", key);
                 return
             }
         };
 
         // Images that don't use the texture cache can early out.
         if !template.data.uses_texture_cache() {
             return;
         }
@@ -937,28 +925,29 @@ impl ResourceCache {
         }
     }
 
     pub fn end_frame(&mut self) {
         debug_assert_eq!(self.state, State::QueryResources);
         self.state = State::Idle;
     }
 
-    pub fn on_memory_pressure(&mut self) {
-        // This is drastic. It will basically flush everything out of the cache,
-        // and the next frame will have to rebuild all of its resources.
-        // We may want to look into something less extreme, but on the other hand this
-        // should only be used in situations where are running low enough on memory
-        // that we risk crashing if we don't do something about it.
-        // The advantage of clearing the cache completely is that it gets rid of any
-        // remaining fragmentation that could have persisted if we kept around the most
-        // recently used resources.
-        self.cached_images.clear();
-        self.cached_glyphs.clear();
-        self.cached_render_tasks.clear();
+    pub fn clear(&mut self, what: ClearCache) {
+        if what.contains(ClearCache::IMAGES) {
+            self.cached_images.clear();
+        }
+        if what.contains(ClearCache::GLYPHS) {
+            self.cached_glyphs.clear();
+        }
+        if what.contains(ClearCache::GLYPH_DIMENSIONS) {
+            self.cached_glyph_dimensions.clear();
+        }
+        if what.contains(ClearCache::RENDER_TASKS) {
+            self.cached_render_tasks.clear();
+        }
     }
 
     pub fn clear_namespace(&mut self, namespace: IdNamespace) {
         self.resources
             .image_templates
             .images
             .retain(|key, _| key.0 != namespace);
 
@@ -1030,26 +1019,28 @@ pub struct PlainResources {
 
 #[cfg(feature = "capture")]
 #[derive(Serialize)]
 pub struct PlainCacheRef<'a> {
     current_frame_id: FrameId,
     glyphs: PlainGlyphCacheRef<'a>,
     glyph_dimensions: &'a GlyphDimensionsCache,
     images: &'a ImageCache,
+    render_tasks: &'a RenderTaskCache,
     textures: &'a TextureCache,
 }
 
 #[cfg(feature = "replay")]
 #[derive(Deserialize)]
 pub struct PlainCacheOwn {
     current_frame_id: FrameId,
     glyphs: PlainGlyphCacheOwn,
     glyph_dimensions: GlyphDimensionsCache,
     images: ImageCache,
+    render_tasks: RenderTaskCache,
     textures: TextureCache,
 }
 
 #[cfg(feature = "replay")]
 const NATIVE_FONT: &'static [u8] = include_bytes!("../res/Proggy.ttf");
 
 impl ResourceCache {
     #[cfg(feature = "capture")]
@@ -1281,16 +1272,17 @@ impl ResourceCache {
                             })
                         })
                         .collect();
                     (font_instance, ResourceClassCache { resources })
                 })
                 .collect(),
             glyph_dimensions: &self.cached_glyph_dimensions,
             images: &self.cached_images,
+            render_tasks: &self.cached_render_tasks,
             textures: &self.texture_cache,
         }
     }
 
     #[cfg(feature = "replay")]
     pub fn load_capture(
         &mut self,
         resources: PlainResources,
@@ -1345,32 +1337,32 @@ impl ResourceCache {
                             })
                             .collect();
                         (font_instance, ResourceClassCache { resources })
                     })
                     .collect();
                 self.current_frame_id = cached.current_frame_id;
                 self.cached_glyphs = GlyphCache { glyph_key_caches };
                 self.cached_glyph_dimensions = cached.glyph_dimensions;
+                self.cached_images = cached.images;
+                self.cached_render_tasks = cached.render_tasks;
                 self.texture_cache = cached.textures;
             }
             None => {
                 self.current_frame_id = FrameId(0);
                 self.cached_glyphs.clear();
                 self.cached_glyph_dimensions.clear();
                 self.cached_images.clear();
+                self.cached_render_tasks.clear();
                 let max_texture_size = self.texture_cache.max_texture_size();
                 self.texture_cache = TextureCache::new(max_texture_size);
             }
         }
 
-        self.state = State::Idle;
         self.glyph_rasterizer.reset();
-        self.pending_image_requests.clear();
-
         let res = &mut self.resources;
         res.font_templates.clear();
         *res.font_instances.write().unwrap() = resources.font_instances;
         res.image_templates.images.clear();
 
         info!("\tfont templates...");
         let native_font_replacement = Arc::new(NATIVE_FONT.to_vec());
         for (key, plain_template) in resources.font_templates {
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -48,17 +48,18 @@ impl SceneProperties {
     ) -> LayoutTransform {
         match *property {
             PropertyBinding::Value(value) => value,
             PropertyBinding::Binding(ref key) => {
                 self.transform_properties
                     .get(&key.id)
                     .cloned()
                     .unwrap_or_else(|| {
-                        warn!("Property binding {:?} has an invalid value.", key);
+                        warn!("Property binding has an invalid value.");
+                        debug!("key={:?}", key);
                         LayoutTransform::identity()
                     })
             }
         }
     }
 
     /// Get the current value for a float property.
     pub fn resolve_float(
@@ -68,17 +69,18 @@ impl SceneProperties {
     ) -> f32 {
         match *property {
             PropertyBinding::Value(value) => value,
             PropertyBinding::Binding(ref key) => {
                 self.float_properties
                     .get(&key.id)
                     .cloned()
                     .unwrap_or_else(|| {
-                        warn!("Property binding {:?} has an invalid value.", key);
+                        warn!("Property binding has an invalid value.");
+                        debug!("key={:?}", key);
                         default_value
                     })
             }
         }
     }
 }
 
 /// A representation of the layout within the display port for a given document or iframe.
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -194,17 +194,17 @@ impl<T: RenderTarget> RenderTargetList<T
             .last_mut()
             .and_then(|target| target.allocate(alloc_size));
 
         let origin = match existing_origin {
             Some(origin) => origin,
             None => {
                 let mut new_target = T::new(Some(self.max_size), self.screen_size);
                 let origin = new_target.allocate(alloc_size).expect(&format!(
-                    "Each render task must allocate <= size of one target! ({:?})",
+                    "Each render task must allocate <= size of one target! ({})",
                     alloc_size
                 ));
                 self.targets.push(new_target);
                 origin
             }
         };
 
         (origin, RenderTargetIndex(self.targets.len() - 1))
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.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 {BuiltDisplayList, BuiltDisplayListDescriptor, ClipId, ColorF, DeviceIntPoint, DeviceUintRect};
-use {DeviceUintSize, FontInstanceKey, FontInstanceOptions};
+use {DeviceUintSize, ExternalScrollId, FontInstanceKey, FontInstanceOptions};
 use {FontInstancePlatformOptions, FontKey, FontVariation, GlyphDimensions, GlyphKey, ImageData};
 use {ImageDescriptor, ImageKey, ItemTag, LayoutPoint, LayoutSize, LayoutTransform, LayoutVector2D};
 use {NativeFontHandle, WorldPoint};
 use app_units::Au;
 use channel::{self, MsgSender, Payload, PayloadSender, PayloadSenderHelperMethods};
 use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
@@ -249,17 +249,17 @@ impl Transaction {
         phase: ScrollEventPhase,
     ) {
         self.ops.push(DocumentMsg::Scroll(scroll_location, cursor, phase));
     }
 
     pub fn scroll_node_with_id(
         &mut self,
         origin: LayoutPoint,
-        id: ClipId,
+        id: IdType,
         clamp: ScrollClamping,
     ) {
         self.ops.push(DocumentMsg::ScrollNodeWithId(origin, id, clamp));
     }
 
     pub fn set_page_zoom(&mut self, page_zoom: ZoomFactor) {
         self.ops.push(DocumentMsg::SetPageZoom(page_zoom));
     }
@@ -379,23 +379,29 @@ pub enum DocumentMsg {
     RemovePipeline(PipelineId),
     EnableFrameOutput(PipelineId, bool),
     SetWindowParameters {
         window_size: DeviceUintSize,
         inner_rect: DeviceUintRect,
         device_pixel_ratio: f32,
     },
     Scroll(ScrollLocation, WorldPoint, ScrollEventPhase),
-    ScrollNodeWithId(LayoutPoint, ClipId, ScrollClamping),
+    ScrollNodeWithId(LayoutPoint, IdType, ScrollClamping),
     TickScrollingBounce,
-    GetScrollNodeState(MsgSender<Vec<ScrollLayerState>>),
+    GetScrollNodeState(MsgSender<Vec<ScrollNodeState>>),
     GenerateFrame,
     UpdateDynamicProperties(DynamicProperties),
 }
 
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub enum IdType {
+    ExternalScrollId(ExternalScrollId),
+    ClipId(ClipId),
+}
+
 impl fmt::Debug for DocumentMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             DocumentMsg::SetDisplayList { .. } => "DocumentMsg::SetDisplayList",
             DocumentMsg::HitTest(..) => "DocumentMsg::HitTest",
             DocumentMsg::SetPageZoom(..) => "DocumentMsg::SetPageZoom",
             DocumentMsg::SetPinchZoom(..) => "DocumentMsg::SetPinchZoom",
             DocumentMsg::SetPan(..) => "DocumentMsg::SetPan",
@@ -420,16 +426,27 @@ bitflags!{
     // Note: capturing `FRAME` without `SCENE` is not currently supported.
     #[derive(Deserialize, Serialize)]
     pub struct CaptureBits: u8 {
         const SCENE = 0x1;
         const FRAME = 0x2;
     }
 }
 
+bitflags!{
+    /// Mask for clearing caches in debug commands.
+    #[derive(Deserialize, Serialize)]
+    pub struct ClearCache: u8 {
+        const IMAGES = 0x1;
+        const GLYPHS = 0x2;
+        const GLYPH_DIMENSIONS = 0x4;
+        const RENDER_TASKS = 0x8;
+    }
+}
+
 /// Information about a loaded capture of each document
 /// that is returned by `RenderBackend`.
 #[derive(Clone, Debug, Deserialize, Serialize)]
 pub struct CapturedDocument {
     pub document_id: DocumentId,
     pub root_pipeline_id: Option<PipelineId>,
     pub window_size: DeviceUintSize,
 }
@@ -443,32 +460,36 @@ pub enum DebugCommand {
     /// Display intermediate render targets on screen.
     EnableRenderTargetDebug(bool),
     /// Display alpha primitive rects.
     EnableAlphaRectsDebug(bool),
     /// Display GPU timing results.
     EnableGpuTimeQueries(bool),
     /// Display GPU overdraw results
     EnableGpuSampleQueries(bool),
+    /// Configure if dual-source blending is used, if available.
+    EnableDualSourceBlending(bool),
     /// Fetch current documents and display lists.
     FetchDocuments,
     /// Fetch current passes and batches.
     FetchPasses,
     /// Fetch clip-scroll tree.
     FetchClipScrollTree,
     /// Fetch render tasks.
     FetchRenderTasks,
     /// Fetch screenshot.
     FetchScreenshot,
     /// Save a capture of all the documents state.
     SaveCapture(PathBuf, CaptureBits),
     /// Load a capture of all the documents state.
     LoadCapture(PathBuf, MsgSender<CapturedDocument>),
-    /// Configure if dual-source blending is used, if available.
-    EnableDualSourceBlending(bool),
+    /// Clear cached resources, forcing them to be re-uploaded from templates.
+    ClearCaches(ClearCache),
+    /// Invalidate GPU cache, forcing the update from the CPU mirror.
+    InvalidateGpuCache,
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ApiMsg {
     /// Add/remove/update images and fonts.
     UpdateResources(ResourceUpdates),
     /// Gets the glyph dimensions
     GetGlyphDimensions(
@@ -793,17 +814,17 @@ impl RenderApi {
             DocumentMsg::SetWindowParameters {
                 window_size,
                 inner_rect,
                 device_pixel_ratio,
             },
         );
     }
 
-    pub fn get_scroll_node_state(&self, document_id: DocumentId) -> Vec<ScrollLayerState> {
+    pub fn get_scroll_node_state(&self, document_id: DocumentId) -> Vec<ScrollNodeState> {
         let (tx, rx) = channel::msg_channel().unwrap();
         self.send(document_id, DocumentMsg::GetScrollNodeState(tx));
         rx.recv().unwrap()
     }
 
     /// Save a capture of the current frame state for debugging.
     pub fn save_capture(&self, path: PathBuf, bits: CaptureBits) {
         let msg = ApiMsg::DebugCommand(DebugCommand::SaveCapture(path, bits));
@@ -843,18 +864,18 @@ pub enum ScrollEventPhase {
     /// The user performed a scroll. The Boolean flag indicates whether the user's fingers are
     /// down, if a touchpad is in use. (If false, the event is a touchpad fling.)
     Move(bool),
     /// The user ended scrolling.
     End,
 }
 
 #[derive(Clone, Deserialize, Serialize)]
-pub struct ScrollLayerState {
-    pub id: ClipId,
+pub struct ScrollNodeState {
+    pub id: ExternalScrollId,
     pub scroll_offset: LayoutVector2D,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
 pub enum ScrollLocation {
     /// Scroll by a certain amount.
     Delta(LayoutVector2D),
     /// Scroll to very top of element.
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -209,16 +209,17 @@ pub struct StickyFrameDisplayItem {
 pub enum ScrollSensitivity {
     ScriptAndInputEvents,
     Script,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ScrollFrameDisplayItem {
     pub id: ClipId,
+    pub external_id: Option<ExternalScrollId>,
     pub image_mask: Option<ImageMask>,
     pub scroll_sensitivity: ScrollSensitivity,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct RectangleDisplayItem {
     pub color: ColorF,
 }
@@ -746,54 +747,55 @@ impl ComplexClipRegion {
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ClipChainId(pub u64, pub PipelineId);
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum ClipId {
     Clip(u64, PipelineId),
     ClipChain(ClipChainId),
-    ClipExternalId(u64, PipelineId),
     DynamicallyAddedNode(u64, PipelineId),
 }
 
 impl ClipId {
     pub fn root_scroll_node(pipeline_id: PipelineId) -> ClipId {
         ClipId::Clip(0, pipeline_id)
     }
 
     pub fn root_reference_frame(pipeline_id: PipelineId) -> ClipId {
         ClipId::DynamicallyAddedNode(0, pipeline_id)
     }
 
-    pub fn new(id: u64, pipeline_id: PipelineId) -> ClipId {
-        // We do this because it is very easy to create accidentally create something that
-        // seems like a root scroll node, but isn't one.
-        if id == 0 {
-            return ClipId::root_scroll_node(pipeline_id);
-        }
-
-        ClipId::ClipExternalId(id, pipeline_id)
-    }
-
     pub fn pipeline_id(&self) -> PipelineId {
         match *self {
             ClipId::Clip(_, pipeline_id) |
             ClipId::ClipChain(ClipChainId(_, pipeline_id)) |
-            ClipId::ClipExternalId(_, pipeline_id) |
             ClipId::DynamicallyAddedNode(_, pipeline_id) => pipeline_id,
         }
     }
 
-    pub fn external_id(&self) -> Option<u64> {
-        match *self {
-            ClipId::ClipExternalId(id, _) => Some(id),
-            _ => None,
-        }
-    }
-
     pub fn is_root_scroll_node(&self) -> bool {
         match *self {
             ClipId::Clip(0, _) => true,
             _ => false,
         }
     }
 }
+
+/// An external identifier that uniquely identifies a scroll frame independent of its ClipId, which
+/// may change from frame to frame. This should be unique within a pipeline. WebRender makes no
+/// attempt to ensure uniqueness. The zero value is reserved for use by the root scroll node of
+/// every pipeline, which always has an external id.
+///
+/// When setting display lists with the `preserve_frame_state` this id is used to preserve scroll
+/// offsets between different sets of ClipScrollNodes which are ScrollFrames.
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub struct ExternalScrollId(pub u64, pub PipelineId);
+
+impl ExternalScrollId {
+    pub fn pipeline_id(&self) -> PipelineId {
+        self.1
+    }
+
+    pub fn is_root(&self) -> bool {
+        self.0 == 0
+    }
+}
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -1,18 +1,18 @@
 /* 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 {AlphaType, BorderDetails, BorderDisplayItem, BorderRadius, BorderWidths, BoxShadowClipMode};
 use {BoxShadowDisplayItem, ClipAndScrollInfo, ClipChainId, ClipChainItem, ClipDisplayItem, ClipId};
-use {ColorF, ComplexClipRegion, DisplayItem, ExtendMode, FilterOp, FontInstanceKey, GlyphInstance};
-use {GlyphOptions, Gradient, GradientDisplayItem, GradientStop, IframeDisplayItem};
-use {ImageDisplayItem, ImageKey, ImageMask, ImageRendering, LayerPrimitiveInfo, LayoutPoint};
-use {LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
+use {ColorF, ComplexClipRegion, DisplayItem, ExtendMode, ExternalScrollId, FilterOp};
+use {FontInstanceKey, GlyphInstance, GlyphOptions, Gradient, GradientDisplayItem, GradientStop};
+use {IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask, ImageRendering, LayerPrimitiveInfo};
+use {LayoutPoint, LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
 use {LineDisplayItem, LineOrientation, LineStyle, LocalClip, MixBlendMode, PipelineId};
 use {PropertyBinding, PushStackingContextDisplayItem, RadialGradient, RadialGradientDisplayItem};
 use {RectangleDisplayItem, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity, Shadow};
 use {SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, StickyOffsetBounds};
 use {TextDisplayItem, TransformStyle, YuvColorSpace, YuvData, YuvImageDisplayItem};
 use bincode;
 use euclid::SideOffsets2D;
 use serde::{Deserialize, Serialize};
@@ -187,17 +187,18 @@ impl<'a> BuiltDisplayListIter<'a> {
 
     pub fn new_with_list_and_data(list: &'a BuiltDisplayList, data: &'a [u8]) -> Self {
         BuiltDisplayListIter {
             list,
             data: &data,
             cur_item: DisplayItem {
                 // Dummy data, will be overwritten by `next`
                 item: SpecificDisplayItem::PopStackingContext,
-                clip_and_scroll: ClipAndScrollInfo::simple(ClipId::new(0, PipelineId::dummy())),
+                clip_and_scroll:
+                    ClipAndScrollInfo::simple(ClipId::root_scroll_node(PipelineId::dummy())),
                 info: LayoutPrimitiveInfo::new(LayoutRect::zero()),
             },
             cur_stops: ItemRange::default(),
             cur_glyphs: ItemRange::default(),
             cur_filters: ItemRange::default(),
             cur_clip_chain_items: ItemRange::default(),
             cur_complex_clip: (ItemRange::default(), 0),
             peeking: Peek::NotPeeking,
@@ -1332,69 +1333,68 @@ impl DisplayListBuilder {
     pub fn push_stops(&mut self, stops: &[GradientStop]) {
         if stops.is_empty() {
             return;
         }
         self.push_new_empty_item(SpecificDisplayItem::SetGradientStops);
         self.push_iter(stops);
     }
 
-    fn generate_clip_id(&mut self, id: Option<ClipId>) -> ClipId {
-        id.unwrap_or_else(|| {
-            self.next_clip_id += 1;
-            ClipId::Clip(self.next_clip_id - 1, self.pipeline_id)
-        })
+    fn generate_clip_id(&mut self) -> ClipId {
+        self.next_clip_id += 1;
+        ClipId::Clip(self.next_clip_id - 1, self.pipeline_id)
     }
 
     fn generate_clip_chain_id(&mut self) -> ClipChainId {
         self.next_clip_chain_id += 1;
         ClipChainId(self.next_clip_chain_id - 1, self.pipeline_id)
     }
 
     pub fn define_scroll_frame<I>(
         &mut self,
-        id: Option<ClipId>,
+        external_id: Option<ExternalScrollId>,
         content_rect: LayoutRect,
         clip_rect: LayoutRect,
         complex_clips: I,
         image_mask: Option<ImageMask>,
         scroll_sensitivity: ScrollSensitivity,
     ) -> ClipId
     where
         I: IntoIterator<Item = ComplexClipRegion>,
         I::IntoIter: ExactSizeIterator + Clone,
     {
         let parent = self.clip_stack.last().unwrap().scroll_node_id;
         self.define_scroll_frame_with_parent(
-            id,
             parent,
+            external_id,
             content_rect,
             clip_rect,
             complex_clips,
             image_mask,
             scroll_sensitivity)
     }
 
     pub fn define_scroll_frame_with_parent<I>(
         &mut self,
-        id: Option<ClipId>,
         parent: ClipId,
+        external_id: Option<ExternalScrollId>,
         content_rect: LayoutRect,
         clip_rect: LayoutRect,
         complex_clips: I,
         image_mask: Option<ImageMask>,
         scroll_sensitivity: ScrollSensitivity,
     ) -> ClipId
     where
         I: IntoIterator<Item = ComplexClipRegion>,
         I::IntoIter: ExactSizeIterator + Clone,
     {
-        let id = self.generate_clip_id(id);
+        let id = self.generate_clip_id();
         let item = SpecificDisplayItem::ScrollFrame(ScrollFrameDisplayItem {
             id,
+            external_id,
             image_mask,
             scroll_sensitivity,
         });
         let info = LayoutPrimitiveInfo::with_clip_rect(content_rect, clip_rect);
 
         let scrollinfo = ClipAndScrollInfo::simple(parent);
         self.push_item_with_clip_scroll_info(item, &info, scrollinfo);
         self.push_iter(complex_clips);
@@ -1413,71 +1413,68 @@ impl DisplayListBuilder {
         let id = self.generate_clip_chain_id();
         self.push_new_empty_item(SpecificDisplayItem::ClipChain(ClipChainItem { id, parent}));
         self.push_iter(clips);
         id
     }
 
     pub fn define_clip<I>(
         &mut self,
-        id: Option<ClipId>,
         clip_rect: LayoutRect,
         complex_clips: I,
         image_mask: Option<ImageMask>,
     ) -> ClipId
     where
         I: IntoIterator<Item = ComplexClipRegion>,
         I::IntoIter: ExactSizeIterator + Clone,
     {
         let parent = self.clip_stack.last().unwrap().scroll_node_id;
         self.define_clip_with_parent(
-            id,
             parent,
             clip_rect,
             complex_clips,
-            image_mask)
+            image_mask
+        )
     }
 
     pub fn define_clip_with_parent<I>(
         &mut self,
-        id: Option<ClipId>,
         parent: ClipId,
         clip_rect: LayoutRect,
         complex_clips: I,
         image_mask: Option<ImageMask>,
     ) -> ClipId
     where
         I: IntoIterator<Item = ComplexClipRegion>,
         I::IntoIter: ExactSizeIterator + Clone,
     {
-        let id = self.generate_clip_id(id);
+        let id = self.generate_clip_id();
         let item = SpecificDisplayItem::Clip(ClipDisplayItem {
             id,
             image_mask: image_mask,
         });
 
         let info = LayoutPrimitiveInfo::new(clip_rect);
 
         let scrollinfo = ClipAndScrollInfo::simple(parent);
         self.push_item_with_clip_scroll_info(item, &info, scrollinfo);
         self.push_iter(complex_clips);
         id
     }
 
     pub fn define_sticky_frame(
         &mut self,
-        id: Option<ClipId>,
         frame_rect: LayoutRect,
         margins: SideOffsets2D<Option<f32>>,
         vertical_offset_bounds: StickyOffsetBounds,
         horizontal_offset_bounds: StickyOffsetBounds,
         previously_applied_offset: LayoutVector2D,
 
     ) -> ClipId {
-        let id = self.generate_clip_id(id);
+        let id = self.generate_clip_id();
         let item = SpecificDisplayItem::StickyFrame(StickyFrameDisplayItem {
             id,
             margins,
             vertical_offset_bounds,
             horizontal_offset_bounds,
             previously_applied_offset,
         });
 
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-b6e69a8efbcd8dc3e0c0a8a9925e6a9355635de3
+e772c3cb8ea0a35e6477e9dc8dd2144e2de87b56
--- a/gfx/wrench/src/rawtest.rs
+++ b/gfx/wrench/src/rawtest.rs
@@ -426,26 +426,32 @@ impl<'a> RawtestHarness<'a> {
             DeviceUintPoint::new(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
 
         let mut do_test = |should_try_and_fail| {
             let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
-            let clip = builder.define_clip(None, rect(110., 120., 200., 200.),
-                                           None::<ComplexClipRegion>, None);
+            let clip = builder.define_clip(
+                rect(110., 120., 200., 200.),
+                None::<ComplexClipRegion>,
+                None
+            );
             builder.push_clip_id(clip);
             builder.push_rect(&PrimitiveInfo::new(rect(100., 100., 100., 100.)),
                               ColorF::new(0.0, 0.0, 1.0, 1.0));
 
             if should_try_and_fail {
                 builder.save();
-                let clip = builder.define_clip(None, rect(80., 80., 90., 90.),
-                                               None::<ComplexClipRegion>, None);
+                let clip = builder.define_clip(
+                    rect(80., 80., 90., 90.),
+                    None::<ComplexClipRegion>,
+                    None
+                );
                 builder.push_clip_id(clip);
                 builder.push_rect(&PrimitiveInfo::new(rect(110., 110., 50., 50.)),
                               ColorF::new(0.0, 1.0, 0.0, 1.0));
                 builder.push_shadow(&PrimitiveInfo::new(rect(100., 100., 100., 100.)),
                     Shadow {
                         offset: LayoutVector2D::new(1.0, 1.0),
                         blur_radius: 1.0,
                         color: ColorF::new(0.0, 0.0, 0.0, 1.0),
@@ -453,18 +459,21 @@ impl<'a> RawtestHarness<'a> {
                 builder.push_line(&PrimitiveInfo::new(rect(110., 110., 50., 2.)),
                                   0.0, LineOrientation::Horizontal,
                                   &ColorF::new(0.0, 0.0, 0.0, 1.0), LineStyle::Solid);
                 builder.restore();
             }
 
             {
                 builder.save();
-                let clip = builder.define_clip(None, rect(80., 80., 100., 100.),
-                                               None::<ComplexClipRegion>, None);
+                let clip = builder.define_clip(
+                    rect(80., 80., 100., 100.),
+                    None::<ComplexClipRegion>,
+                    None
+                );
                 builder.push_clip_id(clip);
                 builder.push_rect(&PrimitiveInfo::new(rect(150., 150., 100., 100.)),
                                   ColorF::new(0.0, 0.0, 1.0, 1.0));
 
                 builder.pop_clip_id();
                 builder.clear_save();
             }
 
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -519,17 +519,17 @@ impl Wrench {
         // operations into separate transactions for mysterious -but probably related
         // to the other comment below- reasons.
         self.api.send_transaction(self.document_id, txn);
 
         let mut txn = Transaction::new();
         for (id, offset) in scroll_offsets {
             txn.scroll_node_with_id(
                 *offset,
-                *id,
+                IdType::ClipId(*id),
                 ScrollClamping::NoClamping,
             );
         }
         // TODO(nical) - Wrench does not notify frames when there was scrolling
         // in the transaction (See RenderNotifier implementations). If we don't
         // generate a frame after scrolling, wrench just stops and some tests
         // will time out.
         // I suppose this was to avoid taking the snapshot after scrolling if
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -1268,21 +1268,23 @@ impl YamlFrameReader {
     ) {
         let clip_rect = yaml["bounds"]
             .as_rect()
             .expect("scroll frame must have a bounds");
         let content_size = yaml["content-size"].as_size().unwrap_or(clip_rect.size);
         let content_rect = LayerRect::new(clip_rect.origin, content_size);
 
         let numeric_id = yaml["id"].as_i64().map(|id| id as u64);
+
         let complex_clips = self.to_complex_clip_regions(&yaml["complex"]);
         let image_mask = self.to_image_mask(&yaml["image-mask"], wrench);
 
+        let external_id = numeric_id.map(|id| ExternalScrollId(id as u64, dl.pipeline_id));
         let real_id = dl.define_scroll_frame(
-            None,
+            external_id,
             content_rect,
             clip_rect,
             complex_clips,
             image_mask,
             ScrollSensitivity::Script,
         );
         if let Some(numeric_id) = numeric_id {
             self.clip_id_map.insert(numeric_id, real_id);
@@ -1304,17 +1306,16 @@ impl YamlFrameReader {
         dl: &mut DisplayListBuilder,
         wrench: &mut Wrench,
         yaml: &Yaml,
     ) {
         let bounds = yaml["bounds"].as_rect().expect("sticky frame must have a bounds");
         let numeric_id = yaml["id"].as_i64().map(|id| id as u64);
 
         let real_id = dl.define_sticky_frame(
-            None,
             bounds,
             SideOffsets2D::new(
                 yaml["margin-top"].as_f32(),
                 yaml["margin-right"].as_f32(),
                 yaml["margin-bottom"].as_f32(),
                 yaml["margin-left"].as_f32(),
             ),
             self.to_sticky_offset_bounds(&yaml["vertical-offset-bounds"]),
@@ -1384,17 +1385,17 @@ impl YamlFrameReader {
     }
 
     pub fn handle_clip(&mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, yaml: &Yaml) {
         let clip_rect = yaml["bounds"].as_rect().expect("clip must have a bounds");
         let numeric_id = yaml["id"].as_i64();
         let complex_clips = self.to_complex_clip_regions(&yaml["complex"]);
         let image_mask = self.to_image_mask(&yaml["image-mask"], wrench);
 
-        let real_id = dl.define_clip(None, clip_rect, complex_clips, image_mask);
+        let real_id = dl.define_clip(clip_rect, complex_clips, image_mask);
         if let Some(numeric_id) = numeric_id {
             self.clip_id_map.insert(numeric_id as u64, real_id);
         }
 
         if !yaml["items"].is_badvalue() {
             dl.push_clip_id(real_id);
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
             dl.pop_clip_id();