Bug 1458870 - Update webrender to commit 9a3bc6b965554c04c0bba326cdee45240c3b4ba7. r=jrmuizel
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 08 May 2018 09:33:03 -0400
changeset 417449 1fa288f68879a79a65799f818de24397fc4c3e84
parent 417448 754824066ed075a0654efdf1ce381bf3660ed650
child 417450 84aa798f616702b18f1344263ae94d3ece78595a
push id103063
push useraciure@mozilla.com
push dateTue, 08 May 2018 23:03:54 +0000
treeherdermozilla-inbound@db6fbfbb688c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1458870
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1458870 - Update webrender to commit 9a3bc6b965554c04c0bba326cdee45240c3b4ba7. r=jrmuizel MozReview-Commit-ID: J3QepnGtmOs
gfx/webrender/Cargo.toml
gfx/webrender/res/debug_font.glsl
gfx/webrender/src/device.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/hit_test.rs
gfx/webrender/src/image.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_api/src/units.rs
gfx/webrender_bindings/Cargo.toml
gfx/webrender_bindings/revision.txt
gfx/wrench/Cargo.toml
gfx/wrench/src/blob.rs
gfx/wrench/src/yaml_frame_reader.rs
gfx/wrench/src/yaml_frame_writer.rs
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -13,39 +13,39 @@ profiler = ["thread_profiler/thread_prof
 debugger = ["ws", "serde_json", "serde", "image", "base64", "debug_renderer"]
 capture = ["webrender_api/serialize", "ron", "serde", "debug_renderer"]
 replay = ["webrender_api/deserialize", "ron", "serde"]
 debug_renderer = []
 pathfinder = ["pathfinder_font_renderer", "pathfinder_gfx_utils", "pathfinder_partitioner", "pathfinder_path_utils"]
 
 [dependencies]
 app_units = "0.6"
+base64 = { optional = true, version = "0.6" }
+bincode = "1.0"
+bitflags = "1.0"
 byteorder = "1.0"
-bincode = "1.0"
+cfg-if = "0.1.2"
 euclid = "0.17"
 fxhash = "0.2.1"
-gleam = "0.4.32"
+gleam = "0.5"
+image = { optional = true, version = "0.18" }
 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"
 plane-split = "0.8"
 png = { optional = true, version = "0.11" }
-smallvec = "0.6"
-ws = { optional = true, version = "0.7.3" }
-serde_json = { optional = true, version = "1.0" }
+rayon = "1"
+ron = { optional = true, version = "0.1.7" }
 serde = { optional = true, version = "1.0", features = ["serde_derive"] }
-image = { optional = true, version = "0.18" }
-base64 = { optional = true, version = "0.6" }
-ron = { optional = true, version = "0.1.7" }
-cfg-if = "0.1.2"
+serde_json = { optional = true, version = "1.0" }
+smallvec = "0.6"
+thread_profiler = "0.1.1"
+time = "0.1"
+webrender_api = {path = "../webrender_api"}
+ws = { optional = true, version = "0.7.3" }
 
 [dependencies.pathfinder_font_renderer]
 git = "https://github.com/pcwalton/pathfinder"
 optional = true
 # Uncomment to test FreeType on macOS:
 # features = ["freetype"]
 
 [dependencies.pathfinder_gfx_utils]
--- a/gfx/webrender/res/debug_font.glsl
+++ b/gfx/webrender/res/debug_font.glsl
@@ -4,21 +4,21 @@
 
 #include shared,shared_other
 
 varying vec2 vColorTexCoord;
 varying vec4 vColor;
 
 #ifdef WR_VERTEX_SHADER
 in vec4 aColor;
-in vec4 aColorTexCoord;
+in vec2 aColorTexCoord;
 
 void main(void) {
     vColor = aColor;
-    vColorTexCoord = aColorTexCoord.xy;
+    vColorTexCoord = aColorTexCoord;
     vec4 pos = vec4(aPosition, 1.0);
     pos.xy = floor(pos.xy * uDevicePixelRatio + 0.5) / uDevicePixelRatio;
     gl_Position = uTransform * pos;
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 void main(void) {
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -39,16 +39,18 @@ impl FrameId {
 impl Add<usize> for FrameId {
     type Output = FrameId;
 
     fn add(self, other: usize) -> FrameId {
         FrameId(self.0 + other)
     }
 }
 
+const GL_FORMAT_RGBA: gl::GLuint = gl::RGBA;
+
 const GL_FORMAT_BGRA_GL: gl::GLuint = gl::BGRA;
 
 const GL_FORMAT_BGRA_GLES: gl::GLuint = gl::BGRA_EXT;
 
 const SHADER_VERSION_GL: &str = "#version 150\n";
 const SHADER_VERSION_GLES: &str = "#version 300 es\n";
 
 const SHADER_KIND_VERTEX: &str = "#define WR_VERTEX_SHADER\n";
@@ -128,21 +130,18 @@ pub fn get_gl_target(target: TextureTarg
     match target {
         TextureTarget::Default => gl::TEXTURE_2D,
         TextureTarget::Array => gl::TEXTURE_2D_ARRAY,
         TextureTarget::Rect => gl::TEXTURE_RECTANGLE,
         TextureTarget::External => gl::TEXTURE_EXTERNAL_OES,
     }
 }
 
-pub fn get_gl_format_bgra(gl: &gl::Gl) -> gl::GLuint {
-    match gl.get_type() {
-        gl::GlType::Gl => GL_FORMAT_BGRA_GL,
-        gl::GlType::Gles => GL_FORMAT_BGRA_GLES,
-    }
+fn supports_extension(extensions: &[String], extension: &str) -> bool {
+    extensions.iter().any(|s| s == extension)
 }
 
 fn get_shader_version(gl: &gl::Gl) -> &'static str {
     match gl.get_type() {
         gl::GlType::Gl => SHADER_VERSION_GL,
         gl::GlType::Gles => SHADER_VERSION_GLES,
     }
 }
@@ -670,16 +669,18 @@ pub struct Device {
 
     device_pixel_ratio: f32,
     upload_method: UploadMethod,
 
     // HW or API capabilities
     #[cfg(feature = "debug_renderer")]
     capabilities: Capabilities,
 
+    bgra_format: gl::GLuint,
+
     // debug
     inside_frame: bool,
 
     // resources
     resource_override_path: Option<PathBuf>,
 
     max_texture_size: u32,
     renderer_name: String,
@@ -696,39 +697,59 @@ pub struct Device {
 impl Device {
     pub fn new(
         gl: Rc<gl::Gl>,
         resource_override_path: Option<PathBuf>,
         upload_method: UploadMethod,
         _file_changed_handler: Box<FileWatcherHandler>,
         cached_programs: Option<Rc<ProgramCache>>,
     ) -> Device {
-        let max_texture_size = gl.get_integer_v(gl::MAX_TEXTURE_SIZE) as u32;
+        let mut max_texture_size = [0];
+        unsafe {
+            gl.get_integer_v(gl::MAX_TEXTURE_SIZE, &mut max_texture_size);
+        }
+        let max_texture_size = max_texture_size[0] as u32;
         let renderer_name = gl.get_string(gl::RENDERER);
 
+        let mut extension_count = [0];
+        unsafe {
+            gl.get_integer_v(gl::NUM_EXTENSIONS, &mut extension_count);
+        }
+        let extension_count = extension_count[0] as gl::GLuint;
         let mut extensions = Vec::new();
-        let extension_count = gl.get_integer_v(gl::NUM_EXTENSIONS) as gl::GLuint;
         for i in 0 .. extension_count {
             extensions.push(gl.get_string_i(gl::EXTENSIONS, i));
         }
 
+        let supports_bgra = supports_extension(&extensions, "GL_EXT_texture_format_BGRA8888");
+        let bgra_format = match gl.get_type() {
+            gl::GlType::Gl => GL_FORMAT_BGRA_GL,
+            gl::GlType::Gles => if supports_bgra {
+                GL_FORMAT_BGRA_GLES
+            } else {
+                GL_FORMAT_RGBA
+            }
+        };
+
         Device {
             gl,
             resource_override_path,
             // This is initialized to 1 by default, but it is reset
             // at the beginning of each frame in `Renderer::bind_frame_data`.
             device_pixel_ratio: 1.0,
             upload_method,
             inside_frame: false,
 
             #[cfg(feature = "debug_renderer")]
             capabilities: Capabilities {
                 supports_multisampling: false, //TODO
             },
 
+            bgra_format,
+
             bound_textures: [0; 16],
             bound_program: 0,
             bound_vao: 0,
             bound_read_fbo: FBOId(0),
             bound_draw_fbo: FBOId(0),
             program_mode_id: UniformLocation::INVALID,
             default_read_fbo: 0,
             default_draw_fbo: 0,
@@ -798,17 +819,21 @@ impl Device {
         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) {
+        let mut status = [0];
+        unsafe {
+            gl.get_shader_iv(id, gl::COMPILE_STATUS, &mut status);
+        }
+        if status[0] == 0 {
             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() {
                 warn!("Warnings detected on shader: {}\n{}", name, log);
             }
@@ -816,20 +841,26 @@ impl Device {
         }
     }
 
     pub fn begin_frame(&mut self) -> FrameId {
         debug_assert!(!self.inside_frame);
         self.inside_frame = true;
 
         // Retrieve the currently set FBO.
-        let default_read_fbo = self.gl.get_integer_v(gl::READ_FRAMEBUFFER_BINDING);
-        self.default_read_fbo = default_read_fbo as gl::GLuint;
-        let default_draw_fbo = self.gl.get_integer_v(gl::DRAW_FRAMEBUFFER_BINDING);
-        self.default_draw_fbo = default_draw_fbo as gl::GLuint;
+        let mut default_read_fbo = [0];
+        unsafe {
+            self.gl.get_integer_v(gl::READ_FRAMEBUFFER_BINDING, &mut default_read_fbo);
+        }
+        self.default_read_fbo = default_read_fbo[0] as gl::GLuint;
+        let mut default_draw_fbo = [0];
+        unsafe {
+            self.gl.get_integer_v(gl::DRAW_FRAMEBUFFER_BINDING, &mut default_draw_fbo);
+        }
+        self.default_draw_fbo = default_draw_fbo[0] as gl::GLuint;
 
         // Texture state
         for i in 0 .. self.bound_textures.len() {
             self.bound_textures[i] = 0;
             self.gl.active_texture(gl::TEXTURE0 + i as gl::GLuint);
             self.gl.bind_texture(gl::TEXTURE_2D, 0);
         }
 
@@ -1091,17 +1122,17 @@ impl Device {
         pixels: Option<&[T]>,
     ) {
         assert!(texture.layer_count > 0 || texture.width + texture.height == 0);
 
         let needed_layer_count = texture.layer_count - texture.fbo_ids.len() as i32;
         let allocate_color = needed_layer_count != 0 || is_resized || pixels.is_some();
 
         if allocate_color {
-            let desc = gl_describe_format(self.gl(), texture.format);
+            let desc = self.gl_describe_format(texture.format);
             match texture.target {
                 gl::TEXTURE_2D_ARRAY => {
                     self.gl.tex_image_3d(
                         texture.target,
                         0,
                         desc.internal,
                         texture.width as _,
                         texture.height as _,
@@ -1202,17 +1233,17 @@ impl Device {
                     depth_rb,
                 );
             }
             self.bind_external_draw_target(original_bound_fbo);
         }
     }
 
     fn update_texture_storage<T: Texel>(&mut self, texture: &Texture, pixels: Option<&[T]>) {
-        let desc = gl_describe_format(self.gl(), texture.format);
+        let desc = self.gl_describe_format(texture.format);
         match texture.target {
             gl::TEXTURE_2D_ARRAY => {
                 self.gl.tex_image_3d(
                     gl::TEXTURE_2D_ARRAY,
                     0,
                     desc.internal,
                     texture.width as _,
                     texture.height as _,
@@ -1292,17 +1323,17 @@ impl Device {
     pub fn free_texture_storage(&mut self, texture: &mut Texture) {
         debug_assert!(self.inside_frame);
 
         if texture.width + texture.height == 0 {
             return;
         }
 
         self.bind_texture(DEFAULT_TEXTURE, texture);
-        let desc = gl_describe_format(self.gl(), texture.format);
+        let desc = self.gl_describe_format(texture.format);
 
         self.free_texture_storage_impl(texture.target, desc);
 
         if let Some(RBOId(depth_rb)) = texture.depth_rb.take() {
             self.gl.delete_renderbuffers(&[depth_rb]);
         }
 
         if !texture.fbo_ids.is_empty() {
@@ -1374,17 +1405,21 @@ impl Device {
 
         let mut loaded = false;
 
         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 mut link_status = [0];
+                unsafe {
+                    self.gl.get_program_iv(pid, gl::LINK_STATUS, &mut link_status);
+                }
+                if link_status[0] == 0 {
                     let error_log = self.gl.get_program_info_log(pid);
                     error!(
                       "Failed to load a program object with a program binary: {} renderer {}\n{}",
                       base_filename,
                       self.renderer_name,
                       error_log
                     );
                 } else {
@@ -1436,17 +1471,21 @@ impl Device {
             // GL recommends detaching and deleting shaders once the link
             // is complete (whether successful or not). This allows the driver
             // 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 mut link_status = [0];
+            unsafe {
+                self.gl.get_program_iv(pid, gl::LINK_STATUS, &mut link_status);
+            }
+            if link_status[0] == 0 {
                 let error_log = self.gl.get_program_info_log(pid);
                 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));
@@ -1548,26 +1587,27 @@ impl Device {
                 }
                 Some(PixelBuffer::new(hint.to_gl(), upload_size))
             },
         };
 
         TextureUploader {
             target: UploadTarget {
                 gl: &*self.gl,
+                bgra_format: self.bgra_format,
                 texture,
             },
             buffer,
             marker: PhantomData,
         }
     }
 
     #[cfg(any(feature = "debug_renderer", feature = "capture"))]
     pub fn read_pixels(&mut self, img_desc: &ImageDescriptor) -> Vec<u8> {
-        let desc = gl_describe_format(self.gl(), img_desc.format);
+        let desc = self.gl_describe_format(img_desc.format);
         self.gl.read_pixels(
             0, 0,
             img_desc.width as i32,
             img_desc.height as i32,
             desc.external,
             desc.pixel_type,
         )
     }
@@ -1576,17 +1616,17 @@ impl Device {
     pub fn read_pixels_into(
         &mut self,
         rect: DeviceUintRect,
         format: ReadPixelsFormat,
         output: &mut [u8],
     ) {
         let (bytes_per_pixel, desc) = match format {
             ReadPixelsFormat::Standard(imf) => {
-                (imf.bytes_per_pixel(), gl_describe_format(self.gl(), imf))
+                (imf.bytes_per_pixel(), self.gl_describe_format(imf))
             }
             ReadPixelsFormat::Rgba8 => {
                 (4, FormatDesc {
                     external: gl::RGBA,
                     internal: gl::RGBA8 as _,
                     pixel_type: gl::UNSIGNED_BYTE,
                 })
             }
@@ -1610,17 +1650,17 @@ impl Device {
     #[cfg(feature = "debug_renderer")]
     pub fn get_tex_image_into(
         &mut self,
         texture: &Texture,
         format: ImageFormat,
         output: &mut [u8],
     ) {
         self.bind_texture(DEFAULT_TEXTURE, texture);
-        let desc = gl_describe_format(self.gl(), format);
+        let desc = self.gl_describe_format(format);
         self.gl.get_tex_image_into_buffer(
             texture.target,
             0,
             desc.external,
             desc.pixel_type,
             output,
         );
     }
@@ -1958,17 +1998,23 @@ impl Device {
         let mut clear_bits = 0;
 
         if let Some(color) = color {
             self.gl.clear_color(color[0], color[1], color[2], color[3]);
             clear_bits |= gl::COLOR_BUFFER_BIT;
         }
 
         if let Some(depth) = depth {
-            debug_assert_ne!(self.gl.get_boolean_v(gl::DEPTH_WRITEMASK), 0);
+            if cfg!(debug_assertions) {
+                let mut mask = [0];
+                unsafe {
+                    self.gl.get_boolean_v(gl::DEPTH_WRITEMASK, &mut mask);
+                }
+                assert_ne!(mask[0], 0);
+            }
             self.gl.clear_depth(depth as f64);
             clear_bits |= gl::DEPTH_BUFFER_BIT;
         }
 
         if clear_bits != 0 {
             match rect {
                 Some(rect) => {
                     self.gl.enable(gl::SCISSOR_TEST);
@@ -2097,17 +2143,17 @@ impl Device {
         self.gl.blend_equation(gl::FUNC_ADD);
     }
     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)
+        supports_extension(&self.extensions, 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,
@@ -2124,55 +2170,55 @@ impl Device {
                 gl::DEBUG_TYPE_PUSH_GROUP => "group push",
                 gl::DEBUG_TYPE_POP_GROUP => "group pop",
                 gl::DEBUG_TYPE_OTHER => "other",
                 _ => "?",
             };
             log!(level, "({}) {}", ty, msg.message);
         }
     }
+
+    fn gl_describe_format(&self, format: ImageFormat) -> FormatDesc {
+        match format {
+            ImageFormat::R8 => FormatDesc {
+                internal: gl::RED as _,
+                external: gl::RED,
+                pixel_type: gl::UNSIGNED_BYTE,
+            },
+            ImageFormat::BGRA8 => {
+                let external = self.bgra_format;
+                FormatDesc {
+                    internal: match self.gl.get_type() {
+                        gl::GlType::Gl => gl::RGBA as _,
+                        gl::GlType::Gles => external as _,
+                    },
+                    external,
+                    pixel_type: gl::UNSIGNED_BYTE,
+                }
+            },
+            ImageFormat::RGBAF32 => FormatDesc {
+                internal: gl::RGBA32F as _,
+                external: gl::RGBA,
+                pixel_type: gl::FLOAT,
+            },
+            ImageFormat::RG8 => FormatDesc {
+                internal: gl::RG8 as _,
+                external: gl::RG,
+                pixel_type: gl::UNSIGNED_BYTE,
+            },
+        }
+    }
 }
 
 struct FormatDesc {
     internal: gl::GLint,
     external: gl::GLuint,
     pixel_type: gl::GLuint,
 }
 
-fn gl_describe_format(gl: &gl::Gl, format: ImageFormat) -> FormatDesc {
-    match format {
-        ImageFormat::R8 => FormatDesc {
-            internal: gl::RED as _,
-            external: gl::RED,
-            pixel_type: gl::UNSIGNED_BYTE,
-        },
-        ImageFormat::BGRA8 => {
-            let external = get_gl_format_bgra(gl);
-            FormatDesc {
-                internal: match gl.get_type() {
-                    gl::GlType::Gl => gl::RGBA as _,
-                    gl::GlType::Gles => external as _,
-                },
-                external,
-                pixel_type: gl::UNSIGNED_BYTE,
-            }
-        },
-        ImageFormat::RGBAF32 => FormatDesc {
-            internal: gl::RGBA32F as _,
-            external: gl::RGBA,
-            pixel_type: gl::FLOAT,
-        },
-        ImageFormat::RG8 => FormatDesc {
-            internal: gl::RG8 as _,
-            external: gl::RG,
-            pixel_type: gl::UNSIGNED_BYTE,
-        },
-    }
-}
-
 struct UploadChunk {
     rect: DeviceUintRect,
     layer_index: i32,
     stride: Option<u32>,
     offset: usize,
 }
 
 struct PixelBuffer {
@@ -2194,16 +2240,17 @@ impl PixelBuffer {
             size_used: 0,
             chunks: SmallVec::new(),
         }
     }
 }
 
 struct UploadTarget<'a> {
     gl: &'a gl::Gl,
+    bgra_format: gl::GLuint,
     texture: &'a Texture,
 }
 
 pub struct TextureUploader<'a, T> {
     target: UploadTarget<'a>,
     buffer: Option<PixelBuffer>,
     marker: PhantomData<T>,
 }
@@ -2270,17 +2317,17 @@ impl<'a, T> TextureUploader<'a, T> {
         }
     }
 }
 
 impl<'a> UploadTarget<'a> {
     fn update_impl(&mut self, chunk: UploadChunk) {
         let (gl_format, bpp, data_type) = match self.texture.format {
             ImageFormat::R8 => (gl::RED, 1, gl::UNSIGNED_BYTE),
-            ImageFormat::BGRA8 => (get_gl_format_bgra(self.gl), 4, gl::UNSIGNED_BYTE),
+            ImageFormat::BGRA8 => (self.bgra_format, 4, gl::UNSIGNED_BYTE),
             ImageFormat::RG8 => (gl::RG, 2, gl::UNSIGNED_BYTE),
             ImageFormat::RGBAF32 => (gl::RGBA, 16, gl::FLOAT),
         };
 
         let row_length = match chunk.stride {
             Some(value) => value / bpp,
             None => self.texture.width,
         };
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -2,32 +2,32 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, ClipAndScrollInfo};
 use api::{ClipId, ColorF, ComplexClipRegion, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DevicePixelScale, DeviceUintRect, DisplayItemRef, Epoch, ExtendMode, ExternalScrollId};
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, GlyphRasterSpace, GradientStop};
-use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, LayoutPrimitiveInfo};
-use api::{LayoutRect, LayoutVector2D, LayoutSize, LayoutTransform};
-use api::{LineOrientation, LineStyle, LocalClip, PipelineId, PropertyBinding};
-use api::{RepeatMode, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity, Shadow};
-use api::{SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect, TileOffset};
-use api::{TransformStyle, YuvColorSpace, YuvData};
+use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint};
+use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
+use api::{LineOrientation, LineStyle, LocalClip, NinePatchBorderSource, PipelineId};
+use api::{PropertyBinding, RepeatMode, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity};
+use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
+use api::{TileOffset, TransformStyle, YuvColorSpace, YuvData};
 use app_units::Au;
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
 use clip_scroll_node::{ClipScrollNode, NodeType, StickyFrameInfo};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
 use euclid::{SideOffsets2D, vec2};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
-use image::{decompose_image, TiledImageInfo};
+use image::{decompose_image, TiledImageInfo, simplify_repeated_primitive};
 use internal_types::{FastHashMap, FastHashSet};
 use picture::PictureCompositeMode;
 use prim_store::{BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient};
 use prim_store::{CachedGradientIndex, EdgeAaSegmentMask, ImageCacheKey, ImagePrimitiveCpu, ImageSource};
 use prim_store::{BrushSegment, PictureIndex, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitiveCpu};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest, TiledImageMap};
@@ -1632,38 +1632,38 @@ impl<'a> DisplayListFlattener<'a> {
                 // Left
                 LayoutRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
                 // Right
                 LayoutRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
             ]
         };
 
         match border_item.details {
-            BorderDetails::Image(ref border) => {
+            BorderDetails::NinePatch(ref border) => {
                 // Calculate the modified rect as specific by border-image-outset
                 let origin = LayoutPoint::new(
                     rect.origin.x - border.outset.left,
                     rect.origin.y - border.outset.top,
                 );
                 let size = LayoutSize::new(
                     rect.size.width + border.outset.left + border.outset.right,
                     rect.size.height + border.outset.top + border.outset.bottom,
                 );
                 let rect = LayoutRect::new(origin, size);
 
                 // Calculate the local texel coords of the slices.
                 let px0 = 0.0;
-                let px1 = border.patch.slice.left as f32;
-                let px2 = border.patch.width as f32 - border.patch.slice.right as f32;
-                let px3 = border.patch.width as f32;
+                let px1 = border.slice.left as f32;
+                let px2 = border.width as f32 - border.slice.right as f32;
+                let px3 = border.width as f32;
 
                 let py0 = 0.0;
-                let py1 = border.patch.slice.top as f32;
-                let py2 = border.patch.height as f32 - border.patch.slice.bottom as f32;
-                let py3 = border.patch.height as f32;
+                let py1 = border.slice.top as f32;
+                let py2 = border.height as f32 - border.slice.bottom as f32;
+                let py3 = border.height as f32;
 
                 let tl_outer = LayoutPoint::new(rect.origin.x, rect.origin.y);
                 let tl_inner = tl_outer + vec2(border_item.widths.left, border_item.widths.top);
 
                 let tr_outer = LayoutPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
                 let tr_inner = tr_outer + vec2(-border_item.widths.right, border_item.widths.top);
 
                 let bl_outer = LayoutPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
@@ -1797,35 +1797,32 @@ impl<'a> DisplayListFlattener<'a> {
                     border.repeat_vertical,
                 );
 
                 let descriptor = BrushSegmentDescriptor {
                     segments,
                     clip_mask_kind: BrushClipMaskKind::Unknown,
                 };
 
-                let prim = BrushPrimitive::new(
-                    BrushKind::Border {
-                        request: ImageRequest {
-                            key: border.image_key,
-                            rendering: ImageRendering::Auto,
-                            tile: None,
-                        },
-                    },
-                    Some(descriptor),
-                );
+                let prim = PrimitiveContainer::Brush(match border.source {
+                    NinePatchBorderSource::Image(image_key) => {
+                        BrushPrimitive::new(
+                            BrushKind::Border {
+                                request: ImageRequest {
+                                    key: image_key,
+                                    rendering: ImageRendering::Auto,
+                                    tile: None,
+                                },
+                            },
+                            Some(descriptor),
+                        )
+                    }
+                });
 
-                let prim = PrimitiveContainer::Brush(prim);
-
-                self.add_primitive(
-                    clip_and_scroll,
-                    info,
-                    Vec::new(),
-                    prim,
-                );
+                self.add_primitive(clip_and_scroll, info, Vec::new(), prim);
             }
             BorderDetails::Normal(ref border) => {
                 self.add_normal_border(info, border, &border_item.widths, clip_and_scroll);
             }
             BorderDetails::Gradient(ref border) => for segment in create_segments(border.outset) {
                 let segment_rel = segment.origin - rect.origin;
                 let mut info = info.clone();
                 info.rect = segment;
@@ -1919,21 +1916,28 @@ impl<'a> DisplayListFlattener<'a> {
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         start_point: LayoutPoint,
         end_point: LayoutPoint,
         stops: ItemRange<GradientStop>,
         stops_count: usize,
         extend_mode: ExtendMode,
         stretch_size: LayoutSize,
-        tile_spacing: LayoutSize,
+        mut tile_spacing: LayoutSize,
     ) {
         let gradient_index = CachedGradientIndex(self.cached_gradients.len());
         self.cached_gradients.push(CachedGradient::new());
 
+        let mut prim_rect = info.rect;
+        simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
+        let info = LayoutPrimitiveInfo {
+            rect: prim_rect,
+            .. *info
+        };
+
         if tile_spacing != LayoutSize::zero() {
             let prim_infos = info.decompose(
                 stretch_size,
                 tile_spacing,
                 64 * 64,
             );
 
             if !prim_infos.is_empty() {
@@ -1952,17 +1956,17 @@ impl<'a> DisplayListFlattener<'a> {
                 }
 
                 return;
             }
         }
 
         self.add_gradient_impl(
             clip_and_scroll,
-            info,
+            &info,
             start_point,
             end_point,
             stops,
             stops_count,
             extend_mode,
             gradient_index,
             stretch_size,
         );
@@ -2009,21 +2013,28 @@ impl<'a> DisplayListFlattener<'a> {
         info: &LayoutPrimitiveInfo,
         center: LayoutPoint,
         start_radius: f32,
         end_radius: f32,
         ratio_xy: f32,
         stops: ItemRange<GradientStop>,
         extend_mode: ExtendMode,
         stretch_size: LayoutSize,
-        tile_spacing: LayoutSize,
+        mut tile_spacing: LayoutSize,
     ) {
         let gradient_index = CachedGradientIndex(self.cached_gradients.len());
         self.cached_gradients.push(CachedGradient::new());
 
+        let mut prim_rect = info.rect;
+        simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
+        let info = LayoutPrimitiveInfo {
+            rect: prim_rect,
+            .. *info
+        };
+
         if tile_spacing != LayoutSize::zero() {
             let prim_infos = info.decompose(
                 stretch_size,
                 tile_spacing,
                 64 * 64,
             );
 
             if !prim_infos.is_empty() {
@@ -2043,17 +2054,17 @@ impl<'a> DisplayListFlattener<'a> {
                 }
 
                 return;
             }
         }
 
         self.add_radial_gradient_impl(
             clip_and_scroll,
-            info,
+            &info,
             center,
             start_radius,
             end_radius,
             ratio_xy,
             stops,
             extend_mode,
             gradient_index,
             stretch_size,
@@ -2150,23 +2161,22 @@ impl<'a> DisplayListFlattener<'a> {
         stretch_size: LayoutSize,
         mut tile_spacing: LayoutSize,
         sub_rect: Option<TexelRect>,
         image_key: ImageKey,
         image_rendering: ImageRendering,
         alpha_type: AlphaType,
         tile_offset: Option<TileOffset>,
     ) {
-        // If the tile spacing is the same as the rect size,
-        // then it is effectively zero. We use this later on
-        // in prim_store to detect if an image can be considered
-        // opaque.
-        if tile_spacing == info.rect.size {
-            tile_spacing = LayoutSize::zero();
-        }
+        let mut prim_rect = info.rect;
+        simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
+        let info = LayoutPrimitiveInfo {
+            rect: prim_rect,
+            .. *info
+        };
 
         let request = ImageRequest {
             key: image_key,
             rendering: image_rendering,
             tile: tile_offset,
         };
 
         let sub_rect = sub_rect.map(|texel_rect| {
@@ -2179,35 +2189,34 @@ impl<'a> DisplayListFlattener<'a> {
                     (texel_rect.uv1.x - texel_rect.uv0.x) as i32,
                     (texel_rect.uv1.y - texel_rect.uv0.y) as i32,
                 ),
             )
         });
 
         // See if conditions are met to run through the new
         // image brush shader, which supports segments.
-        if tile_spacing == LayoutSize::zero() &&
-           tile_offset.is_none() {
+        if tile_offset.is_none() {
             let prim = BrushPrimitive::new(
                 BrushKind::Image {
                     request,
                     current_epoch: Epoch::invalid(),
                     alpha_type,
                     stretch_size,
                     tile_spacing,
                     source: ImageSource::Default,
                     sub_rect,
                     opacity_binding: OpacityBinding::new(),
                 },
                 None,
             );
 
             self.add_primitive(
                 clip_and_scroll,
-                info,
+                &info,
                 Vec::new(),
                 PrimitiveContainer::Brush(prim),
             );
         } else {
             let prim_cpu = ImagePrimitiveCpu {
                 tile_spacing,
                 alpha_type,
                 stretch_size,
@@ -2216,17 +2225,17 @@ impl<'a> DisplayListFlattener<'a> {
                 key: ImageCacheKey {
                     request,
                     texel_rect: sub_rect,
                 },
             };
 
             self.add_primitive(
                 clip_and_scroll,
-                info,
+                &info,
                 Vec::new(),
                 PrimitiveContainer::Image(prim_cpu),
             );
         }
     }
 
     pub fn add_yuv_image(
         &mut self,
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -246,17 +246,17 @@ impl HitTester {
             let mut clipped_in = false;
             for item in items.iter().rev() {
                 if !item.rect.contains(&point_in_layer) ||
                     !item.clip_rect.contains(&point_in_layer) {
                     continue;
                 }
 
                 let clip_chain_index = clip_and_scroll.clip_chain_index;
-                clipped_in |=
+                clipped_in = clipped_in ||
                     self.is_point_clipped_in_for_clip_chain(point, clip_chain_index, &mut test);
                 if !clipped_in {
                     break;
                 }
 
                 // We need to trigger a lookup against the root reference frame here, because
                 // items that are clipped by clip chains won't test against that part of the
                 // hierarchy. If we don't have a valid point for this test, we are likely
--- a/gfx/webrender/src/image.rs
+++ b/gfx/webrender/src/image.rs
@@ -1,15 +1,37 @@
 /* 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::{TileOffset, LayoutRect, LayoutSize, LayoutVector2D, DeviceUintSize};
 use euclid::rect;
 
+/// If repetitions are far enough apart that only one is within
+/// the primitive rect, then we can simplify the parameters and
+/// treat the primitive as not repeated.
+/// This can let us avoid unnecessary work later to handle some
+/// of the parameters.
+pub fn simplify_repeated_primitive(
+    stretch_size: &LayoutSize,
+    tile_spacing: &mut LayoutSize,
+    prim_rect: &mut LayoutRect,
+) {
+    let stride = *stretch_size + *tile_spacing;
+
+    if stride.width >= prim_rect.size.width {
+        tile_spacing.width = 0.0;
+        prim_rect.size.width = f32::min(prim_rect.size.width, stretch_size.width);
+    }
+    if stride.height >= prim_rect.size.height {
+        tile_spacing.height = 0.0;
+        prim_rect.size.height = f32::min(prim_rect.size.height, stretch_size.height);
+    }
+}
+
 pub struct DecomposedTile {
     pub rect: LayoutRect,
     pub stretch_size: LayoutSize,
     pub tile_offset: TileOffset,
 }
 
 pub struct TiledImageInfo {
     /// The bounds of the item in layout space.
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -38,26 +38,26 @@ they're nestable.
 [stacking_contexts]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
 [newframe]: ../webrender_api/struct.RenderApi.html#method.set_display_list
 [notifier]: renderer/struct.Renderer.html#method.set_render_notifier
 */
 
 #[macro_use]
 extern crate bitflags;
 #[macro_use]
+extern crate cfg_if;
+#[macro_use]
 extern crate lazy_static;
 #[macro_use]
 extern crate log;
-#[macro_use]
-extern crate thread_profiler;
-#[macro_use]
-extern crate cfg_if;
 #[cfg(any(feature = "debugger", feature = "capture", feature = "replay"))]
 #[macro_use]
 extern crate serde;
+#[macro_use]
+extern crate thread_profiler;
 
 mod batch;
 mod border;
 mod box_shadow;
 #[cfg(any(feature = "capture", feature = "replay"))]
 mod capture;
 mod clip;
 mod clip_scroll_node;
@@ -183,8 +183,9 @@ pub extern crate webrender_api;
 #[doc(hidden)]
 pub use device::{build_shader_strings, ProgramCache, ReadPixelsFormat, UploadMethod, VertexUsageHint};
 pub use renderer::{AsyncPropertySampler, CpuProfile, DebugFlags, OutputImageHandler, RendererKind};
 pub use renderer::{ExternalImage, ExternalImageHandler, ExternalImageSource, GpuProfile};
 pub use renderer::{GraphicsApi, GraphicsApiInfo, PipelineInfo, Renderer, RendererOptions};
 pub use renderer::{RendererStats, SceneBuilderHooks, ThreadListener};
 pub use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 pub use webrender_api as api;
+pub use resource_cache::intersect_for_tile;
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,17 +1,17 @@
 /* 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, BoxShadowClipMode, BuiltDisplayList, ClipMode, ColorF, ComplexClipRegion};
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch, ExtendMode, FontRenderMode};
 use api::{FilterOp, GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag};
 use api::{GlyphRasterSpace, LayoutPoint, LayoutRect, LayoutSize, LayoutToWorldTransform, LayoutVector2D};
-use api::{PipelineId, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat};
+use api::{PipelineId, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat, DeviceIntSideOffsets};
 use border::{BorderCornerInstance, BorderEdgeKind};
 use box_shadow::BLUR_SAMPLE_SCALE;
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
 use clip::{ClipSourcesHandle, ClipWorkItem};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveRunContext;
@@ -448,20 +448,25 @@ impl BrushPrimitive {
                     local_rect.size.width,
                     local_rect.size.height,
                     0.0,
                     0.0,
                 ]);
             }
             // Images are drawn as a white color, modulated by the total
             // opacity coming from any collapsed property bindings.
-            BrushKind::Image { stretch_size, ref opacity_binding, .. } => {
+            BrushKind::Image { stretch_size, tile_spacing, ref opacity_binding, .. } => {
                 request.push(ColorF::new(1.0, 1.0, 1.0, opacity_binding.current).premultiplied());
                 request.push(PremultipliedColorF::WHITE);
-                request.push([stretch_size.width, stretch_size.height, 0.0, 0.0]);
+                request.push([
+                    stretch_size.width + tile_spacing.width,
+                    stretch_size.height + tile_spacing.height,
+                    0.0,
+                    0.0,
+                ]);
             }
             // Solid rects also support opacity collapsing.
             BrushKind::Solid { color, ref opacity_binding, .. } => {
                 request.push(color.scale_alpha(opacity_binding.current).premultiplied());
             }
             BrushKind::Clear => {
                 // Opaque black with operator dest out
                 request.push(PremultipliedColorF::BLACK);
@@ -1556,17 +1561,26 @@ impl PrimitiveStore {
                         );
                     }
                 }
             }
             PrimitiveKind::Brush => {
                 let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0];
 
                 match brush.kind {
-                    BrushKind::Image { request, sub_rect, ref mut current_epoch, ref mut source, ref mut opacity_binding, .. } => {
+                    BrushKind::Image {
+                        request,
+                        sub_rect,
+                        stretch_size,
+                        ref mut tile_spacing,
+                        ref mut current_epoch,
+                        ref mut source,
+                        ref mut opacity_binding,
+                        ..
+                    } => {
                         let image_properties = frame_state
                             .resource_cache
                             .get_image_properties(request.key);
 
                         // Set if we need to request the source image from the cache this frame.
                         if let Some(image_properties) = image_properties {
                             *current_epoch = image_properties.epoch;
 
@@ -1577,16 +1591,27 @@ impl PrimitiveStore {
                             }
 
                             // Update opacity for this primitive to ensure the correct
                             // batching parameters are used.
                             metadata.opacity.is_opaque =
                                 image_properties.descriptor.is_opaque &&
                                 opacity_binding.current == 1.0;
 
+                            if *tile_spacing != LayoutSize::zero() {
+                                *source = ImageSource::Cache {
+                                    // Size in device-pixels we need to allocate in render task cache.
+                                    size: DeviceIntSize::new(
+                                        image_properties.descriptor.width as i32,
+                                        image_properties.descriptor.height as i32
+                                    ),
+                                    handle: None,
+                                };
+                            }
+
                             // Work out whether this image is a normal / simple type, or if
                             // we need to pre-render it to the render task cache.
                             if let Some(rect) = sub_rect {
                                 *source = ImageSource::Cache {
                                     // Size in device-pixels we need to allocate in render task cache.
                                     size: rect.size,
                                     handle: None,
                                 };
@@ -1594,51 +1619,67 @@ impl PrimitiveStore {
 
                             let mut request_source_image = false;
 
                             // Every frame, for cached items, we need to request the render
                             // task cache item. The closure will be invoked on the first
                             // time through, and any time the render task output has been
                             // evicted from the texture cache.
                             match *source {
-                                ImageSource::Cache { size, ref mut handle } => {
+                                ImageSource::Cache { ref mut size, ref mut handle } => {
+                                    let padding = DeviceIntSideOffsets::new(
+                                        0,
+                                        (tile_spacing.width * size.width as f32 / stretch_size.width) as i32,
+                                        (tile_spacing.height * size.height as f32 / stretch_size.height) as i32,
+                                        0,
+                                    );
+
+                                    let inner_size = *size;
+                                    size.width += padding.horizontal();
+                                    size.height += padding.vertical();
+
+                                    if padding != DeviceIntSideOffsets::zero() {
+                                        metadata.opacity.is_opaque = false;
+                                    }
+
                                     let image_cache_key = ImageCacheKey {
                                         request,
                                         texel_rect: sub_rect,
                                     };
 
                                     // Request a pre-rendered image task.
                                     *handle = Some(frame_state.resource_cache.request_render_task(
                                         RenderTaskCacheKey {
-                                            size,
+                                            size: *size,
                                             kind: RenderTaskCacheKeyKind::Image(image_cache_key),
                                         },
                                         frame_state.gpu_cache,
                                         frame_state.render_tasks,
                                         None,
                                         image_properties.descriptor.is_opaque,
                                         |render_tasks| {
                                             // We need to render the image cache this frame,
                                             // so will need access to the source texture.
                                             request_source_image = true;
 
                                             // Create a task to blit from the texture cache to
                                             // a normal transient render task surface. This will
                                             // copy only the sub-rect, if specified.
-                                            let cache_to_target_task = RenderTask::new_blit(
-                                                size,
+                                            let cache_to_target_task = RenderTask::new_blit_with_padding(
+                                                inner_size,
+                                                &padding,
                                                 BlitSource::Image { key: image_cache_key },
                                             );
                                             let cache_to_target_task_id = render_tasks.add(cache_to_target_task);
 
                                             // Create a task to blit the rect from the child render
                                             // task above back into the right spot in the persistent
                                             // render target cache.
                                             let target_to_cache_task = RenderTask::new_blit(
-                                                size,
+                                                *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.
                                             pic_state.tasks.push(target_to_cache_task_id);
@@ -2290,24 +2331,19 @@ impl PrimitiveStore {
                 //warn!("invalid primitive rect {:?}", metadata.local_rect);
                 return None;
             }
 
             // Inflate the local rect for this primitive by the inflation factor of
             // the picture context. This ensures that even if the primitive itself
             // is not visible, any effects from the blur radius will be correctly
             // taken into account.
-            let local_rect = metadata
-                .local_rect
+            let local_rect = metadata.local_rect
                 .inflate(pic_context.inflation_factor, pic_context.inflation_factor)
-                .intersection(&metadata.local_clip_rect);
-            let local_rect = match local_rect {
-                Some(local_rect) => local_rect,
-                None => return None,
-            };
+                .intersection(&metadata.local_clip_rect)?;
 
             let screen_bounding_rect = calculate_screen_bounding_rect(
                 &prim_run_context.scroll_node.world_content_transform,
                 &local_rect,
                 frame_context.device_pixel_scale,
             );
 
             metadata.screen_rect = screen_bounding_rect
@@ -2400,26 +2436,27 @@ impl PrimitiveStore {
             }
 
             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 = pic_context.original_reference_frame_index
+            let original_relative_transform = pic_context
+                .original_reference_frame_index
                 .and_then(|original_reference_frame_index| {
-                    let parent = frame_context
+                    frame_context
                         .clip_scroll_tree
                         .nodes[original_reference_frame_index.0]
-                        .world_content_transform;
-                    parent.inverse()
-                        .map(|inv_parent| {
-                            inv_parent.pre_mul(&scroll_node.world_content_transform)
-                        })
+                        .world_content_transform
+                        .inverse()
+                })
+                .map(|inv_parent| {
+                    inv_parent.pre_mul(&scroll_node.world_content_transform)
                 });
 
             let clip_chain_rect = if pic_context.apply_local_clip_rect {
                 get_local_clip_rect_for_nodes(scroll_node, clip_chain)
             } else {
                 None
             };
 
@@ -2446,27 +2483,34 @@ impl PrimitiveStore {
                     &child_prim_run_context,
                     pic_context,
                     pic_state,
                     frame_context,
                     frame_state,
                 ) {
                     frame_state.profile_counters.visible_primitives.inc();
 
+                    let clipped_rect = match clip_chain_rect {
+                        Some(ref chain_rect) => match prim_local_rect.intersection(chain_rect) {
+                            Some(rect) => rect,
+                            None => continue,
+                        },
+                        None => prim_local_rect,
+                    };
+
+                    if let Some(ref matrix) = parent_relative_transform {
+                        let bounds = matrix.transform_rect(&clipped_rect);
+                        result.local_rect_in_actual_parent_space =
+                            result.local_rect_in_actual_parent_space.union(&bounds);
+                    }
                     if let Some(ref matrix) = original_relative_transform {
-                        let bounds = matrix.transform_rect(&prim_local_rect);
+                        let bounds = matrix.transform_rect(&clipped_rect);
                         result.local_rect_in_original_parent_space =
                             result.local_rect_in_original_parent_space.union(&bounds);
                     }
-
-                    if let Some(ref matrix) = parent_relative_transform {
-                        let bounds = matrix.transform_rect(&prim_local_rect);
-                        result.local_rect_in_actual_parent_space =
-                            result.local_rect_in_actual_parent_space.union(&bounds);
-                    }
                 }
             }
         }
 
         result
     }
 }
 
@@ -2526,35 +2570,36 @@ fn convert_clip_chain_to_clip_vector(
         })
         .collect()
 }
 
 fn get_local_clip_rect_for_nodes(
     scroll_node: &ClipScrollNode,
     clip_chain: &ClipChain,
 ) -> Option<LayoutRect> {
-    let local_rect = ClipChainNodeIter { current: clip_chain.nodes.clone() }.fold(
-        None,
-        |combined_local_clip_rect: Option<LayoutRect>, node| {
-            if node.work_item.coordinate_system_id != scroll_node.coordinate_system_id {
-                return combined_local_clip_rect;
-            }
+    ClipChainNodeIter { current: clip_chain.nodes.clone() }
+        .fold(
+            None,
+            |combined_local_clip_rect: Option<LayoutRect>, node| {
+                if node.work_item.coordinate_system_id != scroll_node.coordinate_system_id {
+                    return combined_local_clip_rect;
+                }
 
-            Some(match combined_local_clip_rect {
-                Some(combined_rect) =>
-                    combined_rect.intersection(&node.local_clip_rect).unwrap_or_else(LayoutRect::zero),
-                None => node.local_clip_rect,
-            })
-        }
-    );
-
-    match local_rect {
-        Some(local_rect) => scroll_node.coordinate_system_relative_transform.unapply(&local_rect),
-        None => None,
-    }
+                Some(match combined_local_clip_rect {
+                    Some(combined_rect) =>
+                        combined_rect
+                            .intersection(&node.local_clip_rect)
+                            .unwrap_or_else(LayoutRect::zero),
+                    None => node.local_clip_rect,
+                })
+            }
+        )
+        .and_then(|local_rect| {
+            scroll_node.coordinate_system_relative_transform.unapply(&local_rect)
+        })
 }
 
 impl<'a> GpuDataRequest<'a> {
     // Write the GPU cache data for an individual segment.
     fn write_segment(
         &mut self,
         local_rect: LayoutRect,
         extra_data: [f32; 4],
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,13 +1,13 @@
 /* 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::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceSize, ImageDescriptor, ImageFormat};
+use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceSize, DeviceIntSideOffsets, ImageDescriptor, ImageFormat};
 #[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};
@@ -249,16 +249,17 @@ pub enum BlitSource {
     },
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BlitTask {
     pub source: BlitSource,
+    pub padding: DeviceIntSideOffsets,
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskData {
     pub data: [f32; FLOATS_PER_RENDER_TASK_INFO],
 }
@@ -333,32 +334,44 @@ impl RenderTask {
             saved_index: None,
         }
     }
 
     pub fn new_blit(
         size: DeviceIntSize,
         source: BlitSource,
     ) -> Self {
+        RenderTask::new_blit_with_padding(size, &DeviceIntSideOffsets::zero(), source)
+    }
+
+    pub fn new_blit_with_padding(
+        mut size: DeviceIntSize,
+        padding: &DeviceIntSideOffsets,
+        source: BlitSource,
+    ) -> Self {
         let mut children = Vec::new();
 
         // If this blit uses a render task as a source,
         // ensure it's added as a child task. This will
         // ensure it gets allocated in the correct pass
         // and made available as an input when this task
         // executes.
         if let BlitSource::RenderTask { task_id } = source {
             children.push(task_id);
         }
 
+        size.width += padding.horizontal();
+        size.height += padding.vertical();
+
         RenderTask {
             children,
             location: RenderTaskLocation::Dynamic(None, Some(size)),
             kind: RenderTaskKind::Blit(BlitTask {
                 source,
+                padding: *padding,
             }),
             clear_mode: ClearMode::Transparent,
             saved_index: None,
         }
     }
 
     pub fn new_mask(
         outer_rect: DeviceIntRect,
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -144,17 +144,17 @@ struct CachedImageInfo {
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ResourceClassCache<K: Hash + Eq, V, U: Default> {
     resources: FastHashMap<K, V>,
     pub user_data: U,
 }
 
-fn intersect_for_tile(
+pub fn intersect_for_tile(
     dirty: DeviceUintRect,
     width: u32,
     height: u32,
     tile_size: TileSize,
     tile_offset: TileOffset,
 
 ) -> Option<DeviceUintRect> {
         dirty.intersection(&DeviceUintRect::new(
@@ -631,33 +631,32 @@ impl ResourceCache {
             return;
         }
 
         // We can start a worker thread rasterizing right now, if:
         //  - The image is a blob.
         //  - The blob hasn't already been requested this frame.
         if self.pending_image_requests.insert(request) && template.data.is_blob() {
             if let Some(ref mut renderer) = self.blob_image_renderer {
-                let mut dirty_rect = template.dirty_rect;
                 let (offset, w, h) = match template.tiling {
                     Some(tile_size) => {
                         let tile_offset = request.tile.unwrap();
                         let (w, h) = compute_tile_size(
                             &template.descriptor,
                             tile_size,
                             tile_offset,
                         );
                         let offset = DevicePoint::new(
                             tile_offset.x as f32 * tile_size as f32,
                             tile_offset.y as f32 * tile_size as f32,
                         );
 
-                        if let Some(dirty) = dirty_rect {
-                            dirty_rect = intersect_for_tile(dirty, w, h, tile_size, tile_offset);
-                            if dirty_rect.is_none() {
+                        if let Some(dirty) = template.dirty_rect {
+                            if intersect_for_tile(dirty, w, h, tile_size, tile_offset).is_none() {
+                                // don't bother requesting unchanged tiles
                                 return
                             }
                         }
 
                         (offset, w, h)
                     }
                     None => (
                         DevicePoint::zero(),
@@ -670,17 +669,17 @@ impl ResourceCache {
                     &self.resources,
                     request.into(),
                     &BlobImageDescriptor {
                         width: w,
                         height: h,
                         offset,
                         format: template.descriptor.format,
                     },
-                    dirty_rect,
+                    template.dirty_rect,
                 );
             }
         }
     }
 
     pub fn request_glyphs(
         &mut self,
         mut font: FontInstance,
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -479,17 +479,17 @@ impl RenderTarget for ColorRenderTarget 
                         // the allocated destination rect within this target.
                         let (target_rect, _) = task.get_target_rect();
                         self.blits.push(BlitJob {
                             source: BlitJobSource::Texture(
                                 cache_item.texture_id,
                                 cache_item.texture_layer,
                                 source_rect,
                             ),
-                            target_rect,
+                            target_rect: target_rect.inner_rect(task_info.padding)
                         });
                     }
                     BlitSource::RenderTask { .. } => {
                         panic!("BUG: render task blit jobs to render tasks not supported");
                     }
                 }
             }
         }
@@ -668,17 +668,17 @@ impl TextureCacheRenderTarget {
                         // is undefined behavior.
                         panic!("bug: a single blit cannot be to/from texture cache");
                     }
                     BlitSource::RenderTask { task_id } => {
                         // Add a blit job to copy from an existing render
                         // task to this target.
                         self.blits.push(BlitJob {
                             source: BlitJobSource::RenderTask(task_id),
-                            target_rect: target_rect.0,
+                            target_rect: target_rect.0.inner_rect(task_info.padding),
                         });
                     }
                 }
             }
             RenderTaskKind::Glyph(ref mut task_info) => {
                 self.add_glyph_task(task_info, target_rect.0)
             }
             RenderTaskKind::VerticalBlur(..) |
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -263,52 +263,77 @@ pub struct NormalBorder {
 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
 pub enum RepeatMode {
     Stretch,
     Repeat,
     Round,
     Space,
 }
 
-#[repr(C)]
-#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
-pub struct NinePatchDescriptor {
-    pub width: u32,
-    pub height: u32,
-    pub slice: SideOffsets2D<u32>,
-}
-
-#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
-pub struct ImageBorder {
-    pub image_key: ImageKey,
-    pub patch: NinePatchDescriptor,
-    /// Controls whether the center of the 9 patch image is
-    /// rendered or ignored.
-    pub fill: bool,
-    pub outset: SideOffsets2D<f32>,
-    pub repeat_horizontal: RepeatMode,
-    pub repeat_vertical: RepeatMode,
-}
-
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct GradientBorder {
     pub gradient: Gradient,
     pub outset: SideOffsets2D<f32>,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct RadialGradientBorder {
     pub gradient: RadialGradient,
     pub outset: SideOffsets2D<f32>,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+/// TODO(mrobinson): Currently only images are supported, but we will
+/// eventually add support for Gradient and RadialGradient.
+pub enum NinePatchBorderSource {
+    Image(ImageKey),
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct NinePatchBorder {
+    /// Describes what to use as the 9-patch source image. If this is an image,
+    /// it will be stretched to fill the size given by width x height.
+    pub source: NinePatchBorderSource,
+
+    /// The width of the 9-part image.
+    pub width: u32,
+
+    /// The height of the 9-part image.
+    pub height: u32,
+
+    /// Distances from each edge where the image should be sliced up. These
+    /// values are in 9-part-image space (the same space as width and height),
+    /// and the resulting image parts will be used to fill the corresponding
+    /// parts of the border as given by the border widths. This can lead to
+    /// stretching.
+    /// Slices can be overlapping. In that case, the same pixels from the
+    /// 9-part image will show up in multiple parts of the resulting border.
+    pub slice: SideOffsets2D<u32>,
+
+    /// Controls whether the center of the 9 patch image is rendered or
+    /// ignored. The center is never rendered if the slices are overlapping.
+    pub fill: bool,
+
+    /// Determines what happens if the horizontal side parts of the 9-part
+    /// image have a different size than the horizontal parts of the border.
+    pub repeat_horizontal: RepeatMode,
+
+    /// Determines what happens if the vertical side parts of the 9-part
+    /// image have a different size than the vertical parts of the border.
+    pub repeat_vertical: RepeatMode,
+
+    /// The outset for the border.
+    /// TODO(mrobinson): This should be removed and handled by the client.
+    pub outset: SideOffsets2D<f32>,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum BorderDetails {
     Normal(NormalBorder),
-    Image(ImageBorder),
+    NinePatch(NinePatchBorder),
     Gradient(GradientBorder),
     RadialGradient(RadialGradientBorder),
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct BorderDisplayItem {
     pub widths: BorderWidths,
     pub details: BorderDetails,
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -231,45 +231,56 @@ impl<'a> BuiltDisplayListIter<'a> {
         }
 
         // Don't let these bleed into another item
         self.cur_stops = ItemRange::default();
         self.cur_complex_clip = (ItemRange::default(), 0);
         self.cur_clip_chain_items = ItemRange::default();
 
         loop {
-            if self.data.is_empty() {
-                return None;
+            self.next_raw()?;
+            if let SetGradientStops = self.cur_item.item {
+                // SetGradientStops is a dummy item that most consumers should ignore
+                continue;
             }
+            break;
+        }
 
-            {
-                let reader = bincode::IoReader::new(UnsafeReader::new(&mut self.data));
-                bincode::deserialize_in_place(reader, &mut self.cur_item)
-                    .expect("MEH: malicious process?");
-            }
+        Some(self.as_ref())
+    }
 
-            match self.cur_item.item {
-                SetGradientStops => {
-                    self.cur_stops = skip_slice::<GradientStop>(self.list, &mut self.data).0;
+    /// Gets the next display item, even if it's a dummy. Also doesn't handle peeking
+    /// and may leave irrelevant ranges live (so a Clip may have GradientStops if
+    /// for some reason you ask).
+    pub fn next_raw<'b>(&'b mut self) -> Option<DisplayItemRef<'a, 'b>> {
+        use SpecificDisplayItem::*;
 
-                    // This is a dummy item, skip over it
-                    continue;
-                }
-                ClipChain(_) => {
-                    self.cur_clip_chain_items = skip_slice::<ClipId>(self.list, &mut self.data).0;
-                }
-                Clip(_) | ScrollFrame(_) => {
-                    self.cur_complex_clip = self.skip_slice::<ComplexClipRegion>()
-                }
-                Text(_) => self.cur_glyphs = self.skip_slice::<GlyphInstance>().0,
-                PushStackingContext(_) => self.cur_filters = self.skip_slice::<FilterOp>().0,
-                _ => { /* do nothing */ }
+        if self.data.is_empty() {
+            return None;
+        }
+
+        {
+            let reader = bincode::IoReader::new(UnsafeReader::new(&mut self.data));
+            bincode::deserialize_in_place(reader, &mut self.cur_item)
+                .expect("MEH: malicious process?");
+        }
+
+        match self.cur_item.item {
+            SetGradientStops => {
+                self.cur_stops = skip_slice::<GradientStop>(self.list, &mut self.data).0;
             }
-
-            break;
+            ClipChain(_) => {
+                self.cur_clip_chain_items = skip_slice::<ClipId>(self.list, &mut self.data).0;
+            }
+            Clip(_) | ScrollFrame(_) => {
+                self.cur_complex_clip = self.skip_slice::<ComplexClipRegion>()
+            }
+            Text(_) => self.cur_glyphs = self.skip_slice::<GlyphInstance>().0,
+            PushStackingContext(_) => self.cur_filters = self.skip_slice::<FilterOp>().0,
+            _ => { /* do nothing */ }
         }
 
         Some(self.as_ref())
     }
 
     fn skip_slice<T: for<'de> Deserialize<'de>>(&mut self) -> (ItemRange<T>, usize) {
         skip_slice::<T>(self.list, &mut self.data)
     }
@@ -429,17 +440,17 @@ impl<'a, T: for<'de> Deserialize<'de>> :
 #[cfg(feature = "serialize")]
 impl Serialize for BuiltDisplayList {
     fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
         use display_item::CompletelySpecificDisplayItem::*;
         use display_item::GenericDisplayItem;
 
         let mut seq = serializer.serialize_seq(None)?;
         let mut traversal = self.iter();
-        while let Some(item) = traversal.next() {
+        while let Some(item) = traversal.next_raw() {
             let display_item = item.display_item();
             let serial_di = GenericDisplayItem {
                 item: match display_item.item {
                     SpecificDisplayItem::Clip(v) => Clip(
                         v,
                         item.iter.list.get(item.iter.cur_complex_clip.0).collect()
                     ),
                     SpecificDisplayItem::ClipChain(v) => ClipChain(
@@ -881,17 +892,17 @@ impl DisplayListBuilder {
     }
 
     pub fn print_display_list(&mut self) {
         let mut temp = BuiltDisplayList::default();
         mem::swap(&mut temp.data, &mut self.data);
 
         {
             let mut iter = BuiltDisplayListIter::new(&temp);
-            while let Some(item) = iter.next() {
+            while let Some(item) = iter.next_raw() {
                 println!("{:?}", item.display_item());
             }
         }
 
         self.data = temp.data;
     }
 
     fn push_item(&mut self, item: SpecificDisplayItem, info: &LayoutPrimitiveInfo) {
--- a/gfx/webrender_api/src/units.rs
+++ b/gfx/webrender_api/src/units.rs
@@ -9,27 +9,28 @@
 //! don't have the device pixel ratio applied which means they are agnostic to the usage
 //! of hidpi screens and the like.
 //!
 //! The terms "layer" and "stacking context" can be used interchangeably
 //! in the context of coordinate systems.
 
 use app_units::Au;
 use euclid::{Length, TypedRect, TypedScale, TypedSize2D, TypedTransform3D};
-use euclid::{TypedPoint2D, TypedPoint3D, TypedVector2D, TypedVector3D};
+use euclid::{TypedPoint2D, TypedPoint3D, TypedVector2D, TypedVector3D, TypedSideOffsets2D};
 
 /// Geometry in the coordinate system of the render target (screen or intermediate
 /// surface) in physical pixels.
 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct DevicePixel;
 
 pub type DeviceIntRect = TypedRect<i32, DevicePixel>;
 pub type DeviceIntPoint = TypedPoint2D<i32, DevicePixel>;
 pub type DeviceIntSize = TypedSize2D<i32, DevicePixel>;
 pub type DeviceIntLength = Length<i32, DevicePixel>;
+pub type DeviceIntSideOffsets = TypedSideOffsets2D<i32, DevicePixel>;
 
 pub type DeviceUintRect = TypedRect<u32, DevicePixel>;
 pub type DeviceUintPoint = TypedPoint2D<u32, DevicePixel>;
 pub type DeviceUintSize = TypedSize2D<u32, DevicePixel>;
 
 pub type DeviceRect = TypedRect<f32, DevicePixel>;
 pub type DevicePoint = TypedPoint2D<f32, DevicePixel>;
 pub type DeviceVector2D = TypedVector2D<f32, DevicePixel>;
--- 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.32"
+gleam = "0.5"
 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 @@
-8da531dc2cd77a4dadbfe632b04e76454f51ac9f
+9a3bc6b965554c04c0bba326cdee45240c3b4ba7
--- a/gfx/wrench/Cargo.toml
+++ b/gfx/wrench/Cargo.toml
@@ -6,17 +6,17 @@ build = "build.rs"
 license = "MPL-2.0"
 
 [dependencies]
 base64 = "0.6"
 bincode = "1.0"
 byteorder = "1.0"
 env_logger = { version = "0.5", optional = true }
 euclid = "0.17"
-gleam = "0.4"
+gleam = "0.5"
 glutin = "0.13"
 app_units = "0.6"
 image = "0.18"
 clap = { version = "2", features = ["yaml"] }
 lazy_static = "1"
 log = "0.4"
 yaml-rust = { git = "https://github.com/vvuk/yaml-rust", features = ["preserve_order"] }
 serde_json = "1.0"
--- a/gfx/wrench/src/blob.rs
+++ b/gfx/wrench/src/blob.rs
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // A very basic BlobImageRenderer that can only render a checkerboard pattern.
 
 use std::collections::HashMap;
 use std::sync::Arc;
 use std::sync::Mutex;
 use webrender::api::*;
+use webrender::intersect_for_tile;
 
 // Serialize/deserialize the blob.
 
 pub fn serialize_blob(color: ColorU) -> Vec<u8> {
     vec![color.r, color.g, color.b, color.a]
 }
 
 fn deserialize_blob(blob: &[u8]) -> Result<ColorU, ()> {
@@ -24,34 +25,41 @@ fn deserialize_blob(blob: &[u8]) -> Resu
     };
 }
 
 // This is the function that applies the deserialized drawing commands and generates
 // actual image data.
 fn render_blob(
     color: ColorU,
     descriptor: &BlobImageDescriptor,
-    tile: Option<TileOffset>,
+    tile: Option<(TileSize, TileOffset)>,
     dirty_rect: Option<DeviceUintRect>,
 ) -> BlobImageResult {
     // Allocate storage for the result. Right now the resource cache expects the
     // tiles to have have no stride or offset.
     let mut texels = vec![0u8; (descriptor.width * descriptor.height * descriptor.format.bytes_per_pixel()) as usize];
 
     // Generate a per-tile pattern to see it in the demo. For a real use case it would not
     // make sense for the rendered content to depend on its tile.
     let tile_checker = match tile {
-        Some(tile) => (tile.x % 2 == 0) != (tile.y % 2 == 0),
+        Some((_, tile)) => (tile.x % 2 == 0) != (tile.y % 2 == 0),
         None => true,
     };
 
-    let dirty_rect = dirty_rect.unwrap_or(DeviceUintRect::new(
+    let mut dirty_rect = dirty_rect.unwrap_or(DeviceUintRect::new(
         DeviceUintPoint::new(0, 0),
         DeviceUintSize::new(descriptor.width, descriptor.height)));
 
+    if let Some((tile_size, tile)) = tile {
+        dirty_rect = intersect_for_tile(dirty_rect, tile_size as u32, tile_size as u32,
+                                        tile_size, tile)
+            .expect("empty rects should be culled by webrender");
+    }
+
+
     for y in dirty_rect.min_y() .. dirty_rect.max_y() {
         for x in dirty_rect.min_x() .. dirty_rect.max_x() {
             // Apply the tile's offset. This is important: all drawing commands should be
             // translated by this offset to give correct results with tiled blob images.
             let x2 = x + descriptor.offset.x as u32;
             let y2 = y + descriptor.offset.y as u32;
 
             // Render a simple checkerboard pattern
@@ -96,17 +104,17 @@ pub struct BlobCallbacks {
 
 impl BlobCallbacks {
     pub fn new() -> Self {
         BlobCallbacks { request: Box::new(|_|()), resolve: Box::new(|| (())) }
     }
 }
 
 pub struct CheckerboardRenderer {
-    image_cmds: HashMap<ImageKey, ColorU>,
+    image_cmds: HashMap<ImageKey, (ColorU, Option<TileSize>)>,
     callbacks: Arc<Mutex<BlobCallbacks>>,
 
     // The images rendered in the current frame (not kept here between frames).
     rendered_images: HashMap<BlobImageRequest, BlobImageResult>,
 }
 
 impl CheckerboardRenderer {
     pub fn new(callbacks: Arc<Mutex<BlobCallbacks>>) -> Self {
@@ -114,26 +122,25 @@ impl CheckerboardRenderer {
             callbacks,
             image_cmds: HashMap::new(),
             rendered_images: HashMap::new(),
         }
     }
 }
 
 impl BlobImageRenderer for CheckerboardRenderer {
-    fn add(&mut self, key: ImageKey, cmds: BlobImageData, _: Option<TileSize>) {
+    fn add(&mut self, key: ImageKey, cmds: BlobImageData, tile_size: Option<TileSize>) {
         self.image_cmds
-            .insert(key, deserialize_blob(&cmds[..]).unwrap());
+            .insert(key, (deserialize_blob(&cmds[..]).unwrap(), tile_size));
     }
 
     fn update(&mut self, key: ImageKey, cmds: BlobImageData, _dirty_rect: Option<DeviceUintRect>) {
         // Here, updating is just replacing the current version of the commands with
         // the new one (no incremental updates).
-        self.image_cmds
-            .insert(key, deserialize_blob(&cmds[..]).unwrap());
+        self.image_cmds.get_mut(&key).unwrap().0 = deserialize_blob(&cmds[..]).unwrap();
     }
 
     fn delete(&mut self, key: ImageKey) {
         self.image_cmds.remove(&key);
     }
 
     fn request(
         &mut self,
@@ -144,19 +151,21 @@ impl BlobImageRenderer for CheckerboardR
     ) {
         (self.callbacks.lock().unwrap().request)(&request);
         assert!(!self.rendered_images.contains_key(&request));
         // This method is where we kick off our rendering jobs.
         // It should avoid doing work on the calling thread as much as possible.
         // In this example we will use the thread pool to render individual tiles.
 
         // Gather the input data to send to a worker thread.
-        let cmds = self.image_cmds.get(&request.key).unwrap();
+        let &(color, tile_size) = self.image_cmds.get(&request.key).unwrap();
 
-        let result = render_blob(*cmds, descriptor, request.tile, dirty_rect);
+        let tile = request.tile.map(|tile| (tile_size.unwrap(), tile));
+
+        let result = render_blob(color, descriptor, tile, dirty_rect);
 
         self.rendered_images.insert(request, result);
     }
 
     fn resolve(&mut self, request: BlobImageRequest) -> BlobImageResult {
         (self.callbacks.lock().unwrap().resolve)();
         self.rendered_images.remove(&request).unwrap()
     }
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -896,27 +896,25 @@ impl YamlFrameReader {
                         .expect("border must have repeat-vertical")
                     {
                         "stretch" => RepeatMode::Stretch,
                         "repeat" => RepeatMode::Repeat,
                         "round" => RepeatMode::Round,
                         "space" => RepeatMode::Space,
                         s => panic!("Unknown box border image repeat mode {}", s),
                     };
-                    Some(BorderDetails::Image(ImageBorder {
-                        image_key,
-                        patch: NinePatchDescriptor {
-                            width: image_width as u32,
-                            height: image_height as u32,
-                            slice: SideOffsets2D::new(slice[0], slice[1], slice[2], slice[3]),
-                        },
+                    Some(BorderDetails::NinePatch(NinePatchBorder {
+                        source: NinePatchBorderSource::Image(image_key),
+                        width: image_width as u32,
+                        height: image_height as u32,
+                        slice: SideOffsets2D::new(slice[0], slice[1], slice[2], slice[3]),
                         fill,
-                        outset: SideOffsets2D::new(outset[0], outset[1], outset[2], outset[3]),
                         repeat_horizontal,
                         repeat_vertical,
+                        outset: SideOffsets2D::new(outset[0], outset[1], outset[2], outset[3]),
                     }))
                 }
                 "gradient" => {
                     let gradient = self.to_gradient(dl, item);
                     let outset = item["outset"]
                         .as_vec_f32()
                         .expect("borders must have outset");
                     let outset = broadcast(&outset, 4);
--- a/gfx/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wrench/src/yaml_frame_writer.rs
@@ -860,41 +860,47 @@ impl YamlFrameWriter {
                             yaml_node(&mut v, "width", f32_vec_yaml(&widths, true));
                             str_node(&mut v, "border-type", "normal");
                             yaml_node(&mut v, "color", string_vec_yaml(&colors, true));
                             yaml_node(&mut v, "style", string_vec_yaml(&styles, true));
                             if let Some(radius_node) = maybe_radius_yaml(&details.radius) {
                                 yaml_node(&mut v, "radius", radius_node);
                             }
                         }
-                        BorderDetails::Image(ref details) => {
+                        BorderDetails::NinePatch(ref details) => {
                             let widths: Vec<f32> = vec![
                                 item.widths.top,
                                 item.widths.right,
                                 item.widths.bottom,
                                 item.widths.left,
                             ];
                             let outset: Vec<f32> = vec![
                                 details.outset.top,
                                 details.outset.right,
                                 details.outset.bottom,
                                 details.outset.left,
                             ];
                             yaml_node(&mut v, "width", f32_vec_yaml(&widths, true));
                             str_node(&mut v, "border-type", "image");
-                            if let Some(path) = self.path_for_image(details.image_key) {
-                                path_node(&mut v, "image", &path);
+
+                            match details.source {
+                                NinePatchBorderSource::Image(image_key) => {
+                                    if let Some(path) = self.path_for_image(image_key) {
+                                        path_node(&mut v, "image", &path);
+                                    }
+                                }
                             }
-                            u32_node(&mut v, "image-width", details.patch.width);
-                            u32_node(&mut v, "image-height", details.patch.height);
+
+                            u32_node(&mut v, "image-width", details.width);
+                            u32_node(&mut v, "image-height", details.height);
                             let slice: Vec<u32> = vec![
-                                details.patch.slice.top,
-                                details.patch.slice.right,
-                                details.patch.slice.bottom,
-                                details.patch.slice.left,
+                                details.slice.top,
+                                details.slice.right,
+                                details.slice.bottom,
+                                details.slice.left,
                             ];
                             yaml_node(&mut v, "slice", u32_vec_yaml(&slice, true));
                             yaml_node(&mut v, "outset", f32_vec_yaml(&outset, true));
                             match details.repeat_horizontal {
                                 RepeatMode::Stretch => {
                                     str_node(&mut v, "repeat-horizontal", "stretch")
                                 }
                                 RepeatMode::Repeat => {