Bug 1452603 - Update webrender to commit 6f997974cec5772b1797725f4a7942d742e7d7ff. r=jrmuizel
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 12 Apr 2018 11:04:46 -0400
changeset 781476 50c382ab757af549ace2f6dc1d839e3d15112d60
parent 781475 a67e5015e4564bdb78d84765f9e4ff8580bda243
child 781477 dd843b50cd362c62e56036daec5075859794fa98
push id106312
push userhaftandilian@mozilla.com
push dateThu, 12 Apr 2018 23:43:57 +0000
reviewersjrmuizel
bugs1452603
milestone61.0a1
Bug 1452603 - Update webrender to commit 6f997974cec5772b1797725f4a7942d742e7d7ff. r=jrmuizel MozReview-Commit-ID: 1Gq3I7Z2Dd2
gfx/webrender/Cargo.toml
gfx/webrender/examples/common/boilerplate.rs
gfx/webrender/src/batch.rs
gfx/webrender/src/border.rs
gfx/webrender/src/debug_server.rs
gfx/webrender/src/device.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/freelist.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/platform/macos/font.rs
gfx/webrender/src/platform/unix/font.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/shade.rs
gfx/webrender_api/Cargo.toml
gfx/webrender_api/src/api.rs
gfx/webrender_bindings/Cargo.toml
gfx/webrender_bindings/revision.txt
gfx/wrench/Cargo.toml
gfx/wrench/src/wrench.rs
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -17,17 +17,17 @@ debug_renderer = []
 pathfinder = ["pathfinder_font_renderer", "pathfinder_gfx_utils", "pathfinder_partitioner", "pathfinder_path_utils"]
 
 [dependencies]
 app_units = "0.6"
 byteorder = "1.0"
 bincode = "1.0"
 euclid = "0.17"
 fxhash = "0.2.1"
-gleam = "0.4.20"
+gleam = "0.4.32"
 lazy_static = "1"
 log = "0.4"
 num-traits = "0.1.43"
 time = "0.1"
 rayon = "1"
 webrender_api = {path = "../webrender_api"}
 bitflags = "1.0"
 thread_profiler = "0.1.1"
--- a/gfx/webrender/examples/common/boilerplate.rs
+++ b/gfx/webrender/examples/common/boilerplate.rs
@@ -137,16 +137,17 @@ pub fn main_wrapper<E: Example>(
 
     println!("Loading shaders...");
     let opts = webrender::RendererOptions {
         resource_override_path: res_path,
         precache_shaders: E::PRECACHE_SHADERS,
         device_pixel_ratio,
         clear_color: Some(ColorF::new(0.3, 0.0, 0.0, 1.0)),
         //scatter_gpu_cache_updates: false,
+        debug_flags: webrender::DebugFlags::ECHO_DRIVER_MESSAGES,
         ..options.unwrap_or(webrender::RendererOptions::default())
     };
 
     let framebuffer_size = {
         let (width, height) = window.get_inner_size().unwrap();
         DeviceUintSize::new(width, height)
     };
     let notifier = Box::new(Notifier::new(events_loop.create_proxy()));
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -970,33 +970,27 @@ impl AlphaBatchBuilder {
                         }
                     }
                 }
             }
             PrimitiveKind::Border => {
                 let border_cpu =
                     &ctx.prim_store.cpu_borders[prim_metadata.cpu_prim_index.0];
                 // TODO(gw): Select correct blend mode for edges and corners!!
-                let corner_kind = BatchKind::Transformable(
-                    transform_kind,
-                    TransformBatchKind::BorderCorner,
-                );
-                let corner_key = BatchKey::new(corner_kind, non_segmented_blend_mode, no_textures);
-                let edge_kind = BatchKind::Transformable(
-                    transform_kind,
-                    TransformBatchKind::BorderEdge,
-                );
-                let edge_key = BatchKey::new(edge_kind, non_segmented_blend_mode, no_textures);
 
-                // Work around borrow ck on borrowing batch_list twice.
-                {
-                    let batch =
-                        self.batch_list.get_suitable_batch(corner_key, &task_relative_bounding_rect);
-                    for (i, instance_kind) in border_cpu.corner_instances.iter().enumerate()
-                    {
+                if border_cpu.corner_instances.iter().any(|&kind| kind != BorderCornerInstance::None) {
+                    let corner_kind = BatchKind::Transformable(
+                        transform_kind,
+                        TransformBatchKind::BorderCorner,
+                    );
+                    let corner_key = BatchKey::new(corner_kind, non_segmented_blend_mode, no_textures);
+                    let batch = self.batch_list
+                        .get_suitable_batch(corner_key, &task_relative_bounding_rect);
+
+                    for (i, instance_kind) in border_cpu.corner_instances.iter().enumerate() {
                         let sub_index = i as i32;
                         match *instance_kind {
                             BorderCornerInstance::None => {}
                             BorderCornerInstance::Single => {
                                 batch.push(base_instance.build(
                                     sub_index,
                                     BorderCornerSide::Both as i32,
                                     0,
@@ -1013,22 +1007,32 @@ impl AlphaBatchBuilder {
                                     BorderCornerSide::Second as i32,
                                     0,
                                 ));
                             }
                         }
                     }
                 }
 
-                let batch = self.batch_list.get_suitable_batch(edge_key, &task_relative_bounding_rect);
-                for (border_segment, instance_kind) in border_cpu.edges.iter().enumerate() {
-                    match *instance_kind {
-                        BorderEdgeKind::None => {},
-                        _ => {
-                          batch.push(base_instance.build(border_segment as i32, 0, 0));
+                if border_cpu.edges.iter().any(|&kind| kind != BorderEdgeKind::None) {
+                    let edge_kind = BatchKind::Transformable(
+                        transform_kind,
+                        TransformBatchKind::BorderEdge,
+                    );
+                    let edge_key = BatchKey::new(edge_kind, non_segmented_blend_mode, no_textures);
+                    let batch = self.batch_list
+                        .get_suitable_batch(edge_key, &task_relative_bounding_rect);
+
+                    for (border_segment, instance_kind) in border_cpu.edges.iter().enumerate() {
+                        match *instance_kind {
+                            BorderEdgeKind::None => {},
+                            BorderEdgeKind::Solid |
+                            BorderEdgeKind::Clip => {
+                                batch.push(base_instance.build(border_segment as i32, 0, 0));
+                            }
                         }
                     }
                 }
             }
             PrimitiveKind::Image => {
                 let image_cpu = &ctx.prim_store.cpu_images[prim_metadata.cpu_prim_index.0];
 
                 let cache_item = match image_cpu.source {
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -98,144 +98,126 @@ impl BorderCornerKind {
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum BorderEdgeKind {
     None,
     Solid,
     Clip,
 }
 
-trait NormalBorderHelpers {
-    fn get_corner(
-        &self,
-        edge0: &BorderSide,
-        width0: f32,
-        edge1: &BorderSide,
-        width1: f32,
-        radius: &LayerSize,
-        corner: BorderCorner,
-        border_rect: &LayerRect,
-    ) -> BorderCornerKind;
-
-    fn get_edge(&self, edge: &BorderSide, width: f32) -> (BorderEdgeKind, f32);
-}
+fn get_corner(
+    edge0: &BorderSide,
+    width0: f32,
+    edge1: &BorderSide,
+    width1: f32,
+    radius: &LayerSize,
+    corner: BorderCorner,
+    border_rect: &LayerRect,
+) -> BorderCornerKind {
+    // If both widths are zero, a corner isn't formed.
+    if width0 == 0.0 && width1 == 0.0 {
+        return BorderCornerKind::None;
+    }
 
-impl NormalBorderHelpers for NormalBorder {
-    fn get_corner(
-        &self,
-        edge0: &BorderSide,
-        width0: f32,
-        edge1: &BorderSide,
-        width1: f32,
-        radius: &LayerSize,
-        corner: BorderCorner,
-        border_rect: &LayerRect,
-    ) -> BorderCornerKind {
-        // If both widths are zero, a corner isn't formed.
-        if width0 == 0.0 && width1 == 0.0 {
-            return BorderCornerKind::None;
+    // If both edges are transparent, no corner is formed.
+    if edge0.color.a == 0.0 && edge1.color.a == 0.0 {
+        return BorderCornerKind::None;
+    }
+
+    match (edge0.style, edge1.style) {
+        // If both edges are none or hidden, no corner is needed.
+        (BorderStyle::None, BorderStyle::None) |
+        (BorderStyle::None, BorderStyle::Hidden) |
+        (BorderStyle::Hidden, BorderStyle::None) |
+        (BorderStyle::Hidden, BorderStyle::Hidden) => {
+            BorderCornerKind::None
         }
 
-        // If both edges are transparent, no corner is formed.
-        if edge0.color.a == 0.0 && edge1.color.a == 0.0 {
-            return BorderCornerKind::None;
+        // If one of the edges is none or hidden, we just draw one style.
+        (BorderStyle::None, _) |
+        (_, BorderStyle::None) |
+        (BorderStyle::Hidden, _) |
+        (_, BorderStyle::Hidden) => {
+            BorderCornerKind::Clip(BorderCornerInstance::Single)
         }
 
-        match (edge0.style, edge1.style) {
-            // If both edges are none or hidden, no corner is needed.
-            (BorderStyle::None, BorderStyle::None) |
-            (BorderStyle::None, BorderStyle::Hidden) |
-            (BorderStyle::Hidden, BorderStyle::None) |
-            (BorderStyle::Hidden, BorderStyle::Hidden) => {
-                BorderCornerKind::None
-            }
-
-            // If one of the edges is none or hidden, we just draw one style.
-            (BorderStyle::None, _) |
-            (_, BorderStyle::None) |
-            (BorderStyle::Hidden, _) |
-            (_, BorderStyle::Hidden) => {
+        // If both borders are solid, we can draw them with a simple rectangle if
+        // both the colors match and there is no radius.
+        (BorderStyle::Solid, BorderStyle::Solid) => {
+            if edge0.color == edge1.color && radius.width == 0.0 && radius.height == 0.0 {
+                BorderCornerKind::Solid
+            } else {
                 BorderCornerKind::Clip(BorderCornerInstance::Single)
             }
+        }
 
-            // If both borders are solid, we can draw them with a simple rectangle if
-            // both the colors match and there is no radius.
-            (BorderStyle::Solid, BorderStyle::Solid) => {
-                if edge0.color == edge1.color && radius.width == 0.0 && radius.height == 0.0 {
-                    BorderCornerKind::Solid
-                } else {
-                    BorderCornerKind::Clip(BorderCornerInstance::Single)
-                }
-            }
-
-            // Inset / outset borders just modify the color of edges, so can be
-            // drawn with the normal border corner shader.
-            (BorderStyle::Outset, BorderStyle::Outset) |
-            (BorderStyle::Inset, BorderStyle::Inset) |
-            (BorderStyle::Double, BorderStyle::Double) |
-            (BorderStyle::Groove, BorderStyle::Groove) |
-            (BorderStyle::Ridge, BorderStyle::Ridge) => {
-                BorderCornerKind::Clip(BorderCornerInstance::Single)
-            }
+        // Inset / outset borders just modify the color of edges, so can be
+        // drawn with the normal border corner shader.
+        (BorderStyle::Outset, BorderStyle::Outset) |
+        (BorderStyle::Inset, BorderStyle::Inset) |
+        (BorderStyle::Double, BorderStyle::Double) |
+        (BorderStyle::Groove, BorderStyle::Groove) |
+        (BorderStyle::Ridge, BorderStyle::Ridge) => {
+            BorderCornerKind::Clip(BorderCornerInstance::Single)
+        }
 
-            // Dashed and dotted border corners get drawn into a clip mask.
-            (BorderStyle::Dashed, BorderStyle::Dashed) => BorderCornerKind::new_mask(
-                BorderCornerClipKind::Dash,
-                width0,
-                width1,
-                corner,
-                *radius,
-                *border_rect,
-            ),
-            (BorderStyle::Dotted, BorderStyle::Dotted) => BorderCornerKind::new_mask(
-                BorderCornerClipKind::Dot,
-                width0,
-                width1,
-                corner,
-                *radius,
-                *border_rect,
-            ),
+        // Dashed and dotted border corners get drawn into a clip mask.
+        (BorderStyle::Dashed, BorderStyle::Dashed) => BorderCornerKind::new_mask(
+            BorderCornerClipKind::Dash,
+            width0,
+            width1,
+            corner,
+            *radius,
+            *border_rect,
+        ),
+        (BorderStyle::Dotted, BorderStyle::Dotted) => BorderCornerKind::new_mask(
+            BorderCornerClipKind::Dot,
+            width0,
+            width1,
+            corner,
+            *radius,
+            *border_rect,
+        ),
 
-            // Draw border transitions with dots and/or dashes as
-            // solid segments. The old border path didn't support
-            // this anyway, so we might as well start using the new
-            // border path here, since the dashing in the edges is
-            // much higher quality anyway.
-            (BorderStyle::Dotted, _) |
-            (_, BorderStyle::Dotted) |
-            (BorderStyle::Dashed, _) |
-            (_, BorderStyle::Dashed) => BorderCornerKind::Clip(BorderCornerInstance::Single),
+        // Draw border transitions with dots and/or dashes as
+        // solid segments. The old border path didn't support
+        // this anyway, so we might as well start using the new
+        // border path here, since the dashing in the edges is
+        // much higher quality anyway.
+        (BorderStyle::Dotted, _) |
+        (_, BorderStyle::Dotted) |
+        (BorderStyle::Dashed, _) |
+        (_, BorderStyle::Dashed) => BorderCornerKind::Clip(BorderCornerInstance::Single),
 
-            // Everything else can be handled by drawing the corner twice,
-            // where the shader outputs zero alpha for the side it's not
-            // drawing. This is somewhat inefficient in terms of pixels
-            // written, but it's a fairly rare case, and we can optimize
-            // this case later.
-            _ => BorderCornerKind::Clip(BorderCornerInstance::Double),
-        }
+        // Everything else can be handled by drawing the corner twice,
+        // where the shader outputs zero alpha for the side it's not
+        // drawing. This is somewhat inefficient in terms of pixels
+        // written, but it's a fairly rare case, and we can optimize
+        // this case later.
+        _ => BorderCornerKind::Clip(BorderCornerInstance::Double),
+    }
+}
+
+fn get_edge(edge: &BorderSide, width: f32, height: f32) -> (BorderEdgeKind, f32) {
+    if width == 0.0 || height <= 0.0 {
+        return (BorderEdgeKind::None, 0.0);
     }
 
-    fn get_edge(&self, edge: &BorderSide, width: f32) -> (BorderEdgeKind, f32) {
-        if width == 0.0 {
-            return (BorderEdgeKind::None, 0.0);
+    match edge.style {
+        BorderStyle::None | BorderStyle::Hidden => (BorderEdgeKind::None, 0.0),
+
+        BorderStyle::Solid | BorderStyle::Inset | BorderStyle::Outset => {
+            (BorderEdgeKind::Solid, width)
         }
 
-        match edge.style {
-            BorderStyle::None | BorderStyle::Hidden => (BorderEdgeKind::None, 0.0),
-
-            BorderStyle::Solid | BorderStyle::Inset | BorderStyle::Outset => {
-                (BorderEdgeKind::Solid, width)
-            }
-
-            BorderStyle::Double |
-            BorderStyle::Groove |
-            BorderStyle::Ridge |
-            BorderStyle::Dashed |
-            BorderStyle::Dotted => (BorderEdgeKind::Clip, width),
-        }
+        BorderStyle::Double |
+        BorderStyle::Groove |
+        BorderStyle::Ridge |
+        BorderStyle::Dashed |
+        BorderStyle::Dotted => (BorderEdgeKind::Clip, width),
     }
 }
 
 pub fn ensure_no_corner_overlap(
     radius: &mut BorderRadius,
     rect: &LayerRect,
 ) {
     let mut ratio = 1.0;
@@ -426,58 +408,62 @@ impl<'a> DisplayListFlattener<'a> {
                 None,
                 extra_clips,
             );
 
             return;
         }
 
         let corners = [
-            border.get_corner(
+            get_corner(
                 left,
                 widths.left,
                 top,
                 widths.top,
                 &radius.top_left,
                 BorderCorner::TopLeft,
                 &info.rect,
             ),
-            border.get_corner(
+            get_corner(
                 right,
                 widths.right,
                 top,
                 widths.top,
                 &radius.top_right,
                 BorderCorner::TopRight,
                 &info.rect,
             ),
-            border.get_corner(
+            get_corner(
                 right,
                 widths.right,
                 bottom,
                 widths.bottom,
                 &radius.bottom_right,
                 BorderCorner::BottomRight,
                 &info.rect,
             ),
-            border.get_corner(
+            get_corner(
                 left,
                 widths.left,
                 bottom,
                 widths.bottom,
                 &radius.bottom_left,
                 BorderCorner::BottomLeft,
                 &info.rect,
             ),
         ];
 
-        let (left_edge, left_len) = border.get_edge(left, widths.left);
-        let (top_edge, top_len) = border.get_edge(top, widths.top);
-        let (right_edge, right_len) = border.get_edge(right, widths.right);
-        let (bottom_edge, bottom_len) = border.get_edge(bottom, widths.bottom);
+        let (left_edge, left_len) = get_edge(left, widths.left,
+            info.rect.size.height - radius.top_left.height - radius.bottom_left.height);
+        let (top_edge, top_len) = get_edge(top, widths.top,
+            info.rect.size.width - radius.top_left.width - radius.top_right.width);
+        let (right_edge, right_len) = get_edge(right, widths.right,
+            info.rect.size.height - radius.top_right.height - radius.bottom_right.height);
+        let (bottom_edge, bottom_len) = get_edge(bottom, widths.bottom,
+            info.rect.size.width - radius.bottom_right.width - radius.bottom_left.width);
 
         let edges = [left_edge, top_edge, right_edge, bottom_edge];
 
         // Use a simple rectangle case when all edges and corners are either
         // solid or none.
         let all_corners_simple = corners.iter().all(|c| {
             *c == BorderCornerKind::Solid || *c == BorderCornerKind::None
         });
--- a/gfx/webrender/src/debug_server.rs
+++ b/gfx/webrender/src/debug_server.rs
@@ -58,17 +58,17 @@ impl ws::Handler for Server {
                     "enable_gpu_sample_queries" => DebugCommand::EnableGpuSampleQueries(true),
                     "disable_gpu_sample_queries" => DebugCommand::EnableGpuSampleQueries(false),
                     "fetch_passes" => DebugCommand::FetchPasses,
                     "fetch_screenshot" => DebugCommand::FetchScreenshot,
                     "fetch_documents" => DebugCommand::FetchDocuments,
                     "fetch_clip_scroll_tree" => DebugCommand::FetchClipScrollTree,
                     "fetch_render_tasks" => DebugCommand::FetchRenderTasks,
                     msg => {
-                        println!("unknown msg {}", msg);
+                        error!("unknown msg {}", msg);
                         return Ok(());
                     }
                 };
 
                 let msg = ApiMsg::DebugCommand(cmd);
                 self.api_tx.send(msg).unwrap();
             }
             ws::Message::Binary(..) => {}
@@ -100,19 +100,19 @@ impl DebugServer {
                 }
             })
             .unwrap();
 
         let broadcaster = socket.broadcaster();
 
         let join_handle = Some(thread::spawn(move || {
             let address = "127.0.0.1:3583";
-            println!("WebRender debug server started: {}", address);
+            debug!("WebRender debug server started: {}", address);
             if let Err(..) = socket.listen(address) {
-                println!("ERROR: Unable to bind debugger websocket (port may be in use).");
+                error!("ERROR: Unable to bind debugger websocket (port may be in use).");
             }
         }));
 
         DebugServer {
             join_handle,
             broadcaster,
             debug_rx,
             senders: Vec::new(),
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -6,16 +6,17 @@ use super::shader_source;
 use api::{ColorF, ImageFormat};
 use api::{DeviceIntPoint, DeviceIntRect, DeviceUintRect, DeviceUintSize};
 use api::TextureTarget;
 #[cfg(any(feature = "debug_renderer", feature="capture"))]
 use api::ImageDescriptor;
 use euclid::Transform3D;
 use gleam::gl;
 use internal_types::{FastHashMap, RenderTargetInfo};
+use log::Level;
 use smallvec::SmallVec;
 use std::cell::RefCell;
 use std::fs::File;
 use std::io::Read;
 use std::marker::PhantomData;
 use std::mem;
 use std::ops::Add;
 use std::path::PathBuf;
@@ -782,39 +783,39 @@ impl Device {
             Some(pos) => 2 + pos,
             None => return,
         };
         let base_line_number = match log[2 .. end_pos].parse::<usize>() {
             Ok(number) if number >= 2 => number - 2,
             _ => return,
         };
         for (line, prefix) in source.lines().skip(base_line_number).zip(&["|",">","|"]) {
-            println!("{}\t{}", prefix, line);
+            error!("{}\t{}", prefix, line);
         }
     }
 
     pub fn compile_shader(
         gl: &gl::Gl,
         name: &str,
         shader_type: gl::GLenum,
         source: &String,
     ) -> Result<gl::GLuint, ShaderError> {
         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);
+            error!("Failed to compile shader: {}\n{}", name, log);
             #[cfg(debug_assertions)]
             Self::print_shader_errors(source, &log);
             Err(ShaderError::Compilation(name.to_string(), log))
         } else {
             if !log.is_empty() {
-                println!("Warnings detected on shader: {}\n{}", name, log);
+                warn!("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;
@@ -1375,17 +1376,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!(
+                    error!(
                       "Failed to load a program object with a program binary: {} renderer {}\n{}",
                       base_filename,
                       self.renderer_name,
                       error_log
                     );
                 } else {
                     loaded = true;
                 }
@@ -1437,17 +1438,17 @@ impl Device {
             // to free any memory associated with the parsing and compilation.
             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!(
+                error!(
                     "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));
             }
         }
@@ -2098,16 +2099,41 @@ impl Device {
     pub fn set_blend_mode_subpixel_dual_source(&self) {
         self.gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC1_COLOR);
         self.gl.blend_equation(gl::FUNC_ADD);
     }
 
     pub fn supports_extension(&self, extension: &str) -> bool {
         self.extensions.iter().any(|s| s == extension)
     }
+
+    pub fn echo_driver_messages(&self) {
+        for msg in self.gl.get_debug_messages() {
+            let level = match msg.severity {
+                gl::DEBUG_SEVERITY_HIGH => Level::Error,
+                gl::DEBUG_SEVERITY_MEDIUM => Level::Warn,
+                gl::DEBUG_SEVERITY_LOW => Level::Info,
+                gl::DEBUG_SEVERITY_NOTIFICATION => Level::Debug,
+                _ => Level::Trace,
+            };
+            let ty = match msg.ty {
+                gl::DEBUG_TYPE_ERROR => "error",
+                gl::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "deprecated",
+                gl::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "undefined",
+                gl::DEBUG_TYPE_PORTABILITY => "portability",
+                gl::DEBUG_TYPE_PERFORMANCE => "perf",
+                gl::DEBUG_TYPE_MARKER => "marker",
+                gl::DEBUG_TYPE_PUSH_GROUP => "group push",
+                gl::DEBUG_TYPE_POP_GROUP => "group pop",
+                gl::DEBUG_TYPE_OTHER => "other",
+                _ => "?",
+            };
+            log!(level, "({}) {}", ty, msg.message);
+        }
+    }
 }
 
 struct FormatDesc {
     internal: gl::GLint,
     external: gl::GLuint,
     pixel_type: gl::GLuint,
 }
 
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -565,17 +565,21 @@ impl<'a> DisplayListFlattener<'a> {
         item: &DisplayItemRef,
         info: &IframeDisplayItem,
         clip_and_scroll_ids: &ClipAndScrollInfo,
         reference_frame_relative_offset: &LayerVector2D,
     ) {
         let iframe_pipeline_id = info.pipeline_id;
         let pipeline = match self.scene.pipelines.get(&iframe_pipeline_id) {
             Some(pipeline) => pipeline,
-            None => return,
+            None => {
+                //TODO: assert/debug_assert?
+                error!("Unknown pipeline used for iframe {:?}", info);
+                return
+            },
         };
 
         self.id_to_index_mapper.initialize_for_pipeline(pipeline);
 
         self.add_clip_node(
             info.clip_id,
             clip_and_scroll_ids.scroll_node_id,
             ClipRegion::create_for_clip_node_with_local_clip(
--- a/gfx/webrender/src/freelist.rs
+++ b/gfx/webrender/src/freelist.rs
@@ -85,16 +85,22 @@ impl<T> FreeList<T> {
     pub fn recycle(self) -> FreeList<T> {
         FreeList {
             slots: recycle_vec(self.slots),
             free_list_head: None,
             active_count: 0,
         }
     }
 
+    pub fn clear(&mut self) {
+        self.slots.clear();
+        self.free_list_head = None;
+        self.active_count = 0;
+    }
+
     #[allow(dead_code)]
     pub fn get(&self, id: &FreeListHandle<T>) -> &T {
         self.slots[id.index as usize].value.as_ref().unwrap()
     }
 
     #[allow(dead_code)]
     pub fn get_mut(&mut self, id: &FreeListHandle<T>) -> &mut T {
         self.slots[id.index as usize].value.as_mut().unwrap()
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -3,16 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #[cfg(test)]
 use api::{IdNamespace, LayoutPoint};
 use api::{ColorF, ColorU};
 use api::{FontInstanceFlags, FontInstancePlatformOptions};
 use api::{FontKey, FontRenderMode, FontTemplate, FontVariation};
 use api::{GlyphDimensions, GlyphKey, LayerToWorldTransform, SubpixelDirection};
+#[cfg(feature = "pathfinder")]
+use api::NativeFontHandle;
 #[cfg(any(test, feature = "pathfinder"))]
 use api::DeviceIntSize;
 #[cfg(not(feature = "pathfinder"))]
 use api::{ImageData, ImageDescriptor, ImageFormat};
 use app_units::Au;
 #[cfg(not(feature = "pathfinder"))]
 use device::TextureFilter;
 #[cfg(feature = "pathfinder")]
@@ -123,44 +125,44 @@ impl FontTransform {
         FontTransform::new(
             (self.scale_x * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
             (self.skew_x * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
             (self.skew_y * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
             (self.scale_y * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
         )
     }
 
-    #[cfg(not(feature = "pathfinder"))]
+    #[allow(dead_code)]
     pub fn determinant(&self) -> f64 {
         self.scale_x as f64 * self.scale_y as f64 - self.skew_y as f64 * self.skew_x as f64
     }
 
-    #[cfg(not(feature = "pathfinder"))]
+    #[allow(dead_code)]
     pub fn compute_scale(&self) -> Option<(f64, f64)> {
         let det = self.determinant();
         if det != 0.0 {
             let x_scale = (self.scale_x as f64).hypot(self.skew_y as f64);
             let y_scale = det.abs() / x_scale;
             Some((x_scale, y_scale))
         } else {
             None
         }
     }
 
-    #[cfg(not(feature = "pathfinder"))]
+    #[allow(dead_code)]
     pub fn pre_scale(&self, scale_x: f32, scale_y: f32) -> Self {
         FontTransform::new(
             self.scale_x * scale_x,
             self.skew_x * scale_y,
             self.skew_y * scale_x,
             self.scale_y * scale_y,
         )
     }
 
-    #[cfg(not(feature = "pathfinder"))]
+    #[allow(dead_code)]
     pub fn invert_scale(&self, x_scale: f64, y_scale: f64) -> Self {
         self.pre_scale(x_scale.recip() as f32, y_scale.recip() as f32)
     }
 
     pub fn synthesize_italics(&self, skew_factor: f32) -> Self {
         FontTransform::new(
             self.scale_x,
             self.skew_x - self.scale_x * skew_factor,
@@ -448,16 +450,17 @@ impl GlyphRasterizer {
         }
 
         self.add_font_to_pathfinder(&font_key, &template);
     }
 
     #[cfg(feature = "pathfinder")]
     fn add_font_to_pathfinder(&mut self, font_key: &FontKey, template: &FontTemplate) {
         let font_contexts = Arc::clone(&self.font_contexts);
+        debug!("add_font_to_pathfinder({:?})", font_key);
         font_contexts.lock_pathfinder_context().add_font(&font_key, &template);
     }
 
     #[cfg(not(feature = "pathfinder"))]
     fn add_font_to_pathfinder(&mut self, _: &FontKey, _: &FontTemplate) {}
 
     pub fn delete_font(&mut self, font_key: FontKey) {
         self.fonts_to_remove.push(font_key);
@@ -856,20 +859,20 @@ impl AddFont for FontContext {
     }
 }
 
 #[cfg(feature = "pathfinder")]
 impl AddFont for PathfinderFontContext {
     fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate) {
         match *template {
             FontTemplate::Raw(ref bytes, index) => {
-                drop(self.add_font_from_memory(font_key, bytes.clone(), index));
+                drop(self.add_font_from_memory(&font_key, bytes.clone(), index))
             }
             FontTemplate::Native(ref native_font_handle) => {
-                drop(self.add_native_font(font_key, (*native_font_handle).clone().0));
+                drop(self.add_native_font(&font_key, NativeFontHandleWrapper(native_font_handle)))
             }
         }
     }
 }
 
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -1062,8 +1065,11 @@ fn request_render_task_from_pathfinder(g
     let render_pass = match render_mode {
         FontRenderMode::Mono | FontRenderMode::Alpha => &mut render_passes.alpha_glyph_pass,
         FontRenderMode::Subpixel => &mut render_passes.color_glyph_pass,
     };
     render_pass.add_render_task(root_task_id, *glyph_size, RenderTargetKind::Color);
 
     Ok((root_task_id, false))
 }
+
+#[cfg(feature = "pathfinder")]
+pub struct NativeFontHandleWrapper<'a>(pub &'a NativeFontHandle);
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -23,16 +23,18 @@ use core_graphics::font::{CGFont, CGGlyp
 use core_graphics::geometry::{CGAffineTransform, CGPoint, CGSize};
 #[cfg(not(feature = "pathfinder"))]
 use core_graphics::geometry::CGRect;
 use core_text;
 use core_text::font::{CTFont, CTFontRef};
 use core_text::font_descriptor::{kCTFontDefaultOrientation, kCTFontColorGlyphsTrait};
 use gamma_lut::{ColorLut, GammaLut};
 use glyph_rasterizer::{FontInstance, FontTransform};
+#[cfg(feature = "pathfinder")]
+use glyph_rasterizer::NativeFontHandleWrapper;
 #[cfg(not(feature = "pathfinder"))]
 use glyph_rasterizer::{GlyphFormat, GlyphRasterResult, RasterizedGlyph};
 use internal_types::{FastHashMap, ResourceCacheError};
 use std::collections::hash_map::Entry;
 use std::sync::Arc;
 
 pub struct FontContext {
     cg_fonts: FastHashMap<FontKey, CGFont>,
@@ -724,8 +726,15 @@ impl FontContext {
             width: metrics.rasterized_width,
             height: metrics.rasterized_height,
             scale: if bitmap { y_scale.recip() as f32 } else { 1.0 },
             format: if bitmap { GlyphFormat::ColorBitmap } else { font.get_glyph_format() },
             bytes: rasterized_pixels,
         })
     }
 }
+
+#[cfg(feature = "pathfinder")]
+impl<'a> Into<CGFont> for NativeFontHandleWrapper<'a> {
+    fn into(self) -> CGFont {
+        (self.0).0.clone()
+    }
+}
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -14,17 +14,21 @@ use freetype::freetype::{FT_Init_FreeTyp
 use freetype::freetype::{FT_Library, FT_Outline_Get_CBox, FT_Set_Char_Size, FT_Select_Size};
 use freetype::freetype::{FT_Fixed, FT_Matrix, FT_Set_Transform};
 use freetype::freetype::{FT_LOAD_COLOR, FT_LOAD_DEFAULT, FT_LOAD_FORCE_AUTOHINT};
 use freetype::freetype::{FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH, FT_LOAD_NO_AUTOHINT};
 use freetype::freetype::{FT_LOAD_NO_BITMAP, FT_LOAD_NO_HINTING, FT_LOAD_VERTICAL_LAYOUT};
 use freetype::freetype::{FT_FACE_FLAG_SCALABLE, FT_FACE_FLAG_FIXED_SIZES};
 use freetype::succeeded;
 use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphRasterResult, RasterizedGlyph};
+#[cfg(feature = "pathfinder")]
+use glyph_rasterizer::NativeFontHandleWrapper;
 use internal_types::{FastHashMap, ResourceCacheError};
+#[cfg(feature = "pathfinder")]
+use pathfinder_font_renderer::freetype as pf_freetype;
 use std::{cmp, mem, ptr, slice};
 use std::cmp::max;
 use std::ffi::CString;
 use std::sync::Arc;
 
 // These constants are not present in the freetype
 // bindings due to bindgen not handling the way
 // the macros are defined.
@@ -187,17 +191,17 @@ impl FontContext {
                 self.faces.insert(
                     *font_key,
                     Face {
                         face,
                         _bytes: Some(bytes),
                     },
                 );
             } else {
-                println!("WARN: webrender failed to load font");
+                warn!("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();
@@ -214,17 +218,17 @@ impl FontContext {
                 self.faces.insert(
                     *font_key,
                     Face {
                         face,
                         _bytes: None,
                     },
                 );
             } else {
-                println!("WARN: webrender failed to load font");
+                warn!("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) };
@@ -783,8 +787,16 @@ impl FontContext {
 
 impl Drop for FontContext {
     fn drop(&mut self) {
         unsafe {
             FT_Done_FreeType(self.lib);
         }
     }
 }
+
+#[cfg(feature = "pathfinder")]
+impl<'a> Into<pf_freetype::FontDescriptor> for NativeFontHandleWrapper<'a> {
+    fn into(self) -> pf_freetype::FontDescriptor {
+        let NativeFontHandleWrapper(font_handle) = self;
+        pf_freetype::FontDescriptor::new(font_handle.pathname.clone().into(), font_handle.index)
+    }
+}
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -661,16 +661,20 @@ impl RenderBackend {
                 profile_scope!("GetScrollNodeState");
                 tx.send(doc.get_scroll_node_state()).unwrap();
                 DocumentOps::nop()
             }
             FrameMsg::UpdateDynamicProperties(property_bindings) => {
                 doc.dynamic_properties.set_properties(property_bindings);
                 DocumentOps::render()
             }
+            FrameMsg::AppendDynamicProperties(property_bindings) => {
+                doc.dynamic_properties.add_properties(property_bindings);
+                DocumentOps::render()
+            }
         }
     }
 
     fn next_namespace_id(&self) -> IdNamespace {
         IdNamespace(NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed) as u32)
     }
 
     pub fn run(&mut self, mut profile_counters: BackendProfileCounters) {
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -6,16 +6,17 @@ use api::{DeviceIntPoint, DeviceIntRect,
 #[cfg(feature = "pathfinder")]
 use api::FontRenderMode;
 use box_shadow::{BoxShadowCacheKey};
 use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::CoordinateSystemId;
 use device::TextureFilter;
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
+use freelist::{FreeList, FreeListHandle};
 use glyph_rasterizer::GpuGlyphCacheKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{ImageSource, RasterizationSpace};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use prim_store::{PrimitiveIndex, ImageCacheKey};
 #[cfg(feature = "debugger")]
@@ -861,37 +862,40 @@ pub enum RenderTaskCacheKeyKind {
 #[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,
+pub struct RenderTaskCacheEntry {
+    pub 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>,
+    map: FastHashMap<RenderTaskCacheKey, FreeListHandle<RenderTaskCacheEntry>>,
+    cache_entries: FreeList<RenderTaskCacheEntry>,
 }
 
 impl RenderTaskCache {
     pub fn new() -> Self {
         RenderTaskCache {
-            entries: FastHashMap::default(),
+            map: FastHashMap::default(),
+            cache_entries: FreeList::new(),
         }
     }
 
     pub fn clear(&mut self) {
-        self.entries.clear();
+        self.map.clear();
+        self.cache_entries.clear();
     }
 
     pub fn begin_frame(
         &mut self,
         texture_cache: &mut TextureCache,
     ) {
         // Drop any items from the cache that have been
         // evicted from the texture cache.
@@ -901,38 +905,53 @@ impl RenderTaskCache {
         // It will evict render tasks as required, since
         // the access time in the texture cache entry will
         // be stale if this task hasn't been requested
         // for a while.
         //
         // Nonetheless, we should remove stale entries
         // from here so that this hash map doesn't
         // grow indefinitely!
-        self.entries.retain(|_, value| {
-            texture_cache.is_allocated(&value.handle)
-        });
+        let mut keys_to_remove = Vec::new();
+
+        for (key, handle) in &self.map {
+            let entry = self.cache_entries.get(handle);
+            if !texture_cache.is_allocated(&entry.handle) {
+                keys_to_remove.push(key.clone())
+            }
+        }
+
+        for key in &keys_to_remove {
+            let handle = self.map.remove(key).unwrap();
+            self.cache_entries.free(handle);
+        }
     }
 
     pub fn request_render_task<F>(
         &mut self,
         key: RenderTaskCacheKey,
         texture_cache: &mut TextureCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         user_data: Option<[f32; 3]>,
         mut f: F,
     ) -> Result<CacheItem, ()>
          where F: FnMut(&mut RenderTaskTree) -> Result<(RenderTaskId, bool), ()> {
         // Get the texture cache handle for this cache key,
         // or create one.
-        let cache_entry = self.entries
-                              .entry(key)
-                              .or_insert(RenderTaskCacheEntry {
-                                  handle: TextureCacheHandle::new(),
-                              });
+        let cache_entries = &mut self.cache_entries;
+        let entry_handle = self.map
+                               .entry(key)
+                               .or_insert_with(|| {
+                                    let entry = RenderTaskCacheEntry {
+                                        handle: TextureCacheHandle::new(),
+                                    };
+                                    cache_entries.insert(entry)
+                                });
+        let cache_entry = cache_entries.get_mut(entry_handle);
 
         // Check if this texture cache handle is valid.
         if texture_cache.request(&cache_entry.handle, gpu_cache) {
             // Invoke user closure to get render task chain
             // to draw this into the texture cache.
             let (render_task_id, is_opaque) = try!(f(render_tasks));
             let render_task = &mut render_tasks[render_task_id];
 
@@ -995,26 +1014,28 @@ impl RenderTaskCache {
     }
 
     #[allow(dead_code)]
     pub fn get_cache_item_for_render_task(&self,
                                           texture_cache: &TextureCache,
                                           key: &RenderTaskCacheKey)
                                           -> CacheItem {
         // Get the texture cache handle for this cache key.
-        let cache_entry = self.entries.get(key).unwrap();
+        let handle = self.map.get(key).unwrap();
+        let cache_entry = self.cache_entries.get(handle);
         texture_cache.get(&cache_entry.handle)
     }
 
     #[allow(dead_code)]
     pub fn cache_item_is_allocated_for_render_task(&self,
                                                    texture_cache: &TextureCache,
                                                    key: &RenderTaskCacheKey)
                                                    -> bool {
-        let cache_entry = self.entries.get(key).unwrap();
+        let handle = self.map.get(key).unwrap();
+        let cache_entry = self.cache_entries.get(handle);
         texture_cache.is_allocated(&cache_entry.handle)
     }
 }
 
 // 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
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -250,16 +250,17 @@ bitflags! {
         const PROFILER_DBG      = 1 << 0;
         const RENDER_TARGET_DBG = 1 << 1;
         const TEXTURE_CACHE_DBG = 1 << 2;
         const GPU_TIME_QUERIES  = 1 << 3;
         const GPU_SAMPLE_QUERIES= 1 << 4;
         const DISABLE_BATCHING  = 1 << 5;
         const EPOCHS            = 1 << 6;
         const COMPACT_PROFILER  = 1 << 7;
+        const ECHO_DRIVER_MESSAGES = 1 << 8;
     }
 }
 
 fn flag_changed(before: DebugFlags, after: DebugFlags, select: DebugFlags) -> Option<bool> {
     if before & select != after & select {
         Some(after.contains(select))
     } else {
         None
@@ -1252,16 +1253,22 @@ impl LazyInitializedDebugRenderer {
 
     pub fn deinit(self, device: &mut Device) {
         if let Some(debug_renderer) = self.debug_renderer {
             debug_renderer.deinit(device);
         }
     }
 }
 
+pub struct RendererVAOs {
+    prim_vao: VAO,
+    blur_vao: VAO,
+    clip_vao: VAO,
+}
+
 /// The renderer is responsible for submitting to the GPU the work prepared by the
 /// RenderBackend.
 pub struct Renderer {
     result_rx: Receiver<ResultMsg>,
     debug_server: DebugServer,
     pub device: Device,
     pending_texture_updates: Vec<TextureUpdateList>,
     pending_gpu_cache_updates: Vec<GpuCacheUpdateList>,
@@ -1282,19 +1289,17 @@ pub struct Renderer {
     debug_flags: DebugFlags,
     backend_profile_counters: BackendProfileCounters,
     profile_counters: RendererProfileCounters,
     #[cfg(feature = "debug_renderer")]
     profiler: Profiler,
     last_time: u64,
 
     pub gpu_profile: GpuProfiler<GpuProfileTag>,
-    prim_vao: VAO,
-    blur_vao: VAO,
-    clip_vao: VAO,
+    vaos: RendererVAOs,
 
     node_data_texture: VertexDataTexture,
     local_clip_rects_texture: VertexDataTexture,
     render_task_texture: VertexDataTexture,
     gpu_cache_texture: CacheTexture,
 
     gpu_cache_frame_id: FrameId,
     gpu_cache_overflow: bool,
@@ -1407,17 +1412,17 @@ impl Renderer {
             device.supports_extension("GL_ARB_blend_func_extended");
 
         let device_max_size = device.max_texture_size();
         // 512 is the minimum that the texture cache can work with.
         // Broken GL contexts can return a max texture size of zero (See #1260). Better to
         // gracefully fail now than panic as soon as a texture is allocated.
         let min_texture_size = 512;
         if device_max_size < min_texture_size {
-            println!(
+            error!(
                 "Device reporting insufficient max texture size ({})",
                 device_max_size
             );
             return Err(RendererError::MaxTextureSize);
         }
         let max_device_size = cmp::max(
             cmp::min(
                 device_max_size,
@@ -1684,19 +1689,21 @@ impl Renderer {
             profiler: Profiler::new(),
             max_texture_size: max_device_size,
             max_recorded_profiles: options.max_recorded_profiles,
             clear_color: options.clear_color,
             enable_clear_scissor: options.enable_clear_scissor,
             last_time: 0,
             gpu_profile,
             gpu_glyph_renderer,
-            prim_vao,
-            blur_vao,
-            clip_vao,
+            vaos: RendererVAOs {
+                prim_vao,
+                blur_vao,
+                clip_vao,
+            },
             node_data_texture,
             local_clip_rects_texture,
             render_task_texture,
             pipeline_info: PipelineInfo::default(),
             dither_matrix_texture,
             external_image_handler: None,
             output_image_handler: None,
             output_targets: FastHashMap::default(),
@@ -2293,16 +2300,20 @@ impl Renderer {
                         screen_fraction,
                         self.debug.get_mut(&mut self.device),
                         self.debug_flags.contains(DebugFlags::COMPACT_PROFILER),
                     );
                 }
             }
         }
 
+        if self.debug_flags.contains(DebugFlags::ECHO_DRIVER_MESSAGES) {
+            self.device.echo_driver_messages();
+        }
+
         self.backend_profile_counters.reset();
         self.profile_counters.reset();
         self.profile_counters.frame_counter.inc();
 
         profile_timers.cpu_time.profile(|| {
             let _gm = self.gpu_profile.start_marker("end frame");
             self.gpu_profile.end_frame();
             #[cfg(feature = "debug_renderer")]
@@ -2507,21 +2518,17 @@ impl Renderer {
     }
 
     pub(crate) fn draw_instanced_batch_with_previously_bound_textures<T>(
         &mut self,
         data: &[T],
         vertex_array_kind: VertexArrayKind,
         stats: &mut RendererStats,
     ) {
-        let vao = get_vao(vertex_array_kind,
-                          &self.prim_vao,
-                          &self.clip_vao,
-                          &self.blur_vao,
-                          &self.gpu_glyph_renderer);
+        let vao = get_vao(vertex_array_kind, &self.vaos, &self.gpu_glyph_renderer);
 
         self.device.bind_vao(vao);
 
         let batched = !self.debug_flags.contains(DebugFlags::DISABLE_BATCHING);
 
         if batched {
             self.device
                 .update_vao_instances(vao, data, VertexUsageHint::Stream);
@@ -3915,19 +3922,19 @@ impl Renderer {
         if let Some(dither_matrix_texture) = self.dither_matrix_texture {
             self.device.delete_texture(dither_matrix_texture);
         }
         self.node_data_texture.deinit(&mut self.device);
         self.local_clip_rects_texture.deinit(&mut self.device);
         self.render_task_texture.deinit(&mut self.device);
         self.device.delete_pbo(self.texture_cache_upload_pbo);
         self.texture_resolver.deinit(&mut self.device);
-        self.device.delete_vao(self.prim_vao);
-        self.device.delete_vao(self.clip_vao);
-        self.device.delete_vao(self.blur_vao);
+        self.device.delete_vao(self.vaos.prim_vao);
+        self.device.delete_vao(self.vaos.clip_vao);
+        self.device.delete_vao(self.vaos.blur_vao);
 
         #[cfg(feature = "debug_renderer")]
         {
             self.debug.deinit(&mut self.device);
         }
 
         for (_, target) in self.output_targets {
             self.device.delete_fbo(target.fbo_id);
@@ -4483,40 +4490,34 @@ impl Renderer {
         }
 
         self.output_image_handler = Some(Box::new(()) as Box<_>);
         self.external_image_handler = Some(Box::new(image_handler) as Box<_>);
         info!("done.");
     }
 }
 
-// FIXME(pcwalton): We should really gather up all the VAOs into a separate structure so that they
-// don't have to be passed in as parameters here.
 #[cfg(feature = "pathfinder")]
 fn get_vao<'a>(vertex_array_kind: VertexArrayKind,
-               prim_vao: &'a VAO,
-               clip_vao: &'a VAO,
-               blur_vao: &'a VAO,
+               vaos: &'a RendererVAOs,
                gpu_glyph_renderer: &'a GpuGlyphRenderer)
                -> &'a VAO {
     match vertex_array_kind {
-        VertexArrayKind::Primitive => prim_vao,
-        VertexArrayKind::Clip => clip_vao,
-        VertexArrayKind::Blur => blur_vao,
+        VertexArrayKind::Primitive => &vaos.prim_vao,
+        VertexArrayKind::Clip => &vaos.clip_vao,
+        VertexArrayKind::Blur => &vaos.blur_vao,
         VertexArrayKind::VectorStencil => &gpu_glyph_renderer.vector_stencil_vao,
         VertexArrayKind::VectorCover => &gpu_glyph_renderer.vector_cover_vao,
     }
 }
 
 #[cfg(not(feature = "pathfinder"))]
 fn get_vao<'a>(vertex_array_kind: VertexArrayKind,
-               prim_vao: &'a VAO,
-               clip_vao: &'a VAO,
-               blur_vao: &'a VAO,
+               vaos: &'a RendererVAOs,
                _: &'a GpuGlyphRenderer)
                -> &'a VAO {
     match vertex_array_kind {
-        VertexArrayKind::Primitive => prim_vao,
-        VertexArrayKind::Clip => clip_vao,
-        VertexArrayKind::Blur => blur_vao,
+        VertexArrayKind::Primitive => &vaos.prim_vao,
+        VertexArrayKind::Clip => &vaos.clip_vao,
+        VertexArrayKind::Blur => &vaos.blur_vao,
         VertexArrayKind::VectorStencil | VertexArrayKind::VectorCover => unreachable!(),
     }
 }
--- 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::{ClearCache, ColorF, DevicePoint, DeviceUintRect, DeviceUintSize};
+use api::{ClearCache, ColorF, DevicePoint, DeviceUintPoint, 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")]
@@ -477,17 +477,18 @@ impl ResourceCache {
             );
         }
 
         let resource = ImageResource {
             descriptor,
             data,
             epoch: Epoch(0),
             tiling,
-            dirty_rect: None,
+            dirty_rect: Some(DeviceUintRect::new(DeviceUintPoint::zero(),
+                                                 DeviceUintSize::new(descriptor.width, descriptor.height))),
         };
 
         self.resources.image_templates.insert(image_key, resource);
     }
 
     pub fn update_image_template(
         &mut self,
         image_key: ImageKey,
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -26,17 +26,21 @@ impl SceneProperties {
             float_properties: FastHashMap::default(),
         }
     }
 
     /// Set the current property list for this display list.
     pub fn set_properties(&mut self, properties: DynamicProperties) {
         self.transform_properties.clear();
         self.float_properties.clear();
+        self.add_properties(properties);
+    }
 
+    /// Add to the current property list for this display list.
+    pub fn add_properties(&mut self, properties: DynamicProperties) {
         for property in properties.transforms {
             self.transform_properties
                 .insert(property.key.id, property.value);
         }
 
         for property in properties.floats {
             self.float_properties
                 .insert(property.key.id, property.value);
--- a/gfx/webrender/src/shade.rs
+++ b/gfx/webrender/src/shade.rs
@@ -469,16 +469,25 @@ pub struct Shaders {
 }
 
 impl Shaders {
     pub fn new(
         device: &mut Device,
         gl_type: GlType,
         options: &RendererOptions,
     ) -> Result<Self, ShaderError> {
+        // needed for the precache fake draws
+        let dummy_vao = if options.precache_shaders {
+            let vao = device.create_custom_vao(&[]);
+            device.bind_custom_vao(&vao);
+            Some(vao)
+        } else {
+            None
+        };
+
         let brush_solid = BrushShader::new(
             "brush_solid",
             device,
             &[],
             options.precache_shaders,
         )?;
 
         let brush_blend = BrushShader::new(
@@ -676,16 +685,20 @@ impl Shaders {
         let ps_split_composite = LazilyCompiledShader::new(
             ShaderKind::Primitive,
             "ps_split_composite",
             &[],
             device,
             options.precache_shaders,
         )?;
 
+        if let Some(vao) = dummy_vao {
+            device.delete_custom_vao(vao);
+        }
+
         Ok(Shaders {
             cs_blur_a8,
             cs_blur_rgba8,
             brush_solid,
             brush_image,
             brush_blend,
             brush_mix_blend,
             brush_yuv_image,
--- a/gfx/webrender_api/Cargo.toml
+++ b/gfx/webrender_api/Cargo.toml
@@ -13,18 +13,18 @@ deserialize = []
 
 [dependencies]
 app_units = "0.6"
 bincode = "1.0"
 bitflags = "1.0"
 byteorder = "1.2.1"
 ipc-channel = {version = "0.10.0", optional = true}
 euclid = { version = "0.17", features = ["serde"] }
-serde = { version = "=1.0.35", features = ["rc"] }
-serde_derive = { version = "=1.0.35", features = ["deserialize_in_place"] }
+serde = { version = "=1.0.37", features = ["rc"] }
+serde_derive = { version = "=1.0.37", features = ["deserialize_in_place"] }
 serde_bytes = "0.10"
 time = "0.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.5"
 core-graphics = "0.13"
 
 [target.'cfg(target_os = "windows")'.dependencies]
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -319,16 +319,24 @@ impl Transaction {
     }
 
     /// Supply a list of animated property bindings that should be used to resolve
     /// bindings in the current display list.
     pub fn update_dynamic_properties(&mut self, properties: DynamicProperties) {
         self.frame_ops.push(FrameMsg::UpdateDynamicProperties(properties));
     }
 
+    /// Add to the list of animated property bindings that should be used to
+    /// resolve bindings in the current display list. This is a convenience method
+    /// so the caller doesn't have to figure out all the dynamic properties before
+    /// setting them on the transaction but can do them incrementally.
+    pub fn append_dynamic_properties(&mut self, properties: DynamicProperties) {
+        self.frame_ops.push(FrameMsg::AppendDynamicProperties(properties));
+    }
+
     /// Enable copying of the output of this pipeline id to
     /// an external texture for callers to consume.
     pub fn enable_frame_output(&mut self, pipeline_id: PipelineId, enable: bool) {
         self.frame_ops.push(FrameMsg::EnableFrameOutput(pipeline_id, enable));
     }
 
     fn finalize(self) -> (TransactionMsg, Vec<Payload>) {
         (
@@ -481,16 +489,17 @@ pub enum FrameMsg {
     UpdateEpoch(PipelineId, Epoch),
     HitTest(Option<PipelineId>, WorldPoint, HitTestFlags, MsgSender<HitTestResult>),
     SetPan(DeviceIntPoint),
     EnableFrameOutput(PipelineId, bool),
     Scroll(ScrollLocation, WorldPoint),
     ScrollNodeWithId(LayoutPoint, ExternalScrollId, ScrollClamping),
     GetScrollNodeState(MsgSender<Vec<ScrollNodeState>>),
     UpdateDynamicProperties(DynamicProperties),
+    AppendDynamicProperties(DynamicProperties),
 }
 
 impl fmt::Debug for SceneMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             SceneMsg::UpdateEpoch(..) => "SceneMsg::UpdateEpoch",
             SceneMsg::SetDisplayList { .. } => "SceneMsg::SetDisplayList",
             SceneMsg::SetPageZoom(..) => "SceneMsg::SetPageZoom",
@@ -508,16 +517,17 @@ impl fmt::Debug for FrameMsg {
             FrameMsg::UpdateEpoch(..) => "FrameMsg::UpdateEpoch",
             FrameMsg::HitTest(..) => "FrameMsg::HitTest",
             FrameMsg::SetPan(..) => "FrameMsg::SetPan",
             FrameMsg::Scroll(..) => "FrameMsg::Scroll",
             FrameMsg::ScrollNodeWithId(..) => "FrameMsg::ScrollNodeWithId",
             FrameMsg::GetScrollNodeState(..) => "FrameMsg::GetScrollNodeState",
             FrameMsg::EnableFrameOutput(..) => "FrameMsg::EnableFrameOutput",
             FrameMsg::UpdateDynamicProperties(..) => "FrameMsg::UpdateDynamicProperties",
+            FrameMsg::AppendDynamicProperties(..) => "FrameMsg::AppendDynamicProperties",
         })
     }
 }
 
 bitflags!{
     /// Bit flags for WR stages to store in a capture.
     // Note: capturing `FRAME` without `SCENE` is not currently supported.
     #[derive(Deserialize, Serialize)]
--- a/gfx/webrender_bindings/Cargo.toml
+++ b/gfx/webrender_bindings/Cargo.toml
@@ -4,17 +4,17 @@ version = "0.1.0"
 authors = ["The Mozilla Project Developers"]
 license = "MPL-2.0"
 
 [dependencies]
 rayon = "1"
 thread_profiler = "0.1.1"
 euclid = { version = "0.17", features = ["serde"] }
 app_units = "0.6"
-gleam = "0.4.20"
+gleam = "0.4.32"
 log = "0.4"
 
 [dependencies.webrender]
 path = "../webrender"
 version = "0.57.2"
 default-features = false
 features = ["capture"]
 
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-092ada1154b72fe71d2f227a5df0239586d2323a
+6f997974cec5772b1797725f4a7942d742e7d7ff
--- a/gfx/wrench/Cargo.toml
+++ b/gfx/wrench/Cargo.toml
@@ -19,17 +19,17 @@ clap = { version = "2", features = ["yam
 lazy_static = "1"
 log = "0.4"
 yaml-rust = { git = "https://github.com/vvuk/yaml-rust", features = ["preserve_order"] }
 serde_json = "1.0"
 ron = "0.1.5"
 time = "0.1"
 crossbeam = "0.2"
 osmesa-sys = { version = "0.1.2", optional = true }
-osmesa-src = { git = "https://github.com/servo/osmesa-src", optional = true }
+osmesa-src = { git = "https://github.com/jrmuizel/osmesa-src", optional = true, branch = "serialize" }
 webrender = {path = "../webrender", features=["capture","replay","debugger","png","profiler"]}
 webrender_api = {path = "../webrender_api", features=["serialize","deserialize"]}
 serde = {version = "1.0", features = ["derive"] }
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-graphics = "0.13"
 core-foundation = "0.5"
 
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -192,17 +192,17 @@ impl Wrench {
                 Box<webrender::ApiRecordingReceiver>,
             SaveType::Ron => Box::new(RonFrameWriter::new(&PathBuf::from("ron_frames"))) as
                 Box<webrender::ApiRecordingReceiver>,
             SaveType::Binary => Box::new(webrender::BinaryRecorder::new(
                 &PathBuf::from("wr-record.bin"),
             )) as Box<webrender::ApiRecordingReceiver>,
         });
 
-        let mut debug_flags = DebugFlags::default();
+        let mut debug_flags = DebugFlags::ECHO_DRIVER_MESSAGES;
         debug_flags.set(DebugFlags::DISABLE_BATCHING, no_batch);
         let callbacks = Arc::new(Mutex::new(blob::BlobCallbacks::new()));
 
         let opts = webrender::RendererOptions {
             device_pixel_ratio: dp_ratio,
             resource_override_path: shader_override_path,
             recorder,
             enable_subpixel_aa: !no_subpixel_aa,