Bug 1463416 - Update webrender to cset 63c71ca9bbe4dec0ebc9c9bc8ab65b06a6b40641. r=Gankro
authorKartikaya Gupta <kgupta@mozilla.com>
Sat, 26 May 2018 11:09:20 -0400
changeset 420138 b2e3cbd5aecaa2e88368b3c926eeb4b2b735a66c
parent 420137 e70e3d46d44190f66bc61b979c334424606578de
child 420139 52e2d7efe7211e4d3e190b22ce5d057a8fbcec18
push id103731
push useraciure@mozilla.com
push dateMon, 28 May 2018 21:59:58 +0000
treeherdermozilla-inbound@c58d8cbdc302 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGankro
bugs1463416
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 1463416 - Update webrender to cset 63c71ca9bbe4dec0ebc9c9bc8ab65b06a6b40641. r=Gankro MozReview-Commit-ID: 21gmlBlV8En
gfx/webrender/examples/alpha_perf.rs
gfx/webrender/examples/animation.rs
gfx/webrender/examples/basic.rs
gfx/webrender/examples/blob.rs
gfx/webrender/examples/common/boilerplate.rs
gfx/webrender/examples/document.rs
gfx/webrender/examples/frame_output.rs
gfx/webrender/examples/iframe.rs
gfx/webrender/examples/image_resize.rs
gfx/webrender/examples/multiwindow.rs
gfx/webrender/examples/scrolling.rs
gfx/webrender/examples/texture_cache_stress.rs
gfx/webrender/examples/yuv.rs
gfx/webrender/res/brush_image.glsl
gfx/webrender/res/clip_shared.glsl
gfx/webrender/res/cs_border_segment.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_text_run.glsl
gfx/webrender/res/snap.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/border.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/glyph_cache.rs
gfx/webrender/src/glyph_rasterizer/mod.rs
gfx/webrender/src/glyph_rasterizer/no_pathfinder.rs
gfx/webrender/src/glyph_rasterizer/pathfinder.rs
gfx/webrender/src/gpu_types.rs
gfx/webrender/src/platform/macos/font.rs
gfx/webrender/src/platform/unix/font.rs
gfx/webrender/src/platform/windows/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/scene_builder.rs
gfx/webrender/src/shade.rs
gfx/webrender/src/tiling.rs
gfx/webrender_api/Cargo.toml
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/font.rs
gfx/webrender_api/src/image.rs
gfx/webrender_api/src/units.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/src/json_frame_writer.rs
gfx/wrench/src/rawtest.rs
gfx/wrench/src/ron_frame_writer.rs
gfx/wrench/src/wrench.rs
gfx/wrench/src/yaml_frame_reader.rs
gfx/wrench/src/yaml_frame_writer.rs
--- a/gfx/webrender/examples/alpha_perf.rs
+++ b/gfx/webrender/examples/alpha_perf.rs
@@ -18,17 +18,17 @@ struct App {
     rect_count: usize,
 }
 
 impl Example for App {
     fn render(
         &mut self,
         _api: &RenderApi,
         builder: &mut DisplayListBuilder,
-        _resources: &mut ResourceUpdates,
+        _txn: &mut Transaction,
         _framebuffer_size: DeviceUintSize,
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = (0, 0).to(1920, 1080);
         let info = LayoutPrimitiveInfo::new(bounds);
 
         builder.push_stacking_context(
--- a/gfx/webrender/examples/animation.rs
+++ b/gfx/webrender/examples/animation.rs
@@ -29,17 +29,17 @@ struct App {
     opacity: f32,
 }
 
 impl Example for App {
     fn render(
         &mut self,
         _api: &RenderApi,
         builder: &mut DisplayListBuilder,
-        _resources: &mut ResourceUpdates,
+        _txn: &mut Transaction,
         _framebuffer_size: DeviceUintSize,
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         // Create a 200x200 stacking context with an animated transform property.
         let bounds = (0, 0).to(200, 200);
 
         let filters = vec![
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -177,17 +177,17 @@ struct App {
 impl Example for App {
     // Make this the only example to test all shaders for compile errors.
     const PRECACHE_SHADERS: bool = true;
 
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
-        resources: &mut ResourceUpdates,
+        txn: &mut Transaction,
         _: DeviceUintSize,
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
@@ -196,17 +196,17 @@ impl Example for App {
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
         let image_mask_key = api.generate_image_key();
-        resources.add_image(
+        txn.add_image(
             image_mask_key,
             ImageDescriptor::new(2, 2, ImageFormat::R8, true, false),
             ImageData::new(vec![0, 80, 180, 255]),
             None,
         );
         let mask = ImageMask {
             image: image_mask_key,
             rect: (75, 75).by(100, 100),
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -11,17 +11,17 @@ extern crate webrender;
 mod boilerplate;
 
 use boilerplate::{Example, HandyDandyRectBuilder};
 use rayon::{ThreadPool, ThreadPoolBuilder};
 use std::collections::HashMap;
 use std::collections::hash_map::Entry;
 use std::sync::Arc;
 use std::sync::mpsc::{Receiver, Sender, channel};
-use webrender::api::{self, DisplayListBuilder, DocumentId, PipelineId, RenderApi, ResourceUpdates};
+use webrender::api::{self, DisplayListBuilder, DocumentId, PipelineId, RenderApi, Transaction};
 
 // This example shows how to implement a very basic BlobImageRenderer that can only render
 // a checkerboard pattern.
 
 // The deserialized command list internally used by this example is just a color.
 type ImageRenderingCommands = api::ColorU;
 
 // Serialize/deserialize the blob.
@@ -219,31 +219,31 @@ impl api::BlobImageRenderer for Checkerb
 
 struct App {}
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
-        resources: &mut ResourceUpdates,
+        txn: &mut Transaction,
         _framebuffer_size: api::DeviceUintSize,
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let blob_img1 = api.generate_image_key();
-        resources.add_image(
+        txn.add_image(
             blob_img1,
             api::ImageDescriptor::new(500, 500, api::ImageFormat::BGRA8, true, false),
             api::ImageData::new_blob_image(serialize_blob(api::ColorU::new(50, 50, 150, 255))),
             Some(128),
         );
 
         let blob_img2 = api.generate_image_key();
-        resources.add_image(
+        txn.add_image(
             blob_img2,
             api::ImageDescriptor::new(200, 200, api::ImageFormat::BGRA8, true, false),
             api::ImageData::new_blob_image(serialize_blob(api::ColorU::new(50, 150, 50, 255))),
             None,
         );
 
         let bounds = api::LayoutRect::new(api::LayoutPoint::zero(), builder.content_size());
         let info = api::LayoutPrimitiveInfo::new(bounds);
--- a/gfx/webrender/examples/common/boilerplate.rs
+++ b/gfx/webrender/examples/common/boilerplate.rs
@@ -66,17 +66,17 @@ pub trait Example {
     const PRECACHE_SHADERS: bool = false;
     const WIDTH: u32 = 1920;
     const HEIGHT: u32 = 1080;
 
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
-        resources: &mut ResourceUpdates,
+        txn: &mut Transaction,
         framebuffer_size: DeviceUintSize,
         pipeline_id: PipelineId,
         document_id: DocumentId,
     );
     fn on_event(&mut self, glutin::WindowEvent, &RenderApi, DocumentId) -> bool {
         false
     }
     fn get_image_handlers(
@@ -164,35 +164,33 @@ pub fn main_wrapper<E: Example>(
     if let Some(external_image_handler) = external {
         renderer.set_external_image_handler(external_image_handler);
     }
 
     let epoch = Epoch(0);
     let pipeline_id = PipelineId(0, 0);
     let layout_size = framebuffer_size.to_f32() / euclid::TypedScale::new(device_pixel_ratio);
     let mut builder = DisplayListBuilder::new(pipeline_id, layout_size);
-    let mut resources = ResourceUpdates::new();
+    let mut txn = Transaction::new();
 
     example.render(
         &api,
         &mut builder,
-        &mut resources,
+        &mut txn,
         framebuffer_size,
         pipeline_id,
         document_id,
     );
-    let mut txn = Transaction::new();
     txn.set_display_list(
         epoch,
         None,
         layout_size,
         builder.finalize(),
         true,
     );
-    txn.update_resources(resources);
     txn.set_root_pipeline(pipeline_id);
     txn.generate_frame();
     api.send_transaction(document_id, txn);
 
     println!("Entering event loop");
     events_loop.run_forever(|global_event| {
         let mut txn = Transaction::new();
         let mut custom_event = true;
@@ -246,34 +244,32 @@ pub fn main_wrapper<E: Example>(
                 },
             },
             glutin::Event::WindowEvent { event, .. } => custom_event = example.on_event(event, &api, document_id),
             _ => return glutin::ControlFlow::Continue,
         };
 
         if custom_event {
             let mut builder = DisplayListBuilder::new(pipeline_id, layout_size);
-            let mut resources = ResourceUpdates::new();
 
             example.render(
                 &api,
                 &mut builder,
-                &mut resources,
+                &mut txn,
                 framebuffer_size,
                 pipeline_id,
                 document_id,
             );
             txn.set_display_list(
                 epoch,
                 None,
                 layout_size,
                 builder.finalize(),
                 true,
             );
-            txn.update_resources(resources);
             txn.generate_frame();
         }
         api.send_transaction(document_id, txn);
 
         renderer.update();
         renderer.render(framebuffer_size).unwrap();
         let _ = renderer.flush_pipeline_info();
         example.draw_custom(&*gl);
--- a/gfx/webrender/examples/document.rs
+++ b/gfx/webrender/examples/document.rs
@@ -82,17 +82,17 @@ impl App {
     }
 }
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         base_builder: &mut DisplayListBuilder,
-        _: &mut ResourceUpdates,
+        _txn: &mut Transaction,
         framebuffer_size: DeviceUintSize,
         _: PipelineId,
         _: DocumentId,
     ) {
         if self.documents.is_empty() {
             let device_pixel_ratio = framebuffer_size.width as f32 /
                 base_builder.content_size().width;
             // this is the first run, hack around the boilerplate,
--- a/gfx/webrender/examples/frame_output.rs
+++ b/gfx/webrender/examples/frame_output.rs
@@ -62,18 +62,18 @@ impl App {
     fn init_output_document(
         &mut self,
         api: &RenderApi,
         framebuffer_size: DeviceUintSize,
         device_pixel_ratio: f32,
     ) {
         // Generate the external image key that will be used to render the output document to the root document.
         self.external_image_key = Some(api.generate_image_key());
-        let mut resources = ResourceUpdates::new();
-        resources.add_image(
+        let mut txn = Transaction::new();
+        txn.add_image(
             self.external_image_key.unwrap(),
             ImageDescriptor::new(100, 100, ImageFormat::BGRA8, true, false),
             ImageData::External(ExternalImageData {
                 id: ExternalImageId(0),
                 channel_index: 0,
                 image_type: ExternalImageType::TextureHandle(TextureTarget::Default),
             }),
             None,
@@ -107,20 +107,18 @@ impl App {
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
         builder.push_rect(&info, ColorF::new(1.0, 1.0, 0.0, 1.0));
         builder.pop_stacking_context();
 
-        let mut txn = Transaction::new();
         txn.set_root_pipeline(pipeline_id);
         txn.enable_frame_output(document.pipeline_id, true);
-        txn.update_resources(resources);
         txn.set_display_list(
             Epoch(0),
             Some(document.color),
             document.content_rect.size,
             builder.finalize(),
             true,
         );
         txn.generate_frame();
@@ -129,17 +127,17 @@ impl App {
     }
 }
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
-        _resources: &mut ResourceUpdates,
+        _txn: &mut Transaction,
         framebuffer_size: DeviceUintSize,
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         if self.output_document.is_none() {
             let device_pixel_ratio = framebuffer_size.width as f32 /
                 builder.content_size().width;
             self.init_output_document(api, DeviceUintSize::new(200, 200), device_pixel_ratio);
--- a/gfx/webrender/examples/iframe.rs
+++ b/gfx/webrender/examples/iframe.rs
@@ -18,17 +18,17 @@ use webrender::api::*;
 
 struct App {}
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
-        _resources: &mut ResourceUpdates,
+        _txn: &mut Transaction,
         _framebuffer_size: DeviceUintSize,
         pipeline_id: PipelineId,
         document_id: DocumentId,
     ) {
         // All the sub_* things are for the nested pipeline
         let sub_size = DeviceUintSize::new(100, 100);
         let sub_bounds = (0, 0).to(sub_size.width as i32, sub_size.height as i32);
 
--- a/gfx/webrender/examples/image_resize.rs
+++ b/gfx/webrender/examples/image_resize.rs
@@ -18,23 +18,23 @@ struct App {
     image_key: ImageKey,
 }
 
 impl Example for App {
     fn render(
         &mut self,
         _api: &RenderApi,
         builder: &mut DisplayListBuilder,
-        resources: &mut ResourceUpdates,
+        txn: &mut Transaction,
         _framebuffer_size: DeviceUintSize,
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let (image_descriptor, image_data) = image_helper::make_checkerboard(32, 32);
-        resources.add_image(
+        txn.add_image(
             self.image_key,
             image_descriptor,
             image_data,
             None,
         );
 
         let bounds = (0, 0).to(512, 512);
         let info = LayoutPrimitiveInfo::new(bounds);
@@ -94,25 +94,24 @@ impl Example for App {
                 for y in 0 .. 64 {
                     for x in 0 .. 64 {
                         let r = 255 * ((y & 32) == 0) as u8;
                         let g = 255 * ((x & 32) == 0) as u8;
                         image_data.extend_from_slice(&[0, g, r, 0xff]);
                     }
                 }
 
-                let mut updates = ResourceUpdates::new();
-                updates.update_image(
+                let mut txn = Transaction::new();
+                txn.update_image(
                     self.image_key,
                     ImageDescriptor::new(64, 64, ImageFormat::BGRA8, true, false),
                     ImageData::new(image_data),
                     None,
                 );
                 let mut txn = Transaction::new();
-                txn.update_resources(updates);
                 txn.generate_frame();
                 api.send_transaction(document_id, txn);
             }
             _ => {}
         }
 
         false
     }
--- a/gfx/webrender/examples/multiwindow.rs
+++ b/gfx/webrender/examples/multiwindow.rs
@@ -97,27 +97,25 @@ impl Window {
         };
         let notifier = Box::new(Notifier::new(events_loop.create_proxy()));
         let (renderer, sender) = webrender::Renderer::new(gl.clone(), notifier, opts).unwrap();
         let api = sender.create_api();
         let document_id = api.add_document(framebuffer_size, 0);
 
         let epoch = Epoch(0);
         let pipeline_id = PipelineId(0, 0);
-        let mut resources = ResourceUpdates::new();
+        let mut txn = Transaction::new();
 
         let font_key = api.generate_font_key();
         let font_bytes = load_file("../wrench/reftests/text/FreeSans.ttf");
-        resources.add_raw_font(font_key, font_bytes, 0);
+        txn.add_raw_font(font_key, font_bytes, 0);
 
         let font_instance_key = api.generate_font_instance_key();
-        resources.add_font_instance(font_instance_key, font_key, Au::from_px(32), None, None, Vec::new());
+        txn.add_font_instance(font_instance_key, font_key, Au::from_px(32), None, None, Vec::new());
 
-        let mut txn = Transaction::new();
-        txn.update_resources(resources);
         api.send_transaction(document_id, txn);
 
         Window {
             events_loop,
             window,
             renderer,
             name,
             epoch,
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -18,17 +18,17 @@ struct App {
     cursor_position: WorldPoint,
 }
 
 impl Example for App {
     fn render(
         &mut self,
         _api: &RenderApi,
         builder: &mut DisplayListBuilder,
-        _resources: &mut ResourceUpdates,
+        _txn: &mut Transaction,
         _framebuffer_size: DeviceUintSize,
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let info = LayoutPrimitiveInfo::new(
             LayoutRect::new(LayoutPoint::zero(), builder.content_size())
         );
         builder.push_stacking_context(
--- a/gfx/webrender/examples/texture_cache_stress.rs
+++ b/gfx/webrender/examples/texture_cache_stress.rs
@@ -78,17 +78,17 @@ struct App {
     swap_index: usize,
 }
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
-        resources: &mut ResourceUpdates,
+        txn: &mut Transaction,
         _framebuffer_size: DeviceUintSize,
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = (0, 0).to(512, 512);
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
@@ -105,25 +105,25 @@ impl Example for App {
         let y0 = 50.0;
         let image_size = LayoutSize::new(4.0, 4.0);
 
         if self.swap_keys.is_empty() {
             let key0 = api.generate_image_key();
             let key1 = api.generate_image_key();
 
             self.image_generator.generate_image(128);
-            resources.add_image(
+            txn.add_image(
                 key0,
                 ImageDescriptor::new(128, 128, ImageFormat::BGRA8, true, false),
                 ImageData::new(self.image_generator.take()),
                 None,
             );
 
             self.image_generator.generate_image(128);
-            resources.add_image(
+            txn.add_image(
                 key1,
                 ImageDescriptor::new(128, 128, ImageFormat::BGRA8, true, false),
                 ImageData::new(self.image_generator.take()),
                 None,
             );
 
             self.swap_keys.push(key0);
             self.swap_keys.push(key1);
@@ -195,100 +195,100 @@ impl Example for App {
             glutin::WindowEvent::KeyboardInput {
                 input: glutin::KeyboardInput {
                     state: glutin::ElementState::Pressed,
                     virtual_keycode: Some(key),
                     ..
                 },
                 ..
             } => {
-                let mut updates = ResourceUpdates::new();
+                let mut txn = Transaction::new();
 
                 match key {
                     glutin::VirtualKeyCode::S => {
                         self.stress_keys.clear();
 
                         for _ in 0 .. 16 {
                             for _ in 0 .. 16 {
                                 let size = 4;
 
                                 let image_key = api.generate_image_key();
 
                                 self.image_generator.generate_image(size);
 
-                                updates.add_image(
+                                txn.add_image(
                                     image_key,
                                     ImageDescriptor::new(size, size, ImageFormat::BGRA8, true, false),
                                     ImageData::new(self.image_generator.take()),
                                     None,
                                 );
 
                                 self.stress_keys.push(image_key);
                             }
                         }
                     }
                     glutin::VirtualKeyCode::D => if let Some(image_key) = self.image_key.take() {
-                        updates.delete_image(image_key);
+                        txn.delete_image(image_key);
                     },
                     glutin::VirtualKeyCode::U => if let Some(image_key) = self.image_key {
                         let size = 128;
                         self.image_generator.generate_image(size);
 
-                        updates.update_image(
+                        txn.update_image(
                             image_key,
                             ImageDescriptor::new(size, size, ImageFormat::BGRA8, true, false),
                             ImageData::new(self.image_generator.take()),
                             None,
                         );
                     },
                     glutin::VirtualKeyCode::E => {
                         if let Some(image_key) = self.image_key.take() {
-                            updates.delete_image(image_key);
+                            txn.delete_image(image_key);
                         }
 
                         let size = 32;
                         let image_key = api.generate_image_key();
 
                         let image_data = ExternalImageData {
                             id: ExternalImageId(0),
                             channel_index: size as u8,
                             image_type: ExternalImageType::Buffer,
                         };
 
-                        updates.add_image(
+                        txn.add_image(
                             image_key,
                             ImageDescriptor::new(size, size, ImageFormat::BGRA8, true, false),
                             ImageData::External(image_data),
                             None,
                         );
 
                         self.image_key = Some(image_key);
                     }
                     glutin::VirtualKeyCode::R => {
                         if let Some(image_key) = self.image_key.take() {
-                            updates.delete_image(image_key);
+                            txn.delete_image(image_key);
                         }
 
                         let image_key = api.generate_image_key();
                         let size = 32;
                         self.image_generator.generate_image(size);
 
-                        updates.add_image(
+                        txn.add_image(
                             image_key,
                             ImageDescriptor::new(size, size, ImageFormat::BGRA8, true, false),
                             ImageData::new(self.image_generator.take()),
                             None,
                         );
 
                         self.image_key = Some(image_key);
                     }
                     _ => {}
                 }
 
-                api.update_resources(updates);
+                api.update_resources(txn.resource_updates);
                 return true;
             }
             _ => {}
         }
 
         false
     }
 
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -73,17 +73,17 @@ impl webrender::ExternalImageHandler for
 struct App {
 }
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
-        resources: &mut ResourceUpdates,
+        txn: &mut Transaction,
         _framebuffer_size: DeviceUintSize,
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
@@ -95,53 +95,53 @@ impl Example for App {
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
         let yuv_chanel1 = api.generate_image_key();
         let yuv_chanel2 = api.generate_image_key();
         let yuv_chanel2_1 = api.generate_image_key();
         let yuv_chanel3 = api.generate_image_key();
-        resources.add_image(
+        txn.add_image(
             yuv_chanel1,
             ImageDescriptor::new(100, 100, ImageFormat::R8, true, false),
             ImageData::External(ExternalImageData {
                 id: ExternalImageId(0),
                 channel_index: 0,
                 image_type: ExternalImageType::TextureHandle(
                     TextureTarget::Default,
                 ),
             }),
             None,
         );
-        resources.add_image(
+        txn.add_image(
             yuv_chanel2,
             ImageDescriptor::new(100, 100, ImageFormat::RG8, true, false),
             ImageData::External(ExternalImageData {
                 id: ExternalImageId(1),
                 channel_index: 0,
                 image_type: ExternalImageType::TextureHandle(
                     TextureTarget::Default,
                 ),
             }),
             None,
         );
-        resources.add_image(
+        txn.add_image(
             yuv_chanel2_1,
             ImageDescriptor::new(100, 100, ImageFormat::R8, true, false),
             ImageData::External(ExternalImageData {
                 id: ExternalImageId(2),
                 channel_index: 0,
                 image_type: ExternalImageType::TextureHandle(
                     TextureTarget::Default,
                 ),
             }),
             None,
         );
-        resources.add_image(
+        txn.add_image(
             yuv_chanel3,
             ImageDescriptor::new(100, 100, ImageFormat::R8, true, false),
             ImageData::External(ExternalImageData {
                 id: ExternalImageId(3),
                 channel_index: 0,
                 image_type: ExternalImageType::TextureHandle(
                     TextureTarget::Default,
                 ),
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -43,17 +43,17 @@ ImageBrushData fetch_image_data(int addr
 }
 
 #ifdef WR_FEATURE_ALPHA_PASS
 vec2 transform_point_snapped(
     vec2 local_pos,
     RectWithSize local_rect,
     mat4 transform
 ) {
-    vec2 snap_offset = compute_snap_offset(local_pos, transform, local_rect);
+    vec2 snap_offset = compute_snap_offset(local_pos, transform, local_rect, vec2(0.5));
     vec4 world_pos = transform * vec4(local_pos, 0.0, 1.0);
     vec2 device_pos = world_pos.xy / world_pos.w * uDevicePixelRatio;
 
     return device_pos + snap_offset;
 }
 #endif
 
 void brush_vs(
--- a/gfx/webrender/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -62,17 +62,18 @@ ClipVertexInfo write_clip_tile_vertex(Re
             local_clip_rect
         );
 
         vec2 snap_offsets = compute_snap_offset_impl(
             device_pos,
             scroll_node.transform,
             local_clip_rect,
             RectWithSize(snap_positions.xy, snap_positions.zw - snap_positions.xy),
-            snap_positions
+            snap_positions,
+            vec2(0.5)
         );
 
         actual_pos -= snap_offsets;
     }
 
     vec4 node_pos;
 
     // Select the local position, based on whether we are rasterizing this
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/cs_border_segment.glsl
@@ -0,0 +1,127 @@
+/* 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/. */
+
+#include shared,prim_shared,ellipse,shared_border
+
+flat varying vec4 vColor0;
+flat varying vec4 vColor1;
+flat varying vec4 vColorLine;
+flat varying int vFeatures;
+flat varying vec2 vClipCenter;
+flat varying vec4 vClipRadii;
+flat varying vec2 vClipSign;
+
+varying vec2 vPos;
+
+#define CLIP_RADII      1
+#define MIX_COLOR       2
+
+#ifdef WR_VERTEX_SHADER
+
+in vec2 aTaskOrigin;
+in vec4 aRect;
+in vec4 aColor0;
+in vec4 aColor1;
+in int aFlags;
+in vec2 aWidths;
+in vec2 aRadii;
+
+#define SEGMENT_TOP_LEFT        0
+#define SEGMENT_TOP_RIGHT       1
+#define SEGMENT_BOTTOM_RIGHT    2
+#define SEGMENT_BOTTOM_LEFT     3
+#define SEGMENT_LEFT            4
+#define SEGMENT_TOP             5
+#define SEGMENT_RIGHT           6
+#define SEGMENT_BOTTOM          7
+
+vec2 get_outer_corner_scale(int segment) {
+    vec2 p;
+
+    switch (segment) {
+        case SEGMENT_TOP_LEFT:
+            p = vec2(0.0, 0.0);
+            break;
+        case SEGMENT_TOP_RIGHT:
+            p = vec2(1.0, 0.0);
+            break;
+        case SEGMENT_BOTTOM_RIGHT:
+            p = vec2(1.0, 1.0);
+            break;
+        case SEGMENT_BOTTOM_LEFT:
+            p = vec2(0.0, 1.0);
+            break;
+        default:
+            // Should never get hit
+            p = vec2(0.0);
+            break;
+    }
+
+    return p;
+}
+
+void main(void) {
+    vec2 pos = aRect.xy + aRect.zw * aPosition.xy;
+
+    int segment = aFlags & 0xff;
+    int style = (aFlags >> 8) & 0xff;
+
+    vec2 outer_scale = get_outer_corner_scale(segment);
+    vec2 outer = aRect.xy + outer_scale * aRect.zw;
+    vec2 clip_sign = 1.0 - 2.0 * outer_scale;
+
+    vColor0 = aColor0;
+    vColor1 = aColor1;
+    vPos = pos;
+
+    vFeatures = 0;
+    vClipSign = clip_sign;
+    vClipCenter = outer + clip_sign * aRadii;
+    vClipRadii = vec4(aRadii, aRadii - aWidths);
+    vColorLine = vec4(0.0);
+
+    switch (segment) {
+        case SEGMENT_TOP_LEFT:
+        case SEGMENT_TOP_RIGHT:
+        case SEGMENT_BOTTOM_RIGHT:
+        case SEGMENT_BOTTOM_LEFT:
+            vFeatures |= (CLIP_RADII | MIX_COLOR);
+            vColorLine = vec4(outer, aWidths.y * -clip_sign.y, aWidths.x * clip_sign.x);
+            break;
+        default:
+            break;
+    }
+
+    gl_Position = uTransform * vec4(aTaskOrigin + pos, 0.0, 1.0);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+    float aa_range = compute_aa_range(vPos);
+    float d = -1.0;
+
+    // Apply color mix
+    float mix_factor = 0.0;
+    if ((vFeatures & MIX_COLOR) != 0) {
+        float d_line = distance_to_line(vColorLine.xy, vColorLine.zw, vPos);
+        mix_factor = distance_aa(aa_range, -d_line);
+    }
+
+    // Apply clip radii
+    if ((vFeatures & CLIP_RADII) != 0) {
+        vec2 p = vPos - vClipCenter;
+        if (vClipSign.x * p.x < 0.0 && vClipSign.y * p.y < 0.0) {
+            float d_radii_a = distance_to_ellipse(p, vClipRadii.xy, aa_range);
+            float d_radii_b = distance_to_ellipse(p, vClipRadii.zw, aa_range);
+            float d_radii = max(d_radii_a, -d_radii_b);
+            d = max(d, d_radii);
+        }
+    }
+
+    float alpha = distance_aa(aa_range, d);
+    vec4 color = mix(vColor0, vColor1, mix_factor);
+    oFragColor = color * alpha;
+}
+#endif
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -5,16 +5,17 @@
 #include rect,clip_scroll,render_task,resource_cache,snap,transform
 
 #define EXTEND_MODE_CLAMP  0
 #define EXTEND_MODE_REPEAT 1
 
 #define SUBPX_DIR_NONE        0
 #define SUBPX_DIR_HORIZONTAL  1
 #define SUBPX_DIR_VERTICAL    2
+#define SUBPX_DIR_MIXED       3
 
 #define RASTER_LOCAL            0
 #define RASTER_SCREEN           1
 
 uniform sampler2DArray sCacheA8;
 uniform sampler2DArray sCacheRGBA8;
 
 // An A8 target for standalone tasks that is available to all passes.
@@ -63,46 +64,27 @@ RectWithSize fetch_clip_chain_rect(int i
     return RectWithSize(rect.xy, rect.zw);
 }
 
 struct Glyph {
     vec2 offset;
 };
 
 Glyph fetch_glyph(int specific_prim_address,
-                  int glyph_index,
-                  int subpx_dir) {
+                  int glyph_index) {
     // Two glyphs are packed in each texel in the GPU cache.
     int glyph_address = specific_prim_address +
                         VECS_PER_TEXT_RUN +
                         glyph_index / 2;
     vec4 data = fetch_from_resource_cache_1(glyph_address);
     // Select XY or ZW based on glyph index.
     // We use "!= 0" instead of "== 1" here in order to work around a driver
     // bug with equality comparisons on integers.
     vec2 glyph = mix(data.xy, data.zw, bvec2(glyph_index % 2 != 0));
 
-    // In subpixel mode, the subpixel offset has already been
-    // accounted for while rasterizing the glyph.
-    switch (subpx_dir) {
-        case SUBPX_DIR_NONE:
-            break;
-        case SUBPX_DIR_HORIZONTAL:
-            // Glyphs positioned [-0.125, 0.125] get a
-            // subpx position of zero. So include that
-            // offset in the glyph position to ensure
-            // we round to the correct whole position.
-            glyph.x = floor(glyph.x + 0.125);
-            break;
-        case SUBPX_DIR_VERTICAL:
-            glyph.y = floor(glyph.y + 0.125);
-            break;
-        default: break;
-    }
-
     return Glyph(glyph);
 }
 
 struct PrimitiveInstance {
     int prim_address;
     int specific_prim_address;
     int render_task_index;
     int clip_task_index;
@@ -226,17 +208,18 @@ VertexInfo write_vertex(RectWithSize ins
 
     // Clamp to the two local clip rects.
     vec2 clamped_local_pos = clamp_rect(local_pos, local_clip_rect);
 
     /// Compute the snapping offset.
     vec2 snap_offset = compute_snap_offset(
         clamped_local_pos,
         scroll_node.transform,
-        snap_rect
+        snap_rect,
+        vec2(0.5)
     );
 
     // Transform the current vertex to world space.
     vec4 world_pos = scroll_node.transform * vec4(clamped_local_pos, 0.0, 1.0);
 
     // Convert the world positions to device pixel space.
     vec2 device_pos = world_pos.xy / world_pos.w * uDevicePixelRatio;
 
--- a/gfx/webrender/res/ps_text_run.glsl
+++ b/gfx/webrender/res/ps_text_run.glsl
@@ -15,39 +15,45 @@ varying vec4 vUvClip;
 
 #ifdef WR_VERTEX_SHADER
 
 VertexInfo write_text_vertex(vec2 clamped_local_pos,
                              RectWithSize local_clip_rect,
                              float z,
                              ClipScrollNode scroll_node,
                              PictureTask task,
-                             RectWithSize snap_rect) {
+                             RectWithSize snap_rect,
+                             vec2 snap_bias) {
+#if defined(WR_FEATURE_GLYPH_TRANSFORM) || !defined(WR_FEATURE_TRANSFORM)
+    // Ensure the transform does not contain a subpixel translation to ensure
+    // that glyph snapping is stable for equivalent glyph subpixel positions.
+    scroll_node.transform[3].xy = floor(scroll_node.transform[3].xy + 0.5);
+#endif
+
     // Transform the current vertex to world space.
     vec4 world_pos = scroll_node.transform * vec4(clamped_local_pos, 0.0, 1.0);
 
     // Convert the world positions to device pixel space.
     vec2 device_pos = world_pos.xy / world_pos.w * uDevicePixelRatio;
 
     // Apply offsets for the render task to get correct screen location.
     vec2 final_pos = device_pos -
                      task.content_origin +
                      task.common_data.task_rect.p0;
 
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
     // For transformed subpixels, we just need to align the glyph origin to a device pixel.
-    // Only check the scroll node transform's translation since the scales and axes match.
-    vec2 world_snap_p0 = snap_rect.p0 + scroll_node.transform[3].xy * uDevicePixelRatio;
-    final_pos += floor(world_snap_p0 + 0.5) - world_snap_p0;
+    final_pos += floor(snap_rect.p0 + snap_bias) - snap_rect.p0;
 #elif !defined(WR_FEATURE_TRANSFORM)
     // Compute the snapping offset only if the scroll node transform is axis-aligned.
     final_pos += compute_snap_offset(
         clamped_local_pos,
         scroll_node.transform,
-        snap_rect
+        snap_rect,
+        snap_bias
     );
 #endif
 
     gl_Position = uTransform * vec4(final_pos, z, 1.0);
 
     VertexInfo vi = VertexInfo(
         clamped_local_pos,
         device_pos,
@@ -66,19 +72,17 @@ void main(void) {
     int resource_address = prim.user_data1;
     int subpx_dir = prim.user_data2 >> 16;
     int color_mode = prim.user_data2 & 0xffff;
 
     if (color_mode == COLOR_MODE_FROM_PASS) {
         color_mode = uMode;
     }
 
-    Glyph glyph = fetch_glyph(prim.specific_prim_address,
-                              glyph_index,
-                              subpx_dir);
+    Glyph glyph = fetch_glyph(prim.specific_prim_address, glyph_index);
     GlyphResource res = fetch_glyph_resource(resource_address);
 
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
     // Transform from local space to glyph space.
     mat2 transform = mat2(prim.scroll_node.transform) * uDevicePixelRatio;
 
     // Compute the glyph rect in glyph space.
     RectWithSize glyph_rect = RectWithSize(res.offset + transform * (text.offset + glyph.offset),
@@ -107,22 +111,48 @@ void main(void) {
 
     // Select the corner of the glyph rect that we are processing.
     vec2 local_pos = glyph_rect.p0 + glyph_rect.size * aPosition.xy;
 
     // Clamp to the local clip rect.
     local_pos = clamp_rect(local_pos, prim.local_clip_rect);
 #endif
 
+    vec2 snap_bias;
+    // In subpixel mode, the subpixel offset has already been
+    // accounted for while rasterizing the glyph. However, we
+    // must still round with a subpixel bias rather than rounding
+    // to the nearest whole pixel, depending on subpixel direciton.
+    switch (subpx_dir) {
+        case SUBPX_DIR_NONE:
+        default:
+            snap_bias = vec2(0.5);
+            break;
+        case SUBPX_DIR_HORIZONTAL:
+            // Glyphs positioned [-0.125, 0.125] get a
+            // subpx position of zero. So include that
+            // offset in the glyph position to ensure
+            // we round to the correct whole position.
+            snap_bias = vec2(0.125, 0.5);
+            break;
+        case SUBPX_DIR_VERTICAL:
+            snap_bias = vec2(0.5, 0.125);
+            break;
+        case SUBPX_DIR_MIXED:
+            snap_bias = vec2(0.125);
+            break;
+    }
+
     VertexInfo vi = write_text_vertex(local_pos,
                                       prim.local_clip_rect,
                                       prim.z,
                                       prim.scroll_node,
                                       prim.task,
-                                      glyph_rect);
+                                      glyph_rect,
+                                      snap_bias);
 
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
     vec2 f = (transform * vi.local_pos - glyph_rect.p0) / glyph_rect.size;
     vUvClip = vec4(f, 1.0 - f);
 #else
     vec2 f = (vi.local_pos - glyph_rect.p0) / glyph_rect.size;
 #endif
 
--- a/gfx/webrender/res/snap.glsl
+++ b/gfx/webrender/res/snap.glsl
@@ -22,42 +22,45 @@ vec4 compute_snap_positions(mat4 transfo
     return world_snap;
 }
 
 vec2 compute_snap_offset_impl(
     vec2 reference_pos,
     mat4 transform,
     RectWithSize snap_rect,
     RectWithSize reference_rect,
-    vec4 snap_positions) {
+    vec4 snap_positions,
+    vec2 snap_bias) {
 
     /// World offsets applied to the corners of the snap rectangle.
-    vec4 snap_offsets = floor(snap_positions + 0.5) - snap_positions;
+    vec4 snap_offsets = floor(snap_positions + snap_bias.xyxy) - snap_positions;
 
     /// Compute the position of this vertex inside the snap rectangle.
     vec2 normalized_snap_pos = (reference_pos - reference_rect.p0) / reference_rect.size;
 
     /// Compute the actual world offset for this vertex needed to make it snap.
     return mix(snap_offsets.xy, snap_offsets.zw, normalized_snap_pos);
 }
 
 // Compute a snapping offset in world space (adjusted to pixel ratio),
 // given local position on the scroll_node and a snap rectangle.
 vec2 compute_snap_offset(vec2 local_pos,
                          mat4 transform,
-                         RectWithSize snap_rect) {
+                         RectWithSize snap_rect,
+                         vec2 snap_bias) {
     vec4 snap_positions = compute_snap_positions(
         transform,
         snap_rect
     );
 
     vec2 snap_offsets = compute_snap_offset_impl(
         local_pos,
         transform,
         snap_rect,
         snap_rect,
-        snap_positions
+        snap_positions,
+        snap_bias
     );
 
     return snap_offsets;
 }
 
 #endif //WR_VERTEX_SHADER
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.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::{AlphaType, ClipMode, DeviceIntRect, DeviceIntSize};
 use api::{DeviceUintRect, DeviceUintPoint, ExternalImageType, FilterOp, ImageRendering, LayoutRect};
-use api::{DeviceIntPoint, SubpixelDirection, YuvColorSpace, YuvFormat};
+use api::{DeviceIntPoint, YuvColorSpace, YuvFormat};
 use api::{LayoutToWorldTransform, WorldPixel};
 use border::{BorderCornerInstance, BorderCornerSide, BorderEdgeKind};
 use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::{CoordinateSystemId};
 use euclid::{TypedTransform3D, vec3};
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheAddress};
 use gpu_types::{BrushFlags, BrushInstance, ClipChainRectIndex, ClipMaskBorderCornerDotDash};
@@ -17,17 +17,17 @@ use gpu_types::{ClipMaskInstance, ClipSc
 use gpu_types::{PrimitiveInstance, RasterizationSpace, SimplePrimitiveInstance, ZBufferId};
 use gpu_types::ZBufferIdGenerator;
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, CachedGradient, DeferredResolve};
 use prim_store::{EdgeAaSegmentMask, ImageSource, PictureIndex, PrimitiveIndex, PrimitiveKind};
 use prim_store::{PrimitiveMetadata, PrimitiveRun, PrimitiveStore, VisibleGradientTile};
-use prim_store::CachedGradientIndex;
+use prim_store::{BorderSource, CachedGradientIndex};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind};
 use renderer::{BLOCKS_PER_UV_RECT, ShaderColorMode};
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
 use scene::FilterOpHelpers;
 use std::{usize, f32, i32};
 use tiling::{RenderTargetContext};
 use util::{MatrixHelpers, TransformedRectKind};
@@ -1148,16 +1148,17 @@ impl AlphaBatchBuilder {
             PrimitiveKind::TextRun => {
                 let text_cpu =
                     &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
 
                 let font = text_cpu.get_font(
                     ctx.device_pixel_scale,
                     Some(scroll_node.transform),
                 );
+                let subpx_dir = font.get_subpx_dir();
 
                 let glyph_fetch_buffer = &mut self.glyph_fetch_buffer;
                 let batch_list = &mut self.batch_list;
 
                 ctx.resource_cache.fetch_glyphs(
                     font,
                     &text_cpu.glyph_keys,
                     glyph_fetch_buffer,
@@ -1165,21 +1166,17 @@ impl AlphaBatchBuilder {
                     |texture_id, mut glyph_format, glyphs| {
                         debug_assert_ne!(texture_id, SourceTexture::Invalid);
 
                         // Ignore color and only sample alpha when shadowing.
                         if text_cpu.shadow {
                             glyph_format = glyph_format.ignore_color();
                         }
 
-                        let subpx_dir = match glyph_format {
-                            GlyphFormat::Bitmap |
-                            GlyphFormat::ColorBitmap => SubpixelDirection::None,
-                            _ => text_cpu.font.subpx_dir.limit_by(text_cpu.font.render_mode),
-                        };
+                        let subpx_dir = subpx_dir.limit_by(glyph_format);
 
                         let textures = BatchTextures {
                             colors: [
                                 texture_id,
                                 SourceTexture::Invalid,
                                 SourceTexture::Invalid,
                             ],
                         };
@@ -1519,23 +1516,35 @@ impl BrushPrimitive {
                             cache_item.uv_rect_handle.as_int(gpu_cache),
                             (ShaderColorMode::ColorBitmap as i32) << 16|
                              RasterizationSpace::Local as i32,
                             0,
                         ],
                     ))
                 }
             }
-            BrushKind::Border { request, .. } => {
-                let cache_item = resolve_image(
-                    request,
-                    resource_cache,
-                    gpu_cache,
-                    deferred_resolves,
-                );
+            BrushKind::Border { ref source, .. } => {
+                let cache_item = match *source {
+                    BorderSource::Image(request) => {
+                        resolve_image(
+                            request,
+                            resource_cache,
+                            gpu_cache,
+                            deferred_resolves,
+                        )
+                    }
+                    BorderSource::Border { ref handle, .. } => {
+                        let rt_handle = handle
+                            .as_ref()
+                            .expect("bug: render task handle not allocated");
+                        let rt_cache_entry = resource_cache
+                            .get_cached_render_task(rt_handle);
+                        resource_cache.get_texture_cache_item(&rt_cache_entry.handle)
+                    }
+                };
 
                 if cache_item.texture_id == SourceTexture::Invalid {
                     None
                 } else {
                     let textures = BatchTextures::color(cache_item.texture_id);
 
                     Some((
                         BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -1,22 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ClipMode, ColorF, LayoutPoint};
-use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, NormalBorder};
+use api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ColorF, LayoutPoint};
+use api::{ColorU, DeviceRect, DeviceSize, LayoutSizeAu, LayoutPrimitiveInfo, LayoutToDeviceScale};
+use api::{DevicePoint, DeviceIntSize, LayoutRect, LayoutSize, NormalBorder};
+use app_units::Au;
 use clip::ClipSource;
 use ellipse::Ellipse;
 use display_list_flattener::DisplayListFlattener;
-use gpu_types::BrushFlags;
+use gpu_types::{BorderInstance, BorderSegment, BrushFlags};
 use gpu_cache::GpuDataRequest;
-use prim_store::{BorderPrimitiveCpu, BrushClipMaskKind, BrushSegment, BrushSegmentDescriptor};
-use prim_store::{EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
-use util::{lerp, pack_as_float};
+use prim_store::{BorderPrimitiveCpu, BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegment, BrushSegmentDescriptor};
+use prim_store::{BorderSource, EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
+use util::{lerp, pack_as_float, RectHelpers};
 
 #[repr(u8)]
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum BorderCornerInstance {
     None,
     Single, // Single instance needed - corner styles are same or similar.
     Double, // Different corner styles. Draw two instances, one per style.
 }
@@ -31,16 +33,106 @@ pub enum BorderCornerSide {
 #[repr(C)]
 enum BorderCorner {
     TopLeft,
     TopRight,
     BottomLeft,
     BottomRight,
 }
 
+trait AuSizeConverter {
+    fn to_au(&self) -> LayoutSizeAu;
+}
+
+impl AuSizeConverter for LayoutSize {
+    fn to_au(&self) -> LayoutSizeAu {
+        LayoutSizeAu::new(
+            Au::from_f32_px(self.width),
+            Au::from_f32_px(self.height),
+        )
+    }
+}
+
+// TODO(gw): Perhaps there is a better way to store
+//           the border cache key than duplicating
+//           all the border structs with hashable
+//           variants...
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BorderRadiusAu {
+    pub top_left: LayoutSizeAu,
+    pub top_right: LayoutSizeAu,
+    pub bottom_left: LayoutSizeAu,
+    pub bottom_right: LayoutSizeAu,
+}
+
+impl From<BorderRadius> for BorderRadiusAu {
+    fn from(radius: BorderRadius) -> BorderRadiusAu {
+        BorderRadiusAu {
+            top_left: radius.top_left.to_au(),
+            top_right: radius.top_right.to_au(),
+            bottom_right: radius.bottom_right.to_au(),
+            bottom_left: radius.bottom_left.to_au(),
+        }
+    }
+}
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BorderWidthsAu {
+    pub left: Au,
+    pub top: Au,
+    pub right: Au,
+    pub bottom: Au,
+}
+
+impl From<BorderWidths> for BorderWidthsAu {
+    fn from(widths: BorderWidths) -> Self {
+        BorderWidthsAu {
+            left: Au::from_f32_px(widths.left),
+            top: Au::from_f32_px(widths.top),
+            right: Au::from_f32_px(widths.right),
+            bottom: Au::from_f32_px(widths.bottom),
+        }
+    }
+}
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BorderSideAu {
+    pub color: ColorU,
+    pub style: BorderStyle,
+}
+
+impl From<BorderSide> for BorderSideAu {
+    fn from(side: BorderSide) -> Self {
+        BorderSideAu {
+            color: side.color.into(),
+            style: side.style,
+        }
+    }
+}
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BorderCacheKey {
+    pub left: BorderSideAu,
+    pub right: BorderSideAu,
+    pub top: BorderSideAu,
+    pub bottom: BorderSideAu,
+    pub radius: BorderRadiusAu,
+    pub widths: BorderWidthsAu,
+    pub scale: Au,
+}
+
 #[derive(Clone, Debug, PartialEq)]
 pub enum BorderCornerKind {
     None,
     Solid,
     Clip(BorderCornerInstance),
     Mask(
         BorderCornerClipData,
         LayoutSize,
@@ -369,70 +461,64 @@ impl<'a> DisplayListFlattener<'a> {
         ensure_no_corner_overlap(&mut border.radius, &info.rect);
 
         let radius = &border.radius;
         let left = &border.left;
         let right = &border.right;
         let top = &border.top;
         let bottom = &border.bottom;
 
-        let constant_color = left.color;
-        let is_simple_border = [left, top, right, bottom].iter().all(|edge| {
-            edge.style == BorderStyle::Solid &&
-            edge.color == constant_color
+        let brush_border_supported = [left, top, right, bottom].iter().all(|edge| {
+            match edge.style {
+                BorderStyle::Solid |
+                BorderStyle::Hidden |
+                BorderStyle::None |
+                BorderStyle::Inset |
+                BorderStyle::Outset => {
+                    true
+                }
+
+                BorderStyle::Double |
+                BorderStyle::Dotted |
+                BorderStyle::Dashed |
+                BorderStyle::Groove |
+                BorderStyle::Ridge => {
+                    false
+                }
+            }
         });
 
-        if is_simple_border {
-            let extra_clips = vec![
-                ClipSource::new_rounded_rect(
-                    info.rect,
-                    border.radius,
-                    ClipMode::Clip,
-                ),
-                ClipSource::new_rounded_rect(
-                    LayoutRect::new(
-                        LayoutPoint::new(
-                            info.rect.origin.x + widths.left,
-                            info.rect.origin.y + widths.top,
-                        ),
-                        LayoutSize::new(
-                            info.rect.size.width - widths.left - widths.right,
-                            info.rect.size.height - widths.top - widths.bottom,
-                        ),
-                    ),
-                    BorderRadius {
-                        top_left: LayoutSize::new(
-                            (border.radius.top_left.width - widths.left).max(0.0),
-                            (border.radius.top_left.height - widths.top).max(0.0),
-                        ),
-                        top_right: LayoutSize::new(
-                            (border.radius.top_right.width - widths.right).max(0.0),
-                            (border.radius.top_right.height - widths.top).max(0.0),
-                        ),
-                        bottom_left: LayoutSize::new(
-                            (border.radius.bottom_left.width - widths.left).max(0.0),
-                            (border.radius.bottom_left.height - widths.bottom).max(0.0),
-                        ),
-                        bottom_right: LayoutSize::new(
-                            (border.radius.bottom_right.width - widths.right).max(0.0),
-                            (border.radius.bottom_right.height - widths.bottom).max(0.0),
-                        ),
+        if brush_border_supported {
+            let prim = BrushPrimitive::new(
+                BrushKind::Border {
+                    source: BorderSource::Border {
+                        border,
+                        widths: *widths,
+                        cache_key: BorderCacheKey {
+                            left: border.left.into(),
+                            top: border.top.into(),
+                            right: border.right.into(),
+                            bottom: border.bottom.into(),
+                            widths: (*widths).into(),
+                            radius: border.radius.into(),
+                            scale: Au::from_f32_px(0.0),
+                        },
+                        task_info: None,
+                        handle: None,
                     },
-                    ClipMode::ClipOut,
-                ),
-            ];
+                },
+                None,
+            );
 
-            self.add_solid_rectangle(
+            self.add_primitive(
                 clip_and_scroll,
                 info,
-                border.top.color,
-                None,
-                extra_clips,
+                Vec::new(),
+                PrimitiveContainer::Brush(prim),
             );
-
             return;
         }
 
         let corners = [
             get_corner(
                 left,
                 widths.left,
                 top,
@@ -500,18 +586,17 @@ impl<'a> DisplayListFlattener<'a> {
             );
             let p2 = LayoutPoint::new(
                 info.rect.origin.x + info.rect.size.width - right_len,
                 info.rect.origin.y + info.rect.size.height - bottom_len,
             );
             let p3 = info.rect.bottom_right();
 
             let segment = |x0, y0, x1, y1| BrushSegment::new(
-                LayoutPoint::new(x0, y0),
-                LayoutSize::new(x1-x0, y1-y0),
+                LayoutRect::from_floats(x0, y0, x1, y1),
                 true,
                 EdgeAaSegmentMask::all(), // Note: this doesn't seem right, needs revision
                 [0.0; 4],
                 BrushFlags::empty(),
             );
 
             // Add a solid rectangle for each visible edge/corner combination.
             if top_edge == BorderEdgeKind::Solid {
@@ -924,8 +1009,439 @@ struct DotInfo {
     diameter: f32,
 }
 
 impl DotInfo {
     fn new(arc_pos: f32, diameter: f32) -> DotInfo {
         DotInfo { arc_pos, diameter }
     }
 }
+
+#[derive(Debug)]
+pub struct BorderRenderTaskInfo {
+    pub instances: Vec<BorderInstance>,
+    pub segments: Vec<BrushSegment>,
+    pub size: DeviceIntSize,
+}
+
+impl BorderRenderTaskInfo {
+    pub fn new(
+        rect: &LayoutRect,
+        border: &NormalBorder,
+        widths: &BorderWidths,
+        scale: LayoutToDeviceScale,
+    ) -> Self {
+        let mut instances = Vec::new();
+        let mut segments = Vec::new();
+
+        let dp_width_top = (widths.top * scale.0).ceil();
+        let dp_width_bottom = (widths.bottom * scale.0).ceil();
+        let dp_width_left = (widths.left * scale.0).ceil();
+        let dp_width_right = (widths.right * scale.0).ceil();
+
+        let dp_corner_tl = (border.radius.top_left * scale).ceil();
+        let dp_corner_tr = (border.radius.top_right * scale).ceil();
+        let dp_corner_bl = (border.radius.bottom_left * scale).ceil();
+        let dp_corner_br = (border.radius.bottom_right * scale).ceil();
+
+        let dp_size_tl = DeviceSize::new(
+            dp_corner_tl.width.max(dp_width_left),
+            dp_corner_tl.height.max(dp_width_top),
+        );
+        let dp_size_tr = DeviceSize::new(
+            dp_corner_tr.width.max(dp_width_right),
+            dp_corner_tr.height.max(dp_width_top),
+        );
+        let dp_size_br = DeviceSize::new(
+            dp_corner_br.width.max(dp_width_right),
+            dp_corner_br.height.max(dp_width_bottom),
+        );
+        let dp_size_bl = DeviceSize::new(
+            dp_corner_bl.width.max(dp_width_left),
+            dp_corner_bl.height.max(dp_width_bottom),
+        );
+
+        let local_size_tl = LayoutSize::new(
+            border.radius.top_left.width.max(widths.left),
+            border.radius.top_left.height.max(widths.top),
+        );
+        let local_size_tr = LayoutSize::new(
+            border.radius.top_right.width.max(widths.right),
+            border.radius.top_right.height.max(widths.top),
+        );
+        let local_size_br = LayoutSize::new(
+            border.radius.bottom_right.width.max(widths.right),
+            border.radius.bottom_right.height.max(widths.bottom),
+        );
+        let local_size_bl = LayoutSize::new(
+            border.radius.bottom_left.width.max(widths.left),
+            border.radius.bottom_left.height.max(widths.bottom),
+        );
+
+        // TODO(gw): The inner and outer widths don't matter for simple
+        //           border types. Once we push dashing and dotted styles
+        //           through border brushes, we need to calculate an
+        //           appropriate length here.
+        let width_inner = 16.0;
+        let height_inner = 16.0;
+
+        let size = DeviceSize::new(
+            dp_size_tl.width.max(dp_size_bl.width) + width_inner + dp_size_tr.width.max(dp_size_br.width),
+            dp_size_tl.height.max(dp_size_tr.height) + height_inner + dp_size_bl.height.max(dp_size_br.height),
+        );
+
+        // These modulate colors are not part of the specification. They
+        // are derived from the Gecko source code and experimentation, and
+        // used to modulate the colors in order to generate colors for
+        // the inset/outset and groove/ridge border styles.
+        let left_color = border.left.border_color(1.0, 2.0 / 3.0, 0.3, 0.7);
+        let top_color = border.top.border_color(1.0, 2.0 / 3.0, 0.3, 0.7);
+        let right_color = border.right.border_color(2.0 / 3.0, 1.0, 0.7, 0.3);
+        let bottom_color = border.bottom.border_color(2.0 / 3.0, 1.0, 0.7, 0.3);
+
+        add_edge_segment(
+            LayoutRect::from_floats(
+                rect.origin.x,
+                rect.origin.y + local_size_tl.height,
+                rect.origin.x + widths.left,
+                rect.origin.y + rect.size.height - local_size_bl.height,
+            ),
+            DeviceRect::from_floats(
+                0.0,
+                dp_size_tl.height,
+                dp_width_left,
+                size.height - dp_size_bl.height,
+            ),
+            border.left.style,
+            left_color,
+            BorderSegment::Left,
+            EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT,
+            &mut instances,
+            BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_Y,
+            &mut segments,
+        );
+
+        add_edge_segment(
+            LayoutRect::from_floats(
+                rect.origin.x + local_size_tl.width,
+                rect.origin.y,
+                rect.origin.x + rect.size.width - local_size_tr.width,
+                rect.origin.y + widths.top,
+            ),
+            DeviceRect::from_floats(
+                dp_size_tl.width,
+                0.0,
+                size.width - dp_size_tr.width,
+                dp_width_top,
+            ),
+            border.top.style,
+            top_color,
+            BorderSegment::Top,
+            EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM,
+            &mut instances,
+            BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_X,
+            &mut segments,
+        );
+
+        add_edge_segment(
+            LayoutRect::from_floats(
+                rect.origin.x + rect.size.width - widths.right,
+                rect.origin.y + local_size_tr.height,
+                rect.origin.x + rect.size.width,
+                rect.origin.y + rect.size.height - local_size_br.height,
+            ),
+            DeviceRect::from_floats(
+                size.width - dp_width_right,
+                dp_size_tr.height,
+                size.width,
+                size.height - dp_size_br.height,
+            ),
+            border.right.style,
+            right_color,
+            BorderSegment::Right,
+            EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT,
+            &mut instances,
+            BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_Y,
+            &mut segments,
+        );
+
+        add_edge_segment(
+            LayoutRect::from_floats(
+                rect.origin.x + local_size_bl.width,
+                rect.origin.y + rect.size.height - widths.bottom,
+                rect.origin.x + rect.size.width - local_size_br.width,
+                rect.origin.y + rect.size.height,
+            ),
+            DeviceRect::from_floats(
+                dp_size_bl.width,
+                size.height - dp_width_bottom,
+                size.width - dp_size_br.width,
+                size.height,
+            ),
+            border.bottom.style,
+            bottom_color,
+            BorderSegment::Bottom,
+            EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP,
+            &mut instances,
+            BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_X,
+            &mut segments,
+        );
+
+        add_corner_segment(
+            LayoutRect::from_floats(
+                rect.origin.x,
+                rect.origin.y,
+                rect.origin.x + local_size_tl.width,
+                rect.origin.y + local_size_tl.height,
+            ),
+            DeviceRect::from_floats(
+                0.0,
+                0.0,
+                dp_size_tl.width,
+                dp_size_tl.height,
+            ),
+            border.left.style,
+            left_color,
+            border.top.style,
+            top_color,
+            DeviceSize::new(dp_width_left, dp_width_top),
+            dp_corner_tl,
+            BorderSegment::TopLeft,
+            EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT,
+            &mut instances,
+            &mut segments,
+        );
+
+        add_corner_segment(
+            LayoutRect::from_floats(
+                rect.origin.x + rect.size.width - local_size_tr.width,
+                rect.origin.y,
+                rect.origin.x + rect.size.width,
+                rect.origin.y + local_size_tr.height,
+            ),
+            DeviceRect::from_floats(
+                size.width - dp_size_tr.width,
+                0.0,
+                size.width,
+                dp_size_tr.height,
+            ),
+            border.top.style,
+            top_color,
+            border.right.style,
+            right_color,
+            DeviceSize::new(dp_width_right, dp_width_top),
+            dp_corner_tr,
+            BorderSegment::TopRight,
+            EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT,
+            &mut instances,
+            &mut segments,
+        );
+
+        add_corner_segment(
+            LayoutRect::from_floats(
+                rect.origin.x + rect.size.width - local_size_br.width,
+                rect.origin.y + rect.size.height - local_size_br.height,
+                rect.origin.x + rect.size.width,
+                rect.origin.y + rect.size.height,
+            ),
+            DeviceRect::from_floats(
+                size.width - dp_size_br.width,
+                size.height - dp_size_br.height,
+                size.width,
+                size.height,
+            ),
+            border.right.style,
+            right_color,
+            border.bottom.style,
+            bottom_color,
+            DeviceSize::new(dp_width_right, dp_width_bottom),
+            dp_corner_br,
+            BorderSegment::BottomRight,
+            EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT,
+            &mut instances,
+            &mut segments,
+        );
+
+        add_corner_segment(
+            LayoutRect::from_floats(
+                rect.origin.x,
+                rect.origin.y + rect.size.height - local_size_bl.height,
+                rect.origin.x + local_size_bl.width,
+                rect.origin.y + rect.size.height,
+            ),
+            DeviceRect::from_floats(
+                0.0,
+                size.height - dp_size_bl.height,
+                dp_size_bl.width,
+                size.height,
+            ),
+            border.bottom.style,
+            bottom_color,
+            border.left.style,
+            left_color,
+            DeviceSize::new(dp_width_left, dp_width_bottom),
+            dp_corner_bl,
+            BorderSegment::BottomLeft,
+            EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::LEFT,
+            &mut instances,
+            &mut segments,
+        );
+
+        BorderRenderTaskInfo {
+            segments,
+            instances,
+            size: size.to_i32(),
+        }
+    }
+}
+
+fn add_brush_segment(
+    image_rect: LayoutRect,
+    task_rect: DeviceRect,
+    brush_flags: BrushFlags,
+    edge_flags: EdgeAaSegmentMask,
+    brush_segments: &mut Vec<BrushSegment>,
+) {
+    brush_segments.push(
+        BrushSegment::new(
+            image_rect,
+            true,
+            edge_flags,
+            [
+                task_rect.origin.x,
+                task_rect.origin.y,
+                task_rect.origin.x + task_rect.size.width,
+                task_rect.origin.y + task_rect.size.height,
+            ],
+            brush_flags,
+        )
+    );
+}
+
+fn add_segment(
+    task_rect: DeviceRect,
+    style0: BorderStyle,
+    style1: BorderStyle,
+    color0: ColorF,
+    color1: ColorF,
+    segment: BorderSegment,
+    instances: &mut Vec<BorderInstance>,
+    widths: DeviceSize,
+    radius: DeviceSize,
+) {
+    let flags = (segment as i32) |
+                ((style0 as i32) << 8) |
+                ((style1 as i32) << 16);
+
+    let base_instance = BorderInstance {
+        task_origin: DevicePoint::zero(),
+        local_rect: task_rect,
+        flags,
+        color0: color0.premultiplied(),
+        color1: color1.premultiplied(),
+        widths,
+        radius,
+    };
+
+    instances.push(base_instance);
+}
+
+fn add_corner_segment(
+    image_rect: LayoutRect,
+    task_rect: DeviceRect,
+    mut style0: BorderStyle,
+    color0: ColorF,
+    mut style1: BorderStyle,
+    color1: ColorF,
+    widths: DeviceSize,
+    radius: DeviceSize,
+    segment: BorderSegment,
+    edge_flags: EdgeAaSegmentMask,
+    instances: &mut Vec<BorderInstance>,
+    brush_segments: &mut Vec<BrushSegment>,
+) {
+    // TODO(gw): This will need to be a bit more involved when
+    //           we support other border types here. For example,
+    //           groove / ridge borders will always need to
+    //           use two instances.
+
+    if color0.a <= 0.0 && color1.a <= 0.0 {
+        return;
+    }
+
+    if widths.width <= 0.0 && widths.height <= 0.0 {
+        return;
+    }
+
+    let style0_hidden = style0 == BorderStyle::Hidden || style0 == BorderStyle::None;
+    let style1_hidden = style1 == BorderStyle::Hidden || style1 == BorderStyle::None;
+
+    if style0_hidden && style1_hidden {
+        return;
+    }
+
+    if style0_hidden {
+        style0 = style1;
+    }
+    if style1_hidden {
+        style1 = style0;
+    }
+
+    add_segment(
+        task_rect,
+        style0,
+        style1,
+        color0,
+        color1,
+        segment,
+        instances,
+        widths,
+        radius,
+    );
+
+    add_brush_segment(
+        image_rect,
+        task_rect,
+        BrushFlags::SEGMENT_RELATIVE,
+        edge_flags,
+        brush_segments,
+    );
+}
+
+fn add_edge_segment(
+    image_rect: LayoutRect,
+    task_rect: DeviceRect,
+    style: BorderStyle,
+    color: ColorF,
+    segment: BorderSegment,
+    edge_flags: EdgeAaSegmentMask,
+    instances: &mut Vec<BorderInstance>,
+    brush_flags: BrushFlags,
+    brush_segments: &mut Vec<BrushSegment>,
+) {
+    if color.a <= 0.0 {
+        return;
+    }
+
+    if style == BorderStyle::Hidden || style == BorderStyle::None {
+        return;
+    }
+
+    add_segment(
+        task_rect,
+        style,
+        style,
+        color,
+        color,
+        segment,
+        instances,
+        DeviceSize::zero(),
+        DeviceSize::zero(),
+    );
+
+    add_brush_segment(
+        image_rect,
+        task_rect,
+        brush_flags,
+        edge_flags,
+        brush_segments,
+    );
+}
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -22,17 +22,17 @@ use frame_builder::{FrameBuilder, FrameB
 use glyph_rasterizer::FontInstance;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::PictureCompositeMode;
 use prim_store::{BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient};
 use prim_store::{CachedGradientIndex, EdgeAaSegmentMask, ImageSource};
-use prim_store::{BrushSegment, PictureIndex, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
+use prim_store::{BorderSource, BrushSegment, PictureIndex, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitiveCpu};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::{BuiltScene, SceneRequest};
 use std::{f32, mem, usize};
 use tiling::{CompositeOps, ScrollbarPrimitive};
 use util::{MaxRect, RectHelpers, recycle_vec};
@@ -1631,18 +1631,17 @@ impl<'a> DisplayListFlattener<'a> {
                         if repeat_horizontal == RepeatMode::Repeat {
                             brush_flags |= BrushFlags::SEGMENT_REPEAT_X;
                         }
                         if repeat_vertical == RepeatMode::Repeat {
                             brush_flags |= BrushFlags::SEGMENT_REPEAT_Y;
                         }
 
                         let segment = BrushSegment::new(
-                            rect.origin,
-                            rect.size,
+                            rect,
                             true,
                             EdgeAaSegmentMask::empty(),
                             [
                                 uv_rect.uv0.x,
                                 uv_rect.uv0.y,
                                 uv_rect.uv1.x,
                                 uv_rect.uv1.y,
                             ],
@@ -1736,23 +1735,25 @@ impl<'a> DisplayListFlattener<'a> {
                 );
                 let descriptor = BrushSegmentDescriptor {
                     segments,
                     clip_mask_kind: BrushClipMaskKind::Unknown,
                 };
 
                 let prim = PrimitiveContainer::Brush(match border.source {
                     NinePatchBorderSource::Image(image_key) => {
+                        let source = BorderSource::Image(ImageRequest {
+                            key: image_key,
+                            rendering: ImageRendering::Auto,
+                            tile: None,
+                        });
+
                         BrushPrimitive::new(
                             BrushKind::Border {
-                                request: ImageRequest {
-                                    key: image_key,
-                                    rendering: ImageRendering::Auto,
-                                    tile: None,
-                                },
+                                source
                             },
                             Some(descriptor),
                         )
                     }
                 });
 
                 self.add_primitive(clip_and_scroll, info, Vec::new(), prim);
             }
@@ -1961,30 +1962,28 @@ impl<'a> DisplayListFlattener<'a> {
             };
 
             let prim_font = FontInstance::new(
                 font_instance.font_key,
                 font_instance.size,
                 *text_color,
                 font_instance.bg_color,
                 render_mode,
-                font_instance.subpx_dir,
                 flags,
                 font_instance.platform_options,
                 font_instance.variations.clone(),
             );
-            TextRunPrimitiveCpu {
-                font: prim_font,
+            TextRunPrimitiveCpu::new(
+                prim_font,
+                run_offset,
                 glyph_range,
-                glyph_gpu_blocks: Vec::new(),
-                glyph_keys: Vec::new(),
-                offset: run_offset,
-                shadow: false,
+                Vec::new(),
+                false,
                 glyph_raster_space,
-            }
+            )
         };
 
         self.add_primitive(
             clip_and_scroll,
             prim_info,
             Vec::new(),
             PrimitiveContainer::TextRun(prim),
         );
--- a/gfx/webrender/src/glyph_cache.rs
+++ b/gfx/webrender/src/glyph_cache.rs
@@ -1,16 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #[cfg(feature = "pathfinder")]
 use api::DeviceIntPoint;
-use api::GlyphKey;
-use glyph_rasterizer::{FontInstance, GlyphFormat};
+use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey};
 use internal_types::FastHashMap;
 use render_task::RenderTaskCache;
 #[cfg(feature = "pathfinder")]
 use render_task::RenderTaskCacheKey;
 use resource_cache::ResourceClassCache;
 use std::sync::Arc;
 use texture_cache::{EvictionNotice, TextureCache};
 #[cfg(not(feature = "pathfinder"))]
--- a/gfx/webrender/src/glyph_rasterizer/mod.rs
+++ b/gfx/webrender/src/glyph_rasterizer/mod.rs
@@ -1,17 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ColorF, ColorU};
+use api::{ColorF, ColorU, DevicePoint};
 use api::{FontInstanceFlags, FontInstancePlatformOptions};
 use api::{FontKey, FontRenderMode, FontTemplate, FontVariation};
-use api::{GlyphDimensions, GlyphKey, LayoutToWorldTransform, SubpixelDirection};
+use api::{GlyphIndex, GlyphDimensions};
+use api::{LayoutPoint, LayoutToWorldTransform, WorldPoint};
 use app_units::Au;
+use euclid::approxeq::ApproxEq;
 use internal_types::ResourceCacheError;
 use platform::font::FontContext;
 use rayon::ThreadPool;
 use std::cmp;
 use std::hash::{Hash, Hasher};
 use std::mem;
 use std::sync::{Arc, Mutex, MutexGuard};
 use std::sync::mpsc::{channel, Receiver, Sender};
@@ -122,17 +124,37 @@ impl FontTransform {
         FontTransform::new(self.skew_x, self.scale_x, self.scale_y, self.skew_y)
     }
 
     pub fn flip_x(&self) -> Self {
         FontTransform::new(-self.scale_x, self.skew_x, -self.skew_y, self.scale_y)
     }
 
     pub fn flip_y(&self) -> Self {
-        FontTransform::new(self.scale_x, -self.skew_y, self.skew_y, -self.scale_y)
+        FontTransform::new(self.scale_x, -self.skew_x, self.skew_y, -self.scale_y)
+    }
+
+    pub fn transform(&self, point: &LayoutPoint) -> WorldPoint {
+        WorldPoint::new(
+            self.scale_x * point.x + self.skew_x * point.y,
+            self.skew_y * point.x + self.scale_y * point.y,
+        )
+    }
+
+    pub fn get_subpx_dir(&self) -> SubpixelDirection {
+        if self.skew_y.approx_eq(&0.0) {
+            // The X axis is not projected onto the Y axis
+            SubpixelDirection::Horizontal
+        } else if self.scale_x.approx_eq(&0.0) {
+            // The X axis has been swapped with the Y axis
+            SubpixelDirection::Vertical
+        } else {
+            // Use subpixel precision on all axes
+            SubpixelDirection::Mixed
+        }
     }
 }
 
 impl<'a> From<&'a LayoutToWorldTransform> for FontTransform {
     fn from(xform: &'a LayoutToWorldTransform) -> Self {
         FontTransform::new(xform.m11, xform.m21, xform.m12, xform.m22)
     }
 }
@@ -146,67 +168,90 @@ pub struct FontInstance {
     // It is stored as an Au since we need sub-pixel sizes, but
     // can't store as a f32 due to use of this type as a hash key.
     // TODO(gw): Perhaps consider having LogicalAu and DeviceAu
     //           or something similar to that.
     pub size: Au,
     pub color: ColorU,
     pub bg_color: ColorU,
     pub render_mode: FontRenderMode,
-    pub subpx_dir: SubpixelDirection,
     pub flags: FontInstanceFlags,
     pub platform_options: Option<FontInstancePlatformOptions>,
     pub variations: Vec<FontVariation>,
     pub transform: FontTransform,
 }
 
 impl FontInstance {
     pub fn new(
         font_key: FontKey,
         size: Au,
         color: ColorF,
         bg_color: ColorU,
         render_mode: FontRenderMode,
-        subpx_dir: SubpixelDirection,
         flags: FontInstanceFlags,
         platform_options: Option<FontInstancePlatformOptions>,
         variations: Vec<FontVariation>,
     ) -> Self {
         // If a background color is enabled, it only makes sense
         // for it to be completely opaque.
         debug_assert!(bg_color.a == 0 || bg_color.a == 255);
 
         FontInstance {
             font_key,
             size,
             color: color.into(),
             bg_color,
             render_mode,
-            subpx_dir,
             flags,
             platform_options,
             variations,
             transform: FontTransform::identity(),
         }
     }
 
     pub fn get_alpha_glyph_format(&self) -> GlyphFormat {
         if self.transform.is_identity() { GlyphFormat::Alpha } else { GlyphFormat::TransformedAlpha }
     }
 
     pub fn get_subpixel_glyph_format(&self) -> GlyphFormat {
         if self.transform.is_identity() { GlyphFormat::Subpixel } else { GlyphFormat::TransformedSubpixel }
     }
 
+    pub fn disable_subpixel_aa(&mut self) {
+        self.render_mode = self.render_mode.limit_by(FontRenderMode::Alpha);
+    }
+
+    pub fn disable_subpixel_position(&mut self) {
+        self.flags.remove(FontInstanceFlags::SUBPIXEL_POSITION);
+    }
+
+    pub fn use_subpixel_position(&self) -> bool {
+        self.flags.contains(FontInstanceFlags::SUBPIXEL_POSITION) &&
+        self.render_mode != FontRenderMode::Mono
+    }
+
+    pub fn get_subpx_dir(&self) -> SubpixelDirection {
+        if self.use_subpixel_position() {
+            let mut subpx_dir = self.transform.get_subpx_dir();
+            if self.flags.contains(FontInstanceFlags::TRANSPOSE) {
+                subpx_dir = subpx_dir.swap_xy();
+            }
+            subpx_dir
+        } else {
+            SubpixelDirection::None
+        }
+    }
+
     #[allow(dead_code)]
     pub fn get_subpx_offset(&self, glyph: &GlyphKey) -> (f64, f64) {
-        match self.subpx_dir {
-            SubpixelDirection::None => (0.0, 0.0),
-            SubpixelDirection::Horizontal => (glyph.subpixel_offset.into(), 0.0),
-            SubpixelDirection::Vertical => (0.0, glyph.subpixel_offset.into()),
+        if self.use_subpixel_position() {
+            let (dx, dy) = glyph.subpixel_offset;
+            (dx.into(), dy.into())
+        } else {
+            (0.0, 0.0)
         }
     }
 
     #[allow(dead_code)]
     pub fn get_glyph_format(&self) -> GlyphFormat {
         match self.render_mode {
             FontRenderMode::Mono | FontRenderMode::Alpha => self.get_alpha_glyph_format(),
             FontRenderMode::Subpixel => self.get_subpixel_glyph_format(),
@@ -222,16 +267,121 @@ impl FontInstance {
             }
             (bold_offset * x_scale).max(1.0).round() as usize
         } else {
             0
         }
     }
 }
 
+#[repr(u32)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
+pub enum SubpixelDirection {
+    None = 0,
+    Horizontal,
+    Vertical,
+    Mixed,
+}
+
+impl SubpixelDirection {
+    // Limit the subpixel direction to what is supported by the glyph format.
+    pub fn limit_by(self, glyph_format: GlyphFormat) -> Self {
+        match glyph_format {
+            GlyphFormat::Bitmap |
+            GlyphFormat::ColorBitmap => SubpixelDirection::None,
+            _ => self,
+        }
+    }
+
+    pub fn swap_xy(self) -> Self {
+        match self {
+            SubpixelDirection::None | SubpixelDirection::Mixed => self,
+            SubpixelDirection::Horizontal => SubpixelDirection::Vertical,
+            SubpixelDirection::Vertical => SubpixelDirection::Horizontal,
+        }
+    }
+}
+
+#[repr(u8)]
+#[derive(Hash, Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum SubpixelOffset {
+    Zero = 0,
+    Quarter = 1,
+    Half = 2,
+    ThreeQuarters = 3,
+}
+
+impl SubpixelOffset {
+    // Skia quantizes subpixel offsets into 1/4 increments.
+    // Given the absolute position, return the quantized increment
+    fn quantize(pos: f32) -> Self {
+        // Following the conventions of Gecko and Skia, we want
+        // to quantize the subpixel position, such that abs(pos) gives:
+        // [0.0, 0.125) -> Zero
+        // [0.125, 0.375) -> Quarter
+        // [0.375, 0.625) -> Half
+        // [0.625, 0.875) -> ThreeQuarters,
+        // [0.875, 1.0) -> Zero
+        // The unit tests below check for this.
+        let apos = ((pos - pos.floor()) * 8.0) as i32;
+
+        match apos {
+            0 | 7 => SubpixelOffset::Zero,
+            1...2 => SubpixelOffset::Quarter,
+            3...4 => SubpixelOffset::Half,
+            5...6 => SubpixelOffset::ThreeQuarters,
+            _ => unreachable!("bug: unexpected quantized result"),
+        }
+    }
+}
+
+impl Into<f64> for SubpixelOffset {
+    fn into(self) -> f64 {
+        match self {
+            SubpixelOffset::Zero => 0.0,
+            SubpixelOffset::Quarter => 0.25,
+            SubpixelOffset::Half => 0.5,
+            SubpixelOffset::ThreeQuarters => 0.75,
+        }
+    }
+}
+
+#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct GlyphKey {
+    pub index: u32,
+    pub subpixel_offset: (SubpixelOffset, SubpixelOffset),
+}
+
+impl GlyphKey {
+    pub fn new(
+        index: u32,
+        point: DevicePoint,
+        subpx_dir: SubpixelDirection,
+    ) -> GlyphKey {
+        let (dx, dy) = match subpx_dir {
+            SubpixelDirection::None => (0.0, 0.0),
+            SubpixelDirection::Horizontal => (point.x, 0.0),
+            SubpixelDirection::Vertical => (0.0, point.y),
+            SubpixelDirection::Mixed => (point.x, point.y),
+        };
+
+        GlyphKey {
+            index,
+            subpixel_offset: (
+                SubpixelOffset::quantize(dx),
+                SubpixelOffset::quantize(dy),
+            ),
+        }
+    }
+}
+
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[allow(dead_code)]
 pub enum GlyphFormat {
     Alpha,
     TransformedAlpha,
     Subpixel,
@@ -390,21 +540,27 @@ impl GlyphRasterizer {
 
     pub fn prepare_font(&self, font: &mut FontInstance) {
         FontContext::prepare_font(font);
     }
 
     pub fn get_glyph_dimensions(
         &mut self,
         font: &FontInstance,
-        glyph_key: &GlyphKey,
+        glyph_index: GlyphIndex,
     ) -> Option<GlyphDimensions> {
+        let glyph_key = GlyphKey::new(
+            glyph_index,
+            DevicePoint::zero(),
+            SubpixelDirection::None,
+        );
+
         self.font_contexts
             .lock_shared_context()
-            .get_glyph_dimensions(font, glyph_key)
+            .get_glyph_dimensions(font, &glyph_key)
     }
 
     pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
         self.font_contexts
             .lock_shared_context()
             .get_glyph_index(font_key, ch)
     }
 
@@ -449,33 +605,16 @@ impl AddFont for FontContext {
             }
             FontTemplate::Native(ref native_font_handle) => {
                 self.add_native_font(font_key, (*native_font_handle).clone());
             }
         }
     }
 }
 
-#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct GlyphRequest {
-    pub key: GlyphKey,
-    pub font: FontInstance,
-}
-
-impl GlyphRequest {
-    pub fn new(font: &FontInstance, key: &GlyphKey) -> Self {
-        GlyphRequest {
-            key: key.clone(),
-            font: font.clone(),
-        }
-    }
-}
-
 #[allow(dead_code)]
 pub(in glyph_rasterizer) struct GlyphRasterJob {
     key: GlyphKey,
     result: GlyphRasterResult,
 }
 
 #[allow(dead_code)]
 pub enum GlyphRasterResult {
@@ -506,23 +645,23 @@ mod test_glyph_rasterizer {
         use std::io::Read;
         use texture_cache::TextureCache;
         use glyph_cache::GlyphCache;
         use gpu_cache::GpuCache;
         use tiling::SpecialRenderPasses;
         use api::DeviceIntSize;
         use render_task::{RenderTaskCache, RenderTaskTree};
         use profiler::TextureCacheProfileCounters;
-        use api::{FontKey, FontTemplate, FontRenderMode, GlyphKey,
-                  IdNamespace, LayoutPoint, ColorF, ColorU, SubpixelDirection};
+        use api::{FontKey, FontTemplate, FontRenderMode,
+                  IdNamespace, ColorF, ColorU, DevicePoint};
         use render_backend::FrameId;
         use app_units::Au;
         use thread_profiler::register_thread_with_profiler;
         use std::sync::Arc;
-        use glyph_rasterizer::{GlyphRasterizer, FontInstance};
+        use glyph_rasterizer::{FontInstance, GlyphKey, GlyphRasterizer};
 
         let worker = ThreadPoolBuilder::new()
             .thread_name(|idx|{ format!("WRWorker#{}", idx) })
             .start_handler(move |idx| {
                 register_thread_with_profiler(format!("WRWorker#{}", idx));
             })
             .build();
         let workers = Arc::new(worker.unwrap());
@@ -545,29 +684,28 @@ mod test_glyph_rasterizer {
         glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0));
 
         let font = FontInstance::new(
             font_key,
             Au::from_px(32),
             ColorF::new(0.0, 0.0, 0.0, 1.0),
             ColorU::new(0, 0, 0, 0),
             FontRenderMode::Subpixel,
-            SubpixelDirection::Horizontal,
             Default::default(),
             None,
             Vec::new(),
         );
+        let subpx_dir = font.get_subpx_dir();
 
         let mut glyph_keys = Vec::with_capacity(200);
         for i in 0 .. 200 {
             glyph_keys.push(GlyphKey::new(
                 i,
-                LayoutPoint::zero(),
-                font.render_mode,
-                font.subpx_dir,
+                DevicePoint::zero(),
+                subpx_dir,
             ));
         }
 
         for i in 0 .. 4 {
             glyph_rasterizer.request_glyphs(
                 &mut glyph_cache,
                 font.clone(),
                 &glyph_keys[(50 * i) .. (50 * (i + 1))],
@@ -585,9 +723,53 @@ mod test_glyph_rasterizer {
             &mut glyph_cache,
             &mut TextureCache::new(4096),
             &mut gpu_cache,
             &mut render_task_cache,
             &mut render_task_tree,
             &mut TextureCacheProfileCounters::new(),
         );
     }
-}
\ No newline at end of file
+
+    #[test]
+    fn test_subpx_quantize() {
+        use glyph_rasterizer::SubpixelOffset;
+
+        assert_eq!(SubpixelOffset::quantize(0.0), SubpixelOffset::Zero);
+        assert_eq!(SubpixelOffset::quantize(-0.0), SubpixelOffset::Zero);
+
+        assert_eq!(SubpixelOffset::quantize(0.1), SubpixelOffset::Zero);
+        assert_eq!(SubpixelOffset::quantize(0.01), SubpixelOffset::Zero);
+        assert_eq!(SubpixelOffset::quantize(0.05), SubpixelOffset::Zero);
+        assert_eq!(SubpixelOffset::quantize(0.12), SubpixelOffset::Zero);
+        assert_eq!(SubpixelOffset::quantize(0.124), SubpixelOffset::Zero);
+
+        assert_eq!(SubpixelOffset::quantize(0.125), SubpixelOffset::Quarter);
+        assert_eq!(SubpixelOffset::quantize(0.2), SubpixelOffset::Quarter);
+        assert_eq!(SubpixelOffset::quantize(0.25), SubpixelOffset::Quarter);
+        assert_eq!(SubpixelOffset::quantize(0.33), SubpixelOffset::Quarter);
+        assert_eq!(SubpixelOffset::quantize(0.374), SubpixelOffset::Quarter);
+
+        assert_eq!(SubpixelOffset::quantize(0.375), SubpixelOffset::Half);
+        assert_eq!(SubpixelOffset::quantize(0.4), SubpixelOffset::Half);
+        assert_eq!(SubpixelOffset::quantize(0.5), SubpixelOffset::Half);
+        assert_eq!(SubpixelOffset::quantize(0.58), SubpixelOffset::Half);
+        assert_eq!(SubpixelOffset::quantize(0.624), SubpixelOffset::Half);
+
+        assert_eq!(SubpixelOffset::quantize(0.625), SubpixelOffset::ThreeQuarters);
+        assert_eq!(SubpixelOffset::quantize(0.67), SubpixelOffset::ThreeQuarters);
+        assert_eq!(SubpixelOffset::quantize(0.7), SubpixelOffset::ThreeQuarters);
+        assert_eq!(SubpixelOffset::quantize(0.78), SubpixelOffset::ThreeQuarters);
+        assert_eq!(SubpixelOffset::quantize(0.874), SubpixelOffset::ThreeQuarters);
+
+        assert_eq!(SubpixelOffset::quantize(0.875), SubpixelOffset::Zero);
+        assert_eq!(SubpixelOffset::quantize(0.89), SubpixelOffset::Zero);
+        assert_eq!(SubpixelOffset::quantize(0.91), SubpixelOffset::Zero);
+        assert_eq!(SubpixelOffset::quantize(0.967), SubpixelOffset::Zero);
+        assert_eq!(SubpixelOffset::quantize(0.999), SubpixelOffset::Zero);
+
+        assert_eq!(SubpixelOffset::quantize(-1.0), SubpixelOffset::Zero);
+        assert_eq!(SubpixelOffset::quantize(1.0), SubpixelOffset::Zero);
+        assert_eq!(SubpixelOffset::quantize(1.5), SubpixelOffset::Half);
+        assert_eq!(SubpixelOffset::quantize(-1.625), SubpixelOffset::Half);
+        assert_eq!(SubpixelOffset::quantize(-4.33), SubpixelOffset::ThreeQuarters);
+    }
+}
--- a/gfx/webrender/src/glyph_rasterizer/no_pathfinder.rs
+++ b/gfx/webrender/src/glyph_rasterizer/no_pathfinder.rs
@@ -1,23 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Module only available when pathfinder is deactivated when webrender is
 //! compiled regularly (i.e. any configuration without feature = "pathfinder")
 
-use api::{GlyphKey, ImageData, ImageDescriptor, ImageFormat};
+use api::{ImageData, ImageDescriptor, ImageFormat};
 use device::TextureFilter;
 use euclid::size2;
 use gpu_types::UvRectKind;
 use rayon::prelude::*;
 use std::sync::{Arc, MutexGuard};
 use platform::font::FontContext;
-use glyph_rasterizer::{FontInstance, FontContexts, GlyphRasterizer, GlyphRasterJob, GlyphRasterJobs, GlyphRasterResult};
+use glyph_rasterizer::{FontInstance, FontContexts, GlyphKey};
+use glyph_rasterizer::{GlyphRasterizer, GlyphRasterJob, GlyphRasterJobs, GlyphRasterResult};
 use glyph_cache::{GlyphCache, CachedGlyphInfo, GlyphCacheEntry};
 use texture_cache::{TextureCache, TextureCacheHandle};
 use gpu_cache::GpuCache;
 use render_task::{RenderTaskTree, RenderTaskCache};
 use tiling::SpecialRenderPasses;
 use profiler::TextureCacheProfileCounters;
 use std::collections::hash_map::Entry;
 
@@ -197,9 +198,9 @@ impl GlyphRasterizer {
                 glyph_key_cache.insert(key, glyph_info);
             }
         }
 
         // Now that we are done with the critical path (rendering the glyphs),
         // we can schedule removing the fonts if needed.
         self.remove_dead_fonts();
     }
-}
\ No newline at end of file
+}
--- a/gfx/webrender/src/glyph_rasterizer/pathfinder.rs
+++ b/gfx/webrender/src/glyph_rasterizer/pathfinder.rs
@@ -1,31 +1,31 @@
 /* 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/. */
 
 //! Module only available when pathfinder is activated
 
-use api::{DeviceIntPoint, DeviceIntSize, DevicePixel, FontRenderMode, FontKey, FontTemplate, GlyphKey, NativeFontHandle};
+use api::{DeviceIntPoint, DeviceIntSize, DevicePixel, FontRenderMode, FontKey, FontTemplate, NativeFontHandle};
 use euclid::{TypedPoint2D, TypedSize2D, TypedVector2D};
 use pathfinder_font_renderer;
 use pathfinder_partitioner::mesh::Mesh as PathfinderMesh;
 use pathfinder_path_utils::cubic_to_quadratic::CubicToQuadraticTransformer;
 use render_task::{RenderTask, RenderTaskTree, RenderTaskCache, RenderTaskCacheKey, RenderTaskCacheEntryHandle,
                   RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use resource_cache::CacheItem;
 use std::ops::Deref;
 use std::sync::{Arc, Mutex, MutexGuard};
 use tiling::{RenderTargetKind, SpecialRenderPasses};
 use glyph_rasterizer::AddFont;
 use internal_types::ResourceCacheError;
 use glyph_cache::{GlyphCache, GlyphCacheEntry, CachedGlyphInfo};
 use std::collections::hash_map::Entry;
 use std::f32;
-use glyph_rasterizer::{FontInstance, GlyphRasterizer, GlyphFormat, FontContexts};
+use glyph_rasterizer::{FontInstance, GlyphRasterizer, GlyphFormat, GlyphKey, FontContexts};
 use texture_cache::TextureCache;
 use gpu_cache::GpuCache;
 use profiler::TextureCacheProfileCounters;
 
 /// Should match macOS 10.13 High Sierra.
 ///
 /// We multiply by sqrt(2) to compensate for the fact that dilation amounts are relative to the
 /// pixel square on macOS and relative to the vertex normal in Pathfinder.
@@ -185,18 +185,19 @@ impl GlyphRasterizer {
                 None => {
                     let mut pathfinder_font_context = self.font_contexts.lock_pathfinder_context();
 
                     let pathfinder_font_instance = pathfinder_font_renderer::FontInstance {
                         font_key: font.font_key.clone(),
                         size: font.size,
                     };
 
+                    // TODO: pathfinder will need to support 2D subpixel offset
                     let pathfinder_subpixel_offset =
-                        pathfinder_font_renderer::SubpixelOffset(glyph_key.subpixel_offset as u8);
+                        pathfinder_font_renderer::SubpixelOffset(glyph_key.subpixel_offset.0 as u8);
                     let pathfinder_glyph_key =
                         pathfinder_font_renderer::GlyphKey::new(glyph_key.index,
                                                                 pathfinder_subpixel_offset);
                     let glyph_dimensions =
                         match pathfinder_font_context.glyph_dimensions(&pathfinder_font_instance,
                                                                        &pathfinder_glyph_key,
                                                                        false) {
                             Ok(glyph_dimensions) => glyph_dimensions,
@@ -267,19 +268,20 @@ fn request_render_task_from_pathfinder(g
                                        render_tasks: &mut RenderTaskTree,
                                        render_passes: &mut SpecialRenderPasses)
                                        -> Result<RenderTaskId, ()> {
     let pathfinder_font_instance = pathfinder_font_renderer::FontInstance {
         font_key: font.font_key.clone(),
         size: font.size,
     };
 
+    // TODO: pathfinder will need to support 2D subpixel offset
     let pathfinder_subpixel_offset =
-        pathfinder_font_renderer::SubpixelOffset(glyph_key.subpixel_offset as u8);
-    let glyph_subpixel_offset: f64 = glyph_key.subpixel_offset.into();
+        pathfinder_font_renderer::SubpixelOffset(glyph_key.subpixel_offset.0 as u8);
+    let glyph_subpixel_offset: f64 = glyph_key.subpixel_offset.0.into();
     let pathfinder_glyph_key = pathfinder_font_renderer::GlyphKey::new(glyph_key.index,
                                                                        pathfinder_subpixel_offset);
 
     // TODO(pcwalton): Fall back to CPU rendering if Pathfinder fails to collect the outline.
     let mut mesh = PathfinderMesh::new();
     let outline = try!(font_context.glyph_outline(&pathfinder_font_instance,
                                                   &pathfinder_glyph_key));
     let tolerance = CUBIC_TO_QUADRATIC_APPROX_TOLERANCE;
@@ -306,9 +308,9 @@ fn request_render_task_from_pathfinder(g
         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)
 }
 
-pub struct NativeFontHandleWrapper<'a>(pub &'a NativeFontHandle);
\ No newline at end of file
+pub struct NativeFontHandleWrapper<'a>(pub &'a NativeFontHandle);
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -1,13 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{DevicePoint, LayoutToWorldTransform, WorldToLayoutTransform};
+use api::{DevicePoint, DeviceSize, DeviceRect, LayoutToWorldTransform};
+use api::{PremultipliedColorF, WorldToLayoutTransform};
 use gpu_cache::{GpuCacheAddress, GpuDataRequest};
 use prim_store::{VECS_PER_SEGMENT, EdgeAaSegmentMask};
 use render_task::RenderTaskAddress;
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 
 // Contains type that must exactly match the same structures declared in GLSL.
 
 const INT_BITS: usize = 31; //TODO: convert to unsigned
@@ -74,16 +75,45 @@ pub enum BlurDirection {
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BlurInstance {
     pub task_address: RenderTaskAddress,
     pub src_task_address: RenderTaskAddress,
     pub blur_direction: BlurDirection,
 }
 
+#[derive(Debug, Copy, Clone)]
+#[repr(C)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum BorderSegment {
+    TopLeft,
+    TopRight,
+    BottomRight,
+    BottomLeft,
+    Left,
+    Top,
+    Right,
+    Bottom,
+}
+
+#[derive(Debug, Clone)]
+#[repr(C)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BorderInstance {
+    pub task_origin: DevicePoint,
+    pub local_rect: DeviceRect,
+    pub color0: PremultipliedColorF,
+    pub color1: PremultipliedColorF,
+    pub flags: i32,
+    pub widths: DeviceSize,
+    pub radius: DeviceSize,
+}
+
 /// A clipping primitive drawn into the clipping mask.
 /// Could be an image or a rectangle, which defines the
 /// way `address` is treated.
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub struct ClipMaskInstance {
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -1,15 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ColorU, FontKey, FontRenderMode, GlyphDimensions};
 use api::{FontInstanceFlags, FontVariation, NativeFontHandle};
-use api::{GlyphKey, SubpixelDirection};
 use app_units::Au;
 use core_foundation::array::{CFArray, CFArrayRef};
 use core_foundation::base::TCFType;
 use core_foundation::dictionary::CFDictionary;
 use core_foundation::number::{CFNumber, CFNumberRef};
 use core_foundation::string::{CFString, CFStringRef};
 use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGBitmapByteOrder32Little};
 #[cfg(not(feature = "pathfinder"))]
@@ -22,17 +21,17 @@ use core_graphics::data_provider::CGData
 use core_graphics::font::{CGFont, CGGlyph};
 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};
+use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey};
 #[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;
 
@@ -458,17 +457,17 @@ impl FontContext {
     }
 
     pub fn prepare_font(font: &mut FontInstance) {
         match font.render_mode {
             FontRenderMode::Mono => {
                 // In mono mode the color of the font is irrelevant.
                 font.color = ColorU::new(255, 255, 255, 255);
                 // Subpixel positioning is disabled in mono mode.
-                font.subpx_dir = SubpixelDirection::None;
+                font.disable_subpixel_position();
             }
             FontRenderMode::Alpha => {
                 font.color = if font.flags.contains(FontInstanceFlags::FONT_SMOOTHING) {
                     // Only the G channel is used to index grayscale tables,
                     // so use R and B to preserve light/dark determination.
                     let ColorU { g, a, .. } = font.color.luminance_color().quantized_ceil();
                     let rb = if should_use_white_on_black(font.color) { 255 } else { 0 };
                     ColorU::new(rb, g, rb, a)
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -1,29 +1,29 @@
 /* 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::{ColorU, GlyphDimensions, GlyphKey, FontKey, FontRenderMode};
+use api::{ColorU, GlyphDimensions, FontKey, FontRenderMode};
 use api::{FontInstancePlatformOptions, FontLCDFilter, FontHinting};
-use api::{FontInstanceFlags, NativeFontHandle, SubpixelDirection};
+use api::{FontInstanceFlags, NativeFontHandle};
 use freetype::freetype::{FT_BBox, FT_Outline_Translate, FT_Pixel_Mode, FT_Render_Mode};
 use freetype::freetype::{FT_Done_Face, FT_Error, FT_Get_Char_Index, FT_Int32};
 use freetype::freetype::{FT_Done_FreeType, FT_Library_SetLcdFilter, FT_Pos};
 use freetype::freetype::{FT_F26Dot6, FT_Face, FT_Glyph_Format, FT_Long, FT_UInt};
 use freetype::freetype::{FT_GlyphSlot, FT_LcdFilter, FT_New_Face, FT_New_Memory_Face};
 use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph};
 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};
+use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey, 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;
@@ -248,19 +248,20 @@ impl FontContext {
             (font.transform.skew_x != 0.0 || font.transform.skew_y != 0.0)) {
             hinting = FontHinting::None;
         }
         match (hinting, font.render_mode) {
             (FontHinting::None, _) => load_flags |= FT_LOAD_NO_HINTING,
             (FontHinting::Mono, _) => load_flags = FT_LOAD_TARGET_MONO,
             (FontHinting::Light, _) => load_flags = FT_LOAD_TARGET_LIGHT,
             (FontHinting::LCD, FontRenderMode::Subpixel) => {
-                load_flags = match font.subpx_dir {
-                    SubpixelDirection::Vertical => FT_LOAD_TARGET_LCD_V,
-                    _ => FT_LOAD_TARGET_LCD,
+                load_flags = if font.flags.contains(FontInstanceFlags::LCD_VERTICAL) {
+                    FT_LOAD_TARGET_LCD_V
+                } else {
+                    FT_LOAD_TARGET_LCD
                 };
                 if font.flags.contains(FontInstanceFlags::FORCE_AUTOHINT) {
                     load_flags |= FT_LOAD_FORCE_AUTOHINT;
                 }
             }
             _ => {
                 if font.flags.contains(FontInstanceFlags::FORCE_AUTOHINT) {
                     load_flags |= FT_LOAD_FORCE_AUTOHINT;
@@ -370,41 +371,39 @@ impl FontContext {
             FT_Outline_Get_CBox(&(*slot).outline, &mut cbox);
 
             // For spaces and other non-printable characters, early out.
             if (*slot).outline.n_contours == 0 {
                 return cbox;
             }
         }
 
-        // Convert the subpixel offset to floats.
-        let (dx, dy) = font.get_subpx_offset(glyph);
-
         // Apply extra pixel of padding for subpixel AA, due to the filter.
-        let padding = match font.render_mode {
-            FontRenderMode::Subpixel => (self.lcd_extra_pixels * 64) as FT_Pos,
-            FontRenderMode::Alpha |
-            FontRenderMode::Mono => 0 as FT_Pos,
-        };
+        if font.render_mode == FontRenderMode::Subpixel {
+            let padding = (self.lcd_extra_pixels * 64) as FT_Pos;
+            if font.flags.contains(FontInstanceFlags::LCD_VERTICAL) {
+                cbox.yMin -= padding;
+                cbox.yMax += padding;
+            } else {
+                cbox.xMin -= padding;
+                cbox.xMax += padding;
+            }
+        }
 
         // Offset the bounding box by subpixel positioning.
         // Convert to 26.6 fixed point format for FT.
-        match font.subpx_dir {
-            SubpixelDirection::None => {}
-            SubpixelDirection::Horizontal => {
-                let dx = (dx * 64.0 + 0.5) as FT_Long;
-                cbox.xMin += dx - padding;
-                cbox.xMax += dx + padding;
-            }
-            SubpixelDirection::Vertical => {
-                let dy = (dy * 64.0 + 0.5) as FT_Long;
-                cbox.yMin += dy - padding;
-                cbox.yMax += dy + padding;
-            }
-        }
+        let (dx, dy) = font.get_subpx_offset(glyph);
+        let (dx, dy) = (
+            (dx * 64.0 + 0.5) as FT_Pos,
+            -(dy * 64.0 + 0.5) as FT_Pos,
+        );
+        cbox.xMin += dx;
+        cbox.xMax += dx;
+        cbox.yMin += dy;
+        cbox.yMax += dy;
 
         // Outset the box to device pixel boundaries
         cbox.xMin &= !63;
         cbox.yMin &= !63;
         cbox.xMax = (cbox.xMax + 63) & !63;
         cbox.yMax = (cbox.yMax + 63) & !63;
 
         cbox
@@ -517,35 +516,37 @@ impl FontContext {
     }
 
     pub fn prepare_font(font: &mut FontInstance) {
         match font.render_mode {
             FontRenderMode::Mono => {
                 // In mono mode the color of the font is irrelevant.
                 font.color = ColorU::new(0xFF, 0xFF, 0xFF, 0xFF);
                 // Subpixel positioning is disabled in mono mode.
-                font.subpx_dir = SubpixelDirection::None;
+                font.disable_subpixel_position();
             }
             FontRenderMode::Alpha | FontRenderMode::Subpixel => {
                 // We don't do any preblending with FreeType currently, so the color is not used.
                 font.color = ColorU::new(0xFF, 0xFF, 0xFF, 0xFF);
             }
         }
     }
 
     fn rasterize_glyph_outline(
         &mut self,
         slot: FT_GlyphSlot,
         font: &FontInstance,
         key: &GlyphKey,
     ) -> bool {
         // Get the subpixel offsets in FT 26.6 format.
         let (dx, dy) = font.get_subpx_offset(key);
-        let dx = (dx * 64.0 + 0.5) as FT_Long;
-        let dy = (dy * 64.0 + 0.5) as FT_Long;
+        let (dx, dy) = (
+            (dx * 64.0 + 0.5) as FT_Pos,
+            -(dy * 64.0 + 0.5) as FT_Pos,
+        );
 
         // Move the outline curves to be at the origin, taking
         // into account the subpixel positioning.
         unsafe {
             let outline = &(*slot).outline;
             let mut cbox: FT_BBox = mem::uninitialized();
             FT_Outline_Get_CBox(outline, &mut cbox);
             FT_Outline_Translate(
@@ -560,21 +561,24 @@ impl FontContext {
             let filter = match lcd_filter {
                 FontLCDFilter::None => FT_LcdFilter::FT_LCD_FILTER_NONE,
                 FontLCDFilter::Default => FT_LcdFilter::FT_LCD_FILTER_DEFAULT,
                 FontLCDFilter::Light => FT_LcdFilter::FT_LCD_FILTER_LIGHT,
                 FontLCDFilter::Legacy => FT_LcdFilter::FT_LCD_FILTER_LEGACY,
             };
             unsafe { FT_Library_SetLcdFilter(self.lib, filter) };
         }
-        let render_mode = match (font.render_mode, font.subpx_dir) {
-            (FontRenderMode::Mono, _) => FT_Render_Mode::FT_RENDER_MODE_MONO,
-            (FontRenderMode::Alpha, _) => FT_Render_Mode::FT_RENDER_MODE_NORMAL,
-            (FontRenderMode::Subpixel, SubpixelDirection::Vertical) => FT_Render_Mode::FT_RENDER_MODE_LCD_V,
-            (FontRenderMode::Subpixel, _) => FT_Render_Mode::FT_RENDER_MODE_LCD,
+        let render_mode = match font.render_mode {
+            FontRenderMode::Mono => FT_Render_Mode::FT_RENDER_MODE_MONO,
+            FontRenderMode::Alpha => FT_Render_Mode::FT_RENDER_MODE_NORMAL,
+            FontRenderMode::Subpixel => if font.flags.contains(FontInstanceFlags::LCD_VERTICAL) {
+                FT_Render_Mode::FT_RENDER_MODE_LCD_V
+            } else {
+                FT_Render_Mode::FT_RENDER_MODE_LCD
+            },
         };
         let result = unsafe { FT_Render_Glyph(slot, render_mode) };
         if !succeeded(result) {
             error!("Unable to rasterize");
             debug!(
                 "{:?} with {:?}, {:?}",
                 key,
                 render_mode,
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.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::{FontInstanceFlags, FontKey, FontRenderMode};
-use api::{ColorU, GlyphDimensions, GlyphKey, SubpixelDirection};
+use api::{ColorU, GlyphDimensions};
 use dwrote;
 use gamma_lut::ColorLut;
-use glyph_rasterizer::{FontInstance, FontTransform};
+use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey};
 use internal_types::{FastHashMap, ResourceCacheError};
 use std::collections::hash_map::Entry;
 use std::sync::Arc;
 cfg_if! {
     if #[cfg(feature = "pathfinder")] {
         use pathfinder_font_renderer::{PathfinderComPtr, IDWriteFontFace};
         use glyph_rasterizer::NativeFontHandleWrapper;
     } else if #[cfg(not(feature = "pathfinder"))] {
@@ -367,17 +367,17 @@ impl FontContext {
     }
 
     pub fn prepare_font(font: &mut FontInstance) {
         match font.render_mode {
             FontRenderMode::Mono => {
                 // In mono mode the color of the font is irrelevant.
                 font.color = ColorU::new(255, 255, 255, 255);
                 // Subpixel positioning is disabled in mono mode.
-                font.subpx_dir = SubpixelDirection::None;
+                font.disable_subpixel_position();
             }
             FontRenderMode::Alpha => {
                 font.color = font.color.luminance_color().quantize();
             }
             FontRenderMode::Subpixel => {
                 font.color = font.color.quantize();
             }
         }
@@ -464,9 +464,9 @@ impl<'a> From<NativeFontHandleWrapper<'a
         let system_fc = ::dwrote::FontCollection::system();
         let font = match system_fc.get_font_from_descriptor(&font_handle.0) {
             Some(font) => font,
             None => panic!("missing descriptor {:?}", font_handle.0),
         };
         let face = font.create_font_face();
         unsafe { PathfinderComPtr::new(face.as_ptr()) }
     }
-}
\ No newline at end of file
+}
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,26 +1,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{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, TileOffset};
+use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch, ExtendMode};
+use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, TileOffset};
 use api::{GlyphRasterSpace, LayoutPoint, LayoutRect, LayoutSize, LayoutToWorldTransform, LayoutVector2D};
 use api::{PipelineId, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat, DeviceIntSideOffsets};
-use border::{BorderCornerInstance, BorderEdgeKind};
+use api::{BorderWidths, LayoutToWorldScale, NormalBorder};
+use app_units::Au;
+use border::{BorderCacheKey, BorderCornerInstance, BorderRenderTaskInfo, 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;
-use glyph_rasterizer::{FontInstance, FontTransform};
+use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::{BrushFlags, ClipChainRectIndex};
 use image::{for_each_tile, for_each_repetition};
 use picture::{PictureCompositeMode, PictureId, PicturePrimitive};
 #[cfg(debug_assertions)]
 use render_backend::FrameId;
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey};
@@ -251,16 +253,28 @@ pub struct VisibleImageTile {
 }
 
 #[derive(Debug)]
 pub struct VisibleGradientTile {
     pub handle: GpuCacheHandle,
 }
 
 #[derive(Debug)]
+pub enum BorderSource {
+    Image(ImageRequest),
+    Border {
+        handle: Option<RenderTaskCacheEntryHandle>,
+        cache_key: BorderCacheKey,
+        task_info: Option<BorderRenderTaskInfo>,
+        border: NormalBorder,
+        widths: BorderWidths,
+    },
+}
+
+#[derive(Debug)]
 pub enum BrushKind {
     Solid {
         color: ColorF,
         opacity_binding: OpacityBinding,
     },
     Clear,
     Picture {
         pic_index: PictureIndex,
@@ -302,17 +316,17 @@ pub enum BrushKind {
         reverse_stops: bool,
         start_point: LayoutPoint,
         end_point: LayoutPoint,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         visible_tiles: Vec<VisibleGradientTile>,
     },
     Border {
-        request: ImageRequest,
+        source: BorderSource,
     },
 }
 
 impl BrushKind {
     fn supports_segments(&self) -> bool {
         match *self {
             BrushKind::Solid { .. } |
             BrushKind::Image { .. } |
@@ -348,53 +362,52 @@ bitflags! {
     pub struct EdgeAaSegmentMask: u8 {
         const LEFT = 0x1;
         const TOP = 0x2;
         const RIGHT = 0x4;
         const BOTTOM = 0x8;
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub enum BrushSegmentTaskId {
     RenderTaskId(RenderTaskId),
     Opaque,
     Empty,
 }
 
 impl BrushSegmentTaskId {
     pub fn needs_blending(&self) -> bool {
         match *self {
             BrushSegmentTaskId::RenderTaskId(..) => true,
             BrushSegmentTaskId::Opaque | BrushSegmentTaskId::Empty => false,
         }
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct BrushSegment {
     pub local_rect: LayoutRect,
     pub clip_task_id: BrushSegmentTaskId,
     pub may_need_clip_mask: bool,
     pub edge_flags: EdgeAaSegmentMask,
     pub extra_data: [f32; 4],
     pub brush_flags: BrushFlags,
 }
 
 impl BrushSegment {
     pub fn new(
-        origin: LayoutPoint,
-        size: LayoutSize,
+        rect: LayoutRect,
         may_need_clip_mask: bool,
         edge_flags: EdgeAaSegmentMask,
         extra_data: [f32; 4],
         brush_flags: BrushFlags,
     ) -> BrushSegment {
         BrushSegment {
-            local_rect: LayoutRect::new(origin, size),
+            local_rect: rect,
             clip_task_id: BrushSegmentTaskId::Opaque,
             may_need_clip_mask,
             edge_flags,
             extra_data,
             brush_flags,
         }
     }
 }
@@ -756,68 +769,93 @@ impl<'a> GradientGpuBlockBuilder<'a> {
 
 #[derive(Debug, Clone)]
 pub struct TextRunPrimitiveCpu {
     pub font: FontInstance,
     pub offset: LayoutVector2D,
     pub glyph_range: ItemRange<GlyphInstance>,
     pub glyph_keys: Vec<GlyphKey>,
     pub glyph_gpu_blocks: Vec<GpuBlockData>,
+    pub glyph_transform: (DevicePixelScale, FontTransform),
     pub shadow: bool,
     pub glyph_raster_space: GlyphRasterSpace,
 }
 
 impl TextRunPrimitiveCpu {
+    pub fn new(
+        font: FontInstance,
+        offset: LayoutVector2D,
+        glyph_range: ItemRange<GlyphInstance>,
+        glyph_keys: Vec<GlyphKey>,
+        shadow: bool,
+        glyph_raster_space: GlyphRasterSpace,
+    ) -> Self {
+        TextRunPrimitiveCpu {
+            font,
+            offset,
+            glyph_range,
+            glyph_keys,
+            glyph_gpu_blocks: Vec::new(),
+            glyph_transform: (DevicePixelScale::new(1.0), FontTransform::identity()),
+            shadow,
+            glyph_raster_space,
+        }
+    }
+
     pub fn get_font(
         &self,
         device_pixel_scale: DevicePixelScale,
         transform: Option<LayoutToWorldTransform>,
     ) -> FontInstance {
         let mut font = self.font.clone();
         font.size = font.size.scale_by(device_pixel_scale.0);
         if let Some(transform) = transform {
             if transform.has_perspective_component() ||
                !transform.has_2d_inverse() ||
                self.glyph_raster_space != GlyphRasterSpace::Screen {
-                font.render_mode = font.render_mode.limit_by(FontRenderMode::Alpha);
+                font.disable_subpixel_aa();
+                font.disable_subpixel_position();
             } else {
                 font.transform = FontTransform::from(&transform).quantize();
             }
         }
         font
     }
 
     fn prepare_for_render(
         &mut self,
         device_pixel_scale: DevicePixelScale,
         transform: Option<LayoutToWorldTransform>,
         allow_subpixel_aa: bool,
         display_list: &BuiltDisplayList,
         frame_building_state: &mut FrameBuildingState,
     ) {
         if !allow_subpixel_aa && self.font.bg_color.a == 0 {
-            self.font.render_mode = self.font.render_mode.limit_by(FontRenderMode::Alpha);
+            self.font.disable_subpixel_aa();
         }
 
         let font = self.get_font(device_pixel_scale, transform);
 
         // Cache the glyph positions, if not in the cache already.
         // TODO(gw): In the future, remove `glyph_instances`
         //           completely, and just reference the glyphs
         //           directly from the display list.
-        if self.glyph_keys.is_empty() {
-            let subpx_dir = font.subpx_dir.limit_by(font.render_mode);
+        if self.glyph_keys.is_empty() || self.glyph_transform != (device_pixel_scale, font.transform) {
+            let subpx_dir = font.get_subpx_dir();
             let src_glyphs = display_list.get(self.glyph_range);
 
             // TODO(gw): If we support chunks() on AuxIter
             //           in the future, this code below could
             //           be much simpler...
             let mut gpu_block = [0.0; 4];
             for (i, src) in src_glyphs.enumerate() {
-                let key = GlyphKey::new(src.index, src.point, font.render_mode, subpx_dir);
+                let layout_offset = src.point + self.offset;
+                let world_offset = font.transform.transform(&layout_offset);
+                let device_offset = device_pixel_scale.transform_point(&world_offset);
+                let key = GlyphKey::new(src.index, device_offset, subpx_dir);
                 self.glyph_keys.push(key);
 
                 // Two glyphs are packed per GPU block.
 
                 if (i & 1) == 0 {
                     gpu_block[0] = src.point.x;
                     gpu_block[1] = src.point.y;
                 } else {
@@ -827,16 +865,18 @@ impl TextRunPrimitiveCpu {
                 }
             }
 
             // Ensure the last block is added in the case
             // of an odd number of glyphs.
             if (self.glyph_keys.len() & 1) != 0 {
                 self.glyph_gpu_blocks.push(gpu_block.into());
             }
+
+            self.glyph_transform = (device_pixel_scale, font.transform);
         }
 
         frame_building_state.resource_cache
                             .request_glyphs(font,
                                             &self.glyph_keys,
                                             frame_building_state.gpu_cache,
                                             frame_building_state.render_tasks,
                                             frame_building_state.special_render_passes);
@@ -1085,35 +1125,32 @@ impl PrimitiveContainer {
     }
 
     // Create a clone of this PrimitiveContainer, applying whatever
     // changes are necessary to the primitive to support rendering
     // it as part of the supplied shadow.
     pub fn create_shadow(&self, shadow: &Shadow) -> PrimitiveContainer {
         match *self {
             PrimitiveContainer::TextRun(ref info) => {
-                let mut render_mode = info.font.render_mode;
-
+                let mut font = FontInstance {
+                    color: shadow.color.into(),
+                    ..info.font.clone()
+                };
                 if shadow.blur_radius > 0.0 {
-                    render_mode = render_mode.limit_by(FontRenderMode::Alpha);
+                    font.disable_subpixel_aa();
                 }
 
-                PrimitiveContainer::TextRun(TextRunPrimitiveCpu {
-                    font: FontInstance {
-                        color: shadow.color.into(),
-                        render_mode,
-                        ..info.font.clone()
-                    },
-                    offset: info.offset + shadow.offset,
-                    glyph_range: info.glyph_range,
-                    glyph_keys: info.glyph_keys.clone(),
-                    glyph_gpu_blocks: Vec::new(),
-                    shadow: true,
-                    glyph_raster_space: info.glyph_raster_space,
-                })
+                PrimitiveContainer::TextRun(TextRunPrimitiveCpu::new(
+                    font,
+                    info.offset + shadow.offset,
+                    info.glyph_range,
+                    info.glyph_keys.clone(),
+                    true,
+                    info.glyph_raster_space,
+                ))
             }
             PrimitiveContainer::Brush(ref brush) => {
                 match brush.kind {
                     BrushKind::Solid { .. } => {
                         PrimitiveContainer::Brush(BrushPrimitive::new(
                             BrushKind::new_solid(shadow.color),
                             None,
                         ))
@@ -1396,16 +1433,94 @@ impl PrimitiveStore {
     pub fn get_metadata(&self, index: PrimitiveIndex) -> &PrimitiveMetadata {
         &self.cpu_metadata[index.0]
     }
 
     pub fn prim_count(&self) -> usize {
         self.cpu_metadata.len()
     }
 
+    fn build_prim_segments_if_needed(
+        &mut self,
+        prim_index: PrimitiveIndex,
+        pic_state: &mut PictureState,
+        frame_state: &mut FrameBuildingState,
+        frame_context: &FrameBuildingContext,
+    ) {
+        let metadata = &self.cpu_metadata[prim_index.0];
+
+        if metadata.prim_kind != PrimitiveKind::Brush {
+            return;
+        }
+
+        let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0];
+
+        if let BrushKind::Border { ref mut source, .. } = brush.kind {
+            if let BorderSource::Border {
+                ref border,
+                ref mut cache_key,
+                ref widths,
+                ref mut handle,
+                ref mut task_info,
+                ..
+            } = *source {
+                // TODO(gw): When drawing in screen raster mode, we should also incorporate a
+                //           scale factor from the world transform to get an appropriately
+                //           sized border task.
+                let world_scale = LayoutToWorldScale::new(1.0);
+                let scale = world_scale * frame_context.device_pixel_scale;
+                let scale_au = Au::from_f32_px(scale.0);
+                let needs_update = scale_au != cache_key.scale;
+
+                if needs_update {
+                    cache_key.scale = scale_au;
+
+                    *task_info = Some(BorderRenderTaskInfo::new(
+                        &metadata.local_rect,
+                        border,
+                        widths,
+                        scale,
+                    ));
+                }
+
+                let task_info = task_info.as_ref().unwrap();
+
+                *handle = Some(frame_state.resource_cache.request_render_task(
+                    RenderTaskCacheKey {
+                        size: DeviceIntSize::zero(),
+                        kind: RenderTaskCacheKeyKind::Border(cache_key.clone()),
+                    },
+                    frame_state.gpu_cache,
+                    frame_state.render_tasks,
+                    None,
+                    false,          // todo
+                    |render_tasks| {
+                        let task = RenderTask::new_border(
+                            task_info.size,
+                            task_info.instances.clone(),
+                        );
+
+                        let task_id = render_tasks.add(task);
+
+                        pic_state.tasks.push(task_id);
+
+                        task_id
+                    }
+                ));
+
+                if needs_update {
+                    brush.segment_desc = Some(BrushSegmentDescriptor {
+                        segments: task_info.segments.clone(),
+                        clip_mask_kind: BrushClipMaskKind::Unknown,
+                    });
+                }
+            }
+        }
+    }
+
     fn prepare_prim_for_render_inner(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_run_context: &PrimitiveRunContext,
         pic_state_for_children: PictureState,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
@@ -1658,31 +1773,39 @@ impl PrimitiveStore {
                                     key: yuv_key[channel],
                                     rendering: image_rendering,
                                     tile: None,
                                 },
                                 frame_state.gpu_cache,
                             );
                         }
                     }
-                    BrushKind::Border { request, .. } => {
-                        let image_properties = frame_state
-                            .resource_cache
-                            .get_image_properties(request.key);
+                    BrushKind::Border { ref mut source, .. } => {
+                        match *source {
+                            BorderSource::Image(request) => {
+                                let image_properties = frame_state
+                                    .resource_cache
+                                    .get_image_properties(request.key);
 
-                        if let Some(image_properties) = image_properties {
-                            // Update opacity for this primitive to ensure the correct
-                            // batching parameters are used.
-                            metadata.opacity.is_opaque =
-                                image_properties.descriptor.is_opaque;
+                                if let Some(image_properties) = image_properties {
+                                    // Update opacity for this primitive to ensure the correct
+                                    // batching parameters are used.
+                                    metadata.opacity.is_opaque =
+                                        image_properties.descriptor.is_opaque;
 
-                            frame_state.resource_cache.request_image(
-                                request,
-                                frame_state.gpu_cache,
-                            );
+                                    frame_state.resource_cache.request_image(
+                                        request,
+                                        frame_state.gpu_cache,
+                                    );
+                                }
+                            }
+                            BorderSource::Border { .. } => {
+                                // Handled earlier since we need to update the segment
+                                // descriptor *before* update_clip_task() is called.
+                            }
                         }
                     }
                     BrushKind::RadialGradient {
                         gradient_index,
                         stops_range,
                         center,
                         start_radius,
                         end_radius,
@@ -1999,18 +2122,17 @@ impl PrimitiveStore {
                     //           patterns of this and the segment
                     //           builder significantly better, by
                     //           retaining it across primitives.
                     let mut segments = Vec::new();
 
                     segment_builder.build(|segment| {
                         segments.push(
                             BrushSegment::new(
-                                segment.rect.origin,
-                                segment.rect.size,
+                                segment.rect,
                                 segment.has_mask,
                                 segment.edge_flags,
                                 [0.0; 4],
                                 BrushFlags::empty(),
                             ),
                         );
                     });
 
@@ -2385,16 +2507,23 @@ impl PrimitiveStore {
                 clipped,
                 unclipped,
             });
             metadata.clip_chain_rect_index = prim_run_context.clip_chain_rect_index;
 
             (local_rect, unclipped)
         };
 
+        self.build_prim_segments_if_needed(
+            prim_index,
+            pic_state,
+            frame_state,
+            frame_context,
+        );
+
         if may_need_clip_mask && !self.update_clip_task(
             prim_index,
             prim_run_context,
             &unclipped_device_rect,
             pic_state,
             frame_context,
             frame_state,
         ) {
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -808,21 +808,21 @@ impl RenderBackend {
             }
             ApiMsg::FlushSceneBuilder(tx) => {
                 self.scene_tx.send(SceneBuilderRequest::Flush(tx)).unwrap();
             }
             ApiMsg::UpdateResources(updates) => {
                 self.resource_cache
                     .update_resources(updates, &mut profile_counters.resources);
             }
-            ApiMsg::GetGlyphDimensions(instance_key, glyph_keys, tx) => {
-                let mut glyph_dimensions = Vec::with_capacity(glyph_keys.len());
+            ApiMsg::GetGlyphDimensions(instance_key, glyph_indices, tx) => {
+                let mut glyph_dimensions = Vec::with_capacity(glyph_indices.len());
                 if let Some(font) = self.resource_cache.get_font_instance(instance_key) {
-                    for glyph_key in &glyph_keys {
-                        let glyph_dim = self.resource_cache.get_glyph_dimensions(&font, glyph_key);
+                    for glyph_index in &glyph_indices {
+                        let glyph_dim = self.resource_cache.get_glyph_dimensions(&font, *glyph_index);
                         glyph_dimensions.push(glyph_dim);
                     }
                 }
                 tx.send(glyph_dimensions).unwrap();
             }
             ApiMsg::GetGlyphIndices(font_key, text, tx) => {
                 let mut glyph_indices = Vec::new();
                 for ch in text.chars() {
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,25 +1,26 @@
 /* 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, DeviceIntSideOffsets, ImageDescriptor, ImageFormat};
 #[cfg(feature = "pathfinder")]
 use api::FontRenderMode;
+use border::BorderCacheKey;
 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, WeakFreeListHandle};
 use glyph_rasterizer::GpuGlyphCacheKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
-use gpu_types::{ImageSource, RasterizationSpace, UvRectKind};
+use gpu_types::{BorderInstance, ImageSource, RasterizationSpace, UvRectKind};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use picture::PictureCacheKey;
 use prim_store::{PrimitiveIndex, ImageCacheKey};
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
 use render_backend::FrameId;
@@ -247,16 +248,23 @@ pub enum BlitSource {
     RenderTask {
         task_id: RenderTaskId,
     },
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BorderTask {
+    pub instances: Vec<BorderInstance>,
+}
+
+#[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))]
@@ -273,16 +281,17 @@ pub enum RenderTaskKind {
     ClipRegion(ClipRegionTask),
     VerticalBlur(BlurTask),
     HorizontalBlur(BlurTask),
     #[allow(dead_code)]
     Glyph(GlyphTask),
     Readback(DeviceIntRect),
     Scaling(RenderTargetKind),
     Blit(BlitTask),
+    Border(BorderTask),
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ClearMode {
     // Applicable to color and alpha targets.
     Zero,
@@ -552,16 +561,31 @@ impl RenderTask {
                 uv_rect_handle: GpuCacheHandle::new(),
                 uv_rect_kind,
             }),
             clear_mode,
             saved_index: None,
         }
     }
 
+    pub fn new_border(
+        size: DeviceIntSize,
+        instances: Vec<BorderInstance>,
+    ) -> Self {
+        RenderTask {
+            children: Vec::new(),
+            location: RenderTaskLocation::Dynamic(None, Some(size)),
+            kind: RenderTaskKind::Border(BorderTask {
+                instances,
+            }),
+            clear_mode: ClearMode::Transparent,
+            saved_index: None,
+        }
+    }
+
     pub fn new_scaling(
         target_kind: RenderTargetKind,
         src_task_id: RenderTaskId,
         target_size: DeviceIntSize,
     ) -> Self {
         RenderTask {
             children: vec![src_task_id],
             location: RenderTaskLocation::Dynamic(None, Some(target_size)),
@@ -611,16 +635,17 @@ impl RenderTask {
 
             RenderTaskKind::VerticalBlur(ref task) |
             RenderTaskKind::HorizontalBlur(ref task) => {
                 task.uv_rect_kind
             }
 
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Glyph(_) |
+            RenderTaskKind::Border(..) |
             RenderTaskKind::Blit(..) => {
                 UvRectKind::Rect
             }
         }
     }
 
     // Write (up to) 8 floats of data specific to the type
     // of render task that is provided to the GPU shaders
@@ -665,16 +690,17 @@ impl RenderTask {
                     0.0,
                 ]
             }
             RenderTaskKind::Glyph(_) => {
                 [1.0, 0.0, 0.0]
             }
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
+            RenderTaskKind::Border(..) |
             RenderTaskKind::Blit(..) => {
                 [0.0; 3]
             }
         };
 
         let (target_rect, target_index) = self.get_target_rect();
 
         RenderTaskData {
@@ -699,16 +725,17 @@ impl RenderTask {
             RenderTaskKind::VerticalBlur(ref info) |
             RenderTaskKind::HorizontalBlur(ref info) => {
                 gpu_cache.get_address(&info.uv_rect_handle)
             }
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
+            RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Glyph(..) => {
                 panic!("texture handle not supported for this task kind");
             }
         }
     }
 
     pub fn get_dynamic_size(&self) -> DeviceIntSize {
@@ -771,16 +798,17 @@ impl RenderTask {
             RenderTaskKind::Glyph(..) => {
                 RenderTargetKind::Color
             }
 
             RenderTaskKind::Scaling(target_kind) => {
                 target_kind
             }
 
+            RenderTaskKind::Border(..) |
             RenderTaskKind::Picture(..) => {
                 RenderTargetKind::Color
             }
 
             RenderTaskKind::Blit(..) => {
                 RenderTargetKind::Color
             }
         }
@@ -796,16 +824,17 @@ impl RenderTask {
         match self.kind {
             RenderTaskKind::Picture(..) |
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::HorizontalBlur(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Blit(..) |
+            RenderTaskKind::Border(..) |
             RenderTaskKind::Glyph(..) => false,
 
             // TODO(gw): For now, we've disabled the shared clip mask
             //           optimization. It's of dubious value in the
             //           future once we start to cache clip tasks anyway.
             //           I have left shared texture support here though,
             //           just in case we want it in the future.
             RenderTaskKind::CacheMask(..) => false,
@@ -832,16 +861,17 @@ impl RenderTask {
             }
             RenderTaskKind::Picture(ref mut info) => {
                 (&mut info.uv_rect_handle, info.uv_rect_kind)
             }
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::ClipRegion(..) |
+            RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Glyph(..) => {
                 return;
             }
         };
 
         if let Some(mut request) = gpu_cache.request(cache_handle) {
             let p0 = target_rect.origin.to_f32();
@@ -882,16 +912,19 @@ impl RenderTask {
             RenderTaskKind::Readback(ref rect) => {
                 pt.new_level("Readback".to_owned());
                 pt.add_item(format!("rect: {:?}", rect));
             }
             RenderTaskKind::Scaling(ref kind) => {
                 pt.new_level("Scaling".to_owned());
                 pt.add_item(format!("kind: {:?}", kind));
             }
+            RenderTaskKind::Border(..) => {
+                pt.new_level("Border".to_owned());
+            }
             RenderTaskKind::Blit(ref task) => {
                 pt.new_level("Blit".to_owned());
                 pt.add_item(format!("source: {:?}", task.source));
             }
             RenderTaskKind::Glyph(..) => {
                 pt.new_level("Glyph".to_owned());
             }
         }
@@ -926,16 +959,17 @@ impl RenderTask {
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskCacheKeyKind {
     BoxShadow(BoxShadowCacheKey),
     Image(ImageCacheKey),
     #[allow(dead_code)]
     Glyph(GpuGlyphCacheKey),
     Picture(PictureCacheKey),
+    Border(BorderCacheKey),
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskCacheKey {
     pub size: DeviceIntSize,
     pub kind: RenderTaskCacheKeyKind,
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -123,16 +123,20 @@ const GPU_TAG_BRUSH_IMAGE: GpuProfileTag
 const GPU_TAG_BRUSH_SOLID: GpuProfileTag = GpuProfileTag {
     label: "B_Solid",
     color: debug_colors::RED,
 };
 const GPU_TAG_CACHE_CLIP: GpuProfileTag = GpuProfileTag {
     label: "C_Clip",
     color: debug_colors::PURPLE,
 };
+const GPU_TAG_CACHE_BORDER: GpuProfileTag = GpuProfileTag {
+    label: "C_Border",
+    color: debug_colors::CORNSILK,
+};
 const GPU_TAG_SETUP_TARGET: GpuProfileTag = GpuProfileTag {
     label: "target init",
     color: debug_colors::SLATEGREY,
 };
 const GPU_TAG_SETUP_DATA: GpuProfileTag = GpuProfileTag {
     label: "data init",
     color: debug_colors::LIGHTGREY,
 };
@@ -385,16 +389,63 @@ pub(crate) mod desc {
             VertexAttribute {
                 name: "aBlurDirection",
                 count: 1,
                 kind: VertexAttributeKind::I32,
             },
         ],
     };
 
+    pub const BORDER: VertexDescriptor = VertexDescriptor {
+        vertex_attributes: &[
+            VertexAttribute {
+                name: "aPosition",
+                count: 2,
+                kind: VertexAttributeKind::F32,
+            },
+        ],
+        instance_attributes: &[
+            VertexAttribute {
+                name: "aTaskOrigin",
+                count: 2,
+                kind: VertexAttributeKind::F32,
+            },
+            VertexAttribute {
+                name: "aRect",
+                count: 4,
+                kind: VertexAttributeKind::F32,
+            },
+            VertexAttribute {
+                name: "aColor0",
+                count: 4,
+                kind: VertexAttributeKind::F32,
+            },
+            VertexAttribute {
+                name: "aColor1",
+                count: 4,
+                kind: VertexAttributeKind::F32,
+            },
+            VertexAttribute {
+                name: "aFlags",
+                count: 1,
+                kind: VertexAttributeKind::I32,
+            },
+            VertexAttribute {
+                name: "aWidths",
+                count: 2,
+                kind: VertexAttributeKind::F32,
+            },
+            VertexAttribute {
+                name: "aRadii",
+                count: 2,
+                kind: VertexAttributeKind::F32,
+            },
+        ],
+    };
+
     pub const CLIP: VertexDescriptor = VertexDescriptor {
         vertex_attributes: &[
             VertexAttribute {
                 name: "aPosition",
                 count: 2,
                 kind: VertexAttributeKind::F32,
             },
         ],
@@ -568,16 +619,17 @@ pub(crate) mod desc {
 #[derive(Debug, Copy, Clone)]
 pub(crate) enum VertexArrayKind {
     Primitive,
     Blur,
     Clip,
     DashAndDot,
     VectorStencil,
     VectorCover,
+    Border,
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub enum GraphicsApi {
     OpenGL,
 }
 
 #[derive(Clone, Debug)]
@@ -1288,16 +1340,17 @@ impl LazyInitializedDebugRenderer {
     }
 }
 
 pub struct RendererVAOs {
     prim_vao: VAO,
     blur_vao: VAO,
     clip_vao: VAO,
     dash_and_dot_vao: VAO,
+    border_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,
@@ -1574,16 +1627,18 @@ impl Renderer {
         device.update_vao_main_vertices(&prim_vao, &quad_vertices, VertexUsageHint::Static);
 
         let gpu_glyph_renderer = try!(GpuGlyphRenderer::new(&mut device,
                                                             &prim_vao,
                                                             options.precache_shaders));
 
         let blur_vao = device.create_vao_with_new_instances(&desc::BLUR, &prim_vao);
         let clip_vao = device.create_vao_with_new_instances(&desc::CLIP, &prim_vao);
+        let border_vao =
+            device.create_vao_with_new_instances(&desc::BORDER, &prim_vao);
         let dash_and_dot_vao =
             device.create_vao_with_new_instances(&desc::BORDER_CORNER_DASH_AND_DOT, &prim_vao);
         let texture_cache_upload_pbo = device.create_pbo();
 
         let texture_resolver = SourceTextureResolver::new(&mut device);
 
         let node_data_texture = VertexDataTexture::new(&mut device);
         let local_clip_rects_texture = VertexDataTexture::new(&mut device);
@@ -1729,16 +1784,17 @@ impl Renderer {
             last_time: 0,
             gpu_profile,
             gpu_glyph_renderer,
             vaos: RendererVAOs {
                 prim_vao,
                 blur_vao,
                 clip_vao,
                 dash_and_dot_vao,
+                border_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,
@@ -3243,19 +3299,46 @@ impl Renderer {
             self.device
                 .bind_draw_target(Some((texture, layer)), Some(target_size));
         }
 
         self.device.disable_depth();
         self.device.disable_depth_write();
         self.device.set_blend(false);
 
+        for rect in &target.clears {
+            self.device.clear_target(Some([0.0, 0.0, 0.0, 0.0]), None, Some(*rect));
+        }
+
         // Handle any blits to this texture from child tasks.
         self.handle_blits(&target.blits, render_tasks);
 
+        // Draw any borders for this target.
+        if !target.border_segments.is_empty() {
+            let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_BORDER);
+
+            self.device.set_blend(true);
+            self.device.set_blend_mode_premultiplied_alpha();
+
+            self.shaders.cs_border_segment.bind(
+                &mut self.device,
+                &projection,
+                &mut self.renderer_errors,
+            );
+
+            self.draw_instanced_batch(
+                &target.border_segments,
+                VertexArrayKind::Border,
+                &BatchTextures::no_texture(),
+                stats,
+            );
+
+            self.device.set_blend(false);
+        }
+
         // Draw any blurs for this target.
         if !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
             match target.target_kind {
                 RenderTargetKind::Alpha => &mut self.shaders.cs_blur_a8,
                 RenderTargetKind::Color => &mut self.shaders.cs_blur_rgba8,
             }.bind(&mut self.device, &projection, &mut self.renderer_errors);
@@ -3848,16 +3931,17 @@ impl Renderer {
         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.vaos.prim_vao);
         self.device.delete_vao(self.vaos.clip_vao);
         self.device.delete_vao(self.vaos.blur_vao);
         self.device.delete_vao(self.vaos.dash_and_dot_vao);
+        self.device.delete_vao(self.vaos.border_vao);
 
         #[cfg(feature = "debug_renderer")]
         {
             self.debug.deinit(&mut self.device);
         }
 
         for (_, target) in self.output_targets {
             self.device.delete_fbo(target.fbo_id);
@@ -4443,24 +4527,26 @@ fn get_vao<'a>(vertex_array_kind: Vertex
                -> &'a VAO {
     match vertex_array_kind {
         VertexArrayKind::Primitive => &vaos.prim_vao,
         VertexArrayKind::Clip => &vaos.clip_vao,
         VertexArrayKind::Blur => &vaos.blur_vao,
         VertexArrayKind::DashAndDot => &vaos.dash_and_dot_vao,
         VertexArrayKind::VectorStencil => &gpu_glyph_renderer.vector_stencil_vao,
         VertexArrayKind::VectorCover => &gpu_glyph_renderer.vector_cover_vao,
+        VertexArrayKind::Border => &vaos.border_vao,
     }
 }
 
 #[cfg(not(feature = "pathfinder"))]
 fn get_vao<'a>(vertex_array_kind: VertexArrayKind,
                vaos: &'a RendererVAOs,
                _: &'a GpuGlyphRenderer)
                -> &'a VAO {
     match vertex_array_kind {
         VertexArrayKind::Primitive => &vaos.prim_vao,
         VertexArrayKind::Clip => &vaos.clip_vao,
         VertexArrayKind::Blur => &vaos.blur_vao,
         VertexArrayKind::DashAndDot => &vaos.dash_and_dot_vao,
         VertexArrayKind::VectorStencil | VertexArrayKind::VectorCover => unreachable!(),
+        VertexArrayKind::Border => &vaos.border_vao,
     }
 }
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -1,34 +1,34 @@
 /* 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, BlobImageResources, ResourceUpdate, ResourceUpdates};
+use api::{AddFont, BlobImageResources, ResourceUpdate};
 use api::{BlobImageDescriptor, BlobImageError, BlobImageRenderer, BlobImageRequest};
 use api::{ClearCache, ColorF, DevicePoint, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
-use api::{Epoch, FontInstanceKey, FontKey, FontTemplate};
+use api::{Epoch, FontInstanceKey, FontKey, FontTemplate, GlyphIndex};
 use api::{ExternalImageData, ExternalImageType};
 use api::{FontInstanceOptions, FontInstancePlatformOptions, FontVariation};
-use api::{GlyphDimensions, GlyphKey, IdNamespace};
+use api::{GlyphDimensions, IdNamespace};
 use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering};
 use api::{TileOffset, TileSize};
 use app_units::Au;
 #[cfg(feature = "capture")]
 use capture::ExternalCaptureImage;
 #[cfg(feature = "replay")]
 use capture::PlainExternalImage;
 #[cfg(any(feature = "replay", feature = "png"))]
 use capture::CaptureConfig;
 use device::TextureFilter;
 use euclid::size2;
 use glyph_cache::GlyphCache;
 #[cfg(not(feature = "pathfinder"))]
 use glyph_cache::GlyphCacheEntry;
-use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphRasterizer, GlyphRequest};
+use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::UvRectKind;
 use internal_types::{FastHashMap, FastHashSet, SourceTexture, TextureUpdateList};
 use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
 use render_backend::FrameId;
 use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId};
 use render_task::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle, RenderTaskTree};
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
@@ -169,17 +169,17 @@ pub fn intersect_for_tile(
 }
 
 
 impl<K, V, U> ResourceClassCache<K, V, U>
 where
     K: Clone + Hash + Eq + Debug,
     U: Default,
 {
-    pub fn new() -> ResourceClassCache<K, V, U> {
+    pub fn new() -> Self {
         ResourceClassCache {
             resources: FastHashMap::default(),
             user_data: Default::default(),
         }
     }
 
     pub fn get(&self, key: &K) -> &V {
         self.resources.get(key)
@@ -277,17 +277,17 @@ impl BlobImageResources for Resources {
     }
     fn get_image(&self, key: ImageKey) -> Option<(&ImageData, &ImageDescriptor)> {
         self.image_templates
             .get(key)
             .map(|resource| (&resource.data, &resource.descriptor))
     }
 }
 
-pub type GlyphDimensionsCache = FastHashMap<GlyphRequest, Option<GlyphDimensions>>;
+pub type GlyphDimensionsCache = FastHashMap<(FontInstance, GlyphIndex), Option<GlyphDimensions>>;
 
 pub struct ResourceCache {
     cached_glyphs: GlyphCache,
     cached_images: ImageCache,
     cached_render_tasks: RenderTaskCache,
 
     resources: Resources,
     state: State,
@@ -366,24 +366,24 @@ impl ResourceCache {
             user_data,
             is_opaque,
             |render_task_tree| Ok(f(render_task_tree))
         ).expect("Failed to request a render task from the resource cache!")
     }
 
     pub fn update_resources(
         &mut self,
-        updates: ResourceUpdates,
+        updates: Vec<ResourceUpdate>,
         profile_counters: &mut ResourceProfileCounters,
     ) {
         // TODO, there is potential for optimization here, by processing updates in
         // bulk rather than one by one (for example by sorting allocations by size or
         // in a way that reduces fragmentation in the atlas).
 
-        for update in updates.updates {
+        for update in updates {
             match update {
                 ResourceUpdate::AddImage(img) => {
                     if let ImageData::Raw(ref bytes) = img.data {
                         profile_counters.image_templates.inc(bytes.len());
                     }
                     self.add_image_template(img.key, img.descriptor, img.data, img.tiling);
                 }
                 ResourceUpdate::UpdateImage(img) => {
@@ -444,28 +444,26 @@ impl ResourceCache {
         font_key: FontKey,
         glyph_size: Au,
         options: Option<FontInstanceOptions>,
         platform_options: Option<FontInstancePlatformOptions>,
         variations: Vec<FontVariation>,
     ) {
         let FontInstanceOptions {
             render_mode,
-            subpx_dir,
             flags,
             bg_color,
             ..
         } = options.unwrap_or_default();
         let instance = FontInstance::new(
             font_key,
             glyph_size,
             ColorF::new(0.0, 0.0, 0.0, 1.0),
             bg_color,
             render_mode,
-            subpx_dir,
             flags,
             platform_options,
             variations,
         );
         self.resources.font_instances
             .write()
             .unwrap()
             .insert(instance_key, instance);
@@ -505,26 +503,24 @@ impl ResourceCache {
 
         if let ImageData::Blob(ref blob) = data {
             self.blob_image_renderer.as_mut().unwrap().add(
                 image_key,
                 Arc::clone(&blob),
                 tiling,
             );
         }
+        let dirty_rect = Some(descriptor.full_rect());
 
         let resource = ImageResource {
             descriptor,
             data,
             epoch: Epoch(0),
             tiling,
-            dirty_rect: Some(DeviceUintRect::new(
-                DeviceUintPoint::zero(),
-                descriptor.size,
-            )),
+            dirty_rect,
         };
 
         self.resources.image_templates.insert(image_key, resource);
     }
 
     pub fn update_image_template(
         &mut self,
         image_key: ImageKey,
@@ -631,19 +627,24 @@ impl ResourceCache {
                 )),
                 true,
             ),
         };
 
         let needs_upload = self.texture_cache
             .request(&entry.as_ref().unwrap().texture_cache_handle, gpu_cache);
 
-        if !needs_upload && !needs_update {
-            return;
-        }
+        let dirty_rect = if needs_upload {
+            // the texture cache entry has been evicted, treat it as all dirty
+            Some(template.descriptor.full_rect())
+        } else if needs_update {
+            template.dirty_rect
+        } else {
+            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 (offset, size) = match template.tiling {
                     Some(tile_size) => {
@@ -653,17 +654,17 @@ impl ResourceCache {
                             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) = template.dirty_rect {
+                        if let Some(dirty) = dirty_rect {
                             if intersect_for_tile(dirty, actual_size, tile_size, tile_offset).is_none() {
                                 // don't bother requesting unchanged tiles
                                 return
                             }
                         }
 
                         (offset, actual_size)
                     }
@@ -673,17 +674,17 @@ impl ResourceCache {
                 renderer.request(
                     &self.resources,
                     request.into(),
                     &BlobImageDescriptor {
                         size,
                         offset,
                         format: template.descriptor.format,
                     },
-                    template.dirty_rect,
+                    dirty_rect,
                 );
             }
         }
     }
 
     pub fn request_glyphs(
         &mut self,
         mut font: FontInstance,
@@ -807,25 +808,23 @@ impl ResourceCache {
             f(current_texture_id, current_glyph_format, fetch_buffer);
             fetch_buffer.clear();
         }
     }
 
     pub fn get_glyph_dimensions(
         &mut self,
         font: &FontInstance,
-        key: &GlyphKey,
+        glyph_index: GlyphIndex,
     ) -> Option<GlyphDimensions> {
-        let key = GlyphRequest::new(font, key);
-
-        match self.cached_glyph_dimensions.entry(key.clone()) {
+        match self.cached_glyph_dimensions.entry((font.clone(), glyph_index)) {
             Occupied(entry) => *entry.get(),
             Vacant(entry) => *entry.insert(
                 self.glyph_rasterizer
-                    .get_glyph_dimensions(&key.font, &key.key),
+                    .get_glyph_dimensions(font, glyph_index),
             ),
         }
     }
 
     pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
         self.glyph_rasterizer.get_glyph_index(font_key, ch)
     }
 
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -1,44 +1,44 @@
 /* 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::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdates};
+use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdate};
 use api::channel::MsgSender;
 use display_list_flattener::build_scene;
 use frame_builder::{FrameBuilderConfig, FrameBuilder};
 use clip_scroll_tree::ClipScrollTree;
 use internal_types::FastHashSet;
 use resource_cache::FontInstanceMap;
 use render_backend::DocumentView;
 use renderer::{PipelineInfo, SceneBuilderHooks};
 use scene::Scene;
 use std::sync::mpsc::{channel, Receiver, Sender};
 
 // Message from render backend to scene builder.
 pub enum SceneBuilderRequest {
     Transaction {
         document_id: DocumentId,
         scene: Option<SceneRequest>,
-        resource_updates: ResourceUpdates,
+        resource_updates: Vec<ResourceUpdate>,
         frame_ops: Vec<FrameMsg>,
         render: bool,
     },
     WakeUp,
     Flush(MsgSender<()>),
     Stop
 }
 
 // Message from scene builder to render backend.
 pub enum SceneBuilderResult {
     Transaction {
         document_id: DocumentId,
         built_scene: Option<BuiltScene>,
-        resource_updates: ResourceUpdates,
+        resource_updates: Vec<ResourceUpdate>,
         frame_ops: Vec<FrameMsg>,
         render: bool,
         result_tx: Option<Sender<SceneSwapResult>>,
     },
     FlushComplete(MsgSender<()>),
     Stopped,
 }
 
--- a/gfx/webrender/src/shade.rs
+++ b/gfx/webrender/src/shade.rs
@@ -398,16 +398,17 @@ fn create_prim_shader(
 
     let vertex_descriptor = match vertex_format {
         VertexArrayKind::Primitive => desc::PRIM_INSTANCES,
         VertexArrayKind::Blur => desc::BLUR,
         VertexArrayKind::Clip => desc::CLIP,
         VertexArrayKind::DashAndDot => desc::BORDER_CORNER_DASH_AND_DOT,
         VertexArrayKind::VectorStencil => desc::VECTOR_STENCIL,
         VertexArrayKind::VectorCover => desc::VECTOR_COVER,
+        VertexArrayKind::Border => desc::BORDER,
     };
 
     let program = device.create_program(name, &prefix, &vertex_descriptor);
 
     if let Ok(ref program) = program {
         device.bind_shader_samplers(
             program,
             &[
@@ -459,16 +460,17 @@ fn create_clip_shader(name: &'static str
 
 
 pub struct Shaders {
     // These are "cache shaders". These shaders are used to
     // draw intermediate results to cache targets. The results
     // of these shaders are then used by the primitive shaders.
     pub cs_blur_a8: LazilyCompiledShader,
     pub cs_blur_rgba8: LazilyCompiledShader,
+    pub cs_border_segment: LazilyCompiledShader,
 
     // Brush shaders
     brush_solid: BrushShader,
     brush_image: Vec<Option<BrushShader>>,
     brush_blend: BrushShader,
     brush_mix_blend: BrushShader,
     brush_yuv_image: Vec<Option<BrushShader>>,
     brush_radial_gradient: BrushShader,
@@ -706,31 +708,40 @@ impl Shaders {
 
         let ps_border_edge = PrimitiveShader::new(
             "ps_border_edge",
              device,
              &[],
              options.precache_shaders,
         )?;
 
+        let cs_border_segment = LazilyCompiledShader::new(
+            ShaderKind::Cache(VertexArrayKind::Border),
+            "cs_border_segment",
+             &[],
+             device,
+             options.precache_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,
+            cs_border_segment,
             brush_solid,
             brush_image,
             brush_blend,
             brush_mix_blend,
             brush_yuv_image,
             brush_radial_gradient,
             brush_linear_gradient,
             cs_clip_rectangle,
@@ -839,11 +850,12 @@ impl Shaders {
         }
         for shader in self.brush_yuv_image {
             if let Some(shader) = shader {
                 shader.deinit(device);
             }
         }
         self.ps_border_corner.deinit(device);
         self.ps_border_edge.deinit(device);
+        self.cs_border_segment.deinit(device);
         self.ps_split_composite.deinit(device);
     }
 }
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -7,28 +7,28 @@ use api::{DeviceUintRect, DeviceUintSize
 use api::{MixBlendMode, PipelineId};
 use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
 use clip::{ClipStore};
 use clip_scroll_tree::{ClipScrollTree, ClipScrollNodeIndex};
 use device::{FrameId, Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use gpu_cache::{GpuCache};
-use gpu_types::{BlurDirection, BlurInstance};
+use gpu_types::{BorderInstance, BlurDirection, BlurInstance};
 use gpu_types::{ClipScrollNodeData, ZBufferIdGenerator};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveKind, PrimitiveStore};
 use prim_store::{BrushKind, DeferredResolve};
 use profiler::FrameProfileCounters;
 use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskTree};
 use resource_cache::ResourceCache;
-use std::{cmp, usize, f32, i32};
+use std::{cmp, usize, f32, i32, mem};
 use texture_allocator::GuillotineAllocator;
 #[cfg(feature = "pathfinder")]
 use webrender_api::{DevicePixel, FontRenderMode};
 
 const MIN_TARGET_SIZE: u32 = 2048;
 
 #[derive(Debug)]
 pub struct ScrollbarPrimitive {
@@ -428,16 +428,17 @@ impl RenderTarget for ColorRenderTarget 
                     }
                     _ => {
                         // No other primitives make use of primitive caching yet!
                         unreachable!()
                     }
                 }
             }
             RenderTaskKind::ClipRegion(..) |
+            RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) => {
                 panic!("Should not be added to color target!");
             }
             RenderTaskKind::Glyph(..) => {
                 // FIXME(pcwalton): Support color glyphs.
                 panic!("Glyphs should not be added to color target!");
             }
             RenderTaskKind::Readback(device_rect) => {
@@ -560,16 +561,17 @@ impl RenderTarget for AlphaRenderTarget 
                 panic!("bug: invalid clear mode for alpha task");
             }
         }
 
         match task.kind {
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Picture(..) |
             RenderTaskKind::Blit(..) |
+            RenderTaskKind::Border(..) |
             RenderTaskKind::Glyph(..) => {
                 panic!("BUG: should not be added to alpha target!");
             }
             RenderTaskKind::VerticalBlur(ref info) => {
                 info.add_instances(
                     &mut self.vertical_blurs,
                     BlurDirection::Vertical,
                     render_tasks.get_task_address(task_id),
@@ -622,25 +624,29 @@ impl RenderTarget for AlphaRenderTarget 
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct TextureCacheRenderTarget {
     pub target_kind: RenderTargetKind,
     pub horizontal_blurs: Vec<BlurInstance>,
     pub blits: Vec<BlitJob>,
     pub glyphs: Vec<GlyphJob>,
+    pub border_segments: Vec<BorderInstance>,
+    pub clears: Vec<DeviceIntRect>,
 }
 
 impl TextureCacheRenderTarget {
     fn new(target_kind: RenderTargetKind) -> Self {
         TextureCacheRenderTarget {
             target_kind,
             horizontal_blurs: vec![],
             blits: vec![],
             glyphs: vec![],
+            border_segments: vec![],
+            clears: vec![],
         }
     }
 
     fn add_task(
         &mut self,
         task_id: RenderTaskId,
         render_tasks: &mut RenderTaskTree,
     ) {
@@ -673,16 +679,30 @@ impl TextureCacheRenderTarget {
                         // task to this target.
                         self.blits.push(BlitJob {
                             source: BlitJobSource::RenderTask(task_id),
                             target_rect: target_rect.0.inner_rect(task_info.padding),
                         });
                     }
                 }
             }
+            RenderTaskKind::Border(ref mut task_info) => {
+                self.clears.push(target_rect.0);
+
+                // TODO(gw): It may be better to store the task origin in
+                //           the render task data instead of per instance.
+                let task_origin = target_rect.0.origin.to_f32();
+                for instance in &mut task_info.instances {
+                    instance.task_origin = task_origin;
+                }
+
+                let instances = mem::replace(&mut task_info.instances, Vec::new());
+
+                self.border_segments.extend(instances);
+            }
             RenderTaskKind::Glyph(ref mut task_info) => {
                 self.add_glyph_task(task_info, target_rect.0)
             }
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Picture(..) |
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Readback(..) |
--- 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.37", features = ["rc"] }
-serde_derive = { version = "=1.0.37", features = ["deserialize_in_place"] }
+serde = { version = "=1.0.58", features = ["rc"] }
+serde_derive = { version = "=1.0.58", 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
@@ -8,160 +8,66 @@ use app_units::Au;
 use channel::{self, MsgSender, Payload, PayloadSender, PayloadSenderHelperMethods};
 use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
 use std::path::PathBuf;
 use std::u32;
 use {BuiltDisplayList, BuiltDisplayListDescriptor, ColorF, DeviceIntPoint, DeviceUintRect};
 use {DeviceUintSize, ExternalScrollId, FontInstanceKey, FontInstanceOptions};
-use {FontInstancePlatformOptions, FontKey, FontVariation, GlyphDimensions, GlyphKey, ImageData};
+use {FontInstancePlatformOptions, FontKey, FontVariation, GlyphDimensions, GlyphIndex, ImageData};
 use {ImageDescriptor, ImageKey, ItemTag, LayoutPoint, LayoutSize, LayoutTransform, LayoutVector2D};
 use {NativeFontHandle, WorldPoint};
 
 pub type TileSize = u16;
 /// Documents are rendered in the ascending order of their associated layer values.
 pub type DocumentLayer = i8;
 
-/// The resource updates for a given transaction (they must be applied in the same frame).
-#[derive(Clone, Deserialize, Serialize)]
-pub struct ResourceUpdates {
-    pub updates: Vec<ResourceUpdate>,
-}
-
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ResourceUpdate {
     AddImage(AddImage),
     UpdateImage(UpdateImage),
     DeleteImage(ImageKey),
     AddFont(AddFont),
     DeleteFont(FontKey),
     AddFontInstance(AddFontInstance),
     DeleteFontInstance(FontInstanceKey),
 }
 
-impl ResourceUpdates {
-    pub fn new() -> Self {
-        ResourceUpdates {
-            updates: Vec::new(),
-        }
-    }
-
-    pub fn add_image(
-        &mut self,
-        key: ImageKey,
-        descriptor: ImageDescriptor,
-        data: ImageData,
-        tiling: Option<TileSize>,
-    ) {
-        self.updates.push(ResourceUpdate::AddImage(AddImage {
-            key,
-            descriptor,
-            data,
-            tiling,
-        }));
-    }
-
-    pub fn update_image(
-        &mut self,
-        key: ImageKey,
-        descriptor: ImageDescriptor,
-        data: ImageData,
-        dirty_rect: Option<DeviceUintRect>,
-    ) {
-        self.updates.push(ResourceUpdate::UpdateImage(UpdateImage {
-            key,
-            descriptor,
-            data,
-            dirty_rect,
-        }));
-    }
-
-    pub fn delete_image(&mut self, key: ImageKey) {
-        self.updates.push(ResourceUpdate::DeleteImage(key));
-    }
-
-    pub fn add_raw_font(&mut self, key: FontKey, bytes: Vec<u8>, index: u32) {
-        self.updates
-            .push(ResourceUpdate::AddFont(AddFont::Raw(key, bytes, index)));
-    }
-
-    pub fn add_native_font(&mut self, key: FontKey, native_handle: NativeFontHandle) {
-        self.updates
-            .push(ResourceUpdate::AddFont(AddFont::Native(key, native_handle)));
-    }
-
-    pub fn delete_font(&mut self, key: FontKey) {
-        self.updates.push(ResourceUpdate::DeleteFont(key));
-    }
-
-    pub fn add_font_instance(
-        &mut self,
-        key: FontInstanceKey,
-        font_key: FontKey,
-        glyph_size: Au,
-        options: Option<FontInstanceOptions>,
-        platform_options: Option<FontInstancePlatformOptions>,
-        variations: Vec<FontVariation>,
-    ) {
-        self.updates
-            .push(ResourceUpdate::AddFontInstance(AddFontInstance {
-                key,
-                font_key,
-                glyph_size,
-                options,
-                platform_options,
-                variations,
-            }));
-    }
-
-    pub fn delete_font_instance(&mut self, key: FontInstanceKey) {
-        self.updates.push(ResourceUpdate::DeleteFontInstance(key));
-    }
-
-    pub fn merge(&mut self, mut other: ResourceUpdates) {
-        self.updates.append(&mut other.updates);
-    }
-
-    pub fn clear(&mut self) {
-        self.updates.clear()
-    }
-}
-
 /// A Transaction is a group of commands to apply atomically to a document.
 ///
 /// This mechanism ensures that:
 ///  - no other message can be interleaved between two commands that need to be applied together.
 ///  - no redundant work is performed if two commands in the same transaction cause the scene or
 ///    the frame to be rebuilt.
 pub struct Transaction {
     // Operations affecting the scene (applied before scene building).
     scene_ops: Vec<SceneMsg>,
     // Operations affecting the generation of frames (applied after scene building).
     frame_ops: Vec<FrameMsg>,
 
     // Additional display list data.
     payloads: Vec<Payload>,
 
     // Resource updates are applied after scene building.
-    resource_updates: ResourceUpdates,
+    pub resource_updates: Vec<ResourceUpdate>,
 
     // If true the transaction is piped through the scene building thread, if false
     // it will be applied directly on the render backend.
     use_scene_builder_thread: bool,
 
     generate_frame: bool,
 }
 
 impl Transaction {
     pub fn new() -> Self {
         Transaction {
             scene_ops: Vec::new(),
             frame_ops: Vec::new(),
-            resource_updates: ResourceUpdates::new(),
+            resource_updates: Vec::new(),
             payloads: Vec::new(),
             use_scene_builder_thread: false, // TODO: make this true by default.
             generate_frame: false,
         }
     }
 
     // TODO: better name?
     pub fn skip_scene_builder(&mut self) {
@@ -173,17 +79,17 @@ impl Transaction {
     pub fn use_scene_builder_thread(&mut self) {
         self.use_scene_builder_thread = true;
     }
 
     pub fn is_empty(&self) -> bool {
         !self.generate_frame &&
             self.scene_ops.is_empty() &&
             self.frame_ops.is_empty() &&
-            self.resource_updates.updates.is_empty()
+            self.resource_updates.is_empty()
     }
 
     pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) {
         // We track epochs before and after scene building.
         // This one will be applied to the pending scene right away:
         self.scene_ops.push(SceneMsg::UpdateEpoch(pipeline_id, epoch));
         // And this one will be applied to the currently built scene at the end
         // of the transaction (potentially long after the scene_ops one).
@@ -252,18 +158,18 @@ impl Transaction {
                 content_size,
                 list_descriptor,
                 preserve_frame_state,
             }
         );
         self.payloads.push(Payload { epoch, pipeline_id, display_list_data });
     }
 
-    pub fn update_resources(&mut self, resources: ResourceUpdates) {
-        self.resource_updates.merge(resources);
+    pub fn update_resources(&mut self, resources: Vec<ResourceUpdate>) {
+        self.merge(resources);
     }
 
     pub fn set_window_parameters(
         &mut self,
         window_size: DeviceUintSize,
         inner_rect: DeviceUintRect,
         device_pixel_ratio: f32,
     ) {
@@ -348,52 +254,132 @@ impl Transaction {
                 frame_ops: self.frame_ops,
                 resource_updates: self.resource_updates,
                 use_scene_builder_thread: self.use_scene_builder_thread,
                 generate_frame: self.generate_frame,
             },
             self.payloads,
         )
     }
+
+    pub fn add_image(
+        &mut self,
+        key: ImageKey,
+        descriptor: ImageDescriptor,
+        data: ImageData,
+        tiling: Option<TileSize>,
+    ) {
+        self.resource_updates.push(ResourceUpdate::AddImage(AddImage {
+            key,
+            descriptor,
+            data,
+            tiling,
+        }));
+    }
+
+    pub fn update_image(
+        &mut self,
+        key: ImageKey,
+        descriptor: ImageDescriptor,
+        data: ImageData,
+        dirty_rect: Option<DeviceUintRect>,
+    ) {
+        self.resource_updates.push(ResourceUpdate::UpdateImage(UpdateImage {
+            key,
+            descriptor,
+            data,
+            dirty_rect,
+        }));
+    }
+
+    pub fn delete_image(&mut self, key: ImageKey) {
+        self.resource_updates.push(ResourceUpdate::DeleteImage(key));
+    }
+
+    pub fn add_raw_font(&mut self, key: FontKey, bytes: Vec<u8>, index: u32) {
+        self.resource_updates
+            .push(ResourceUpdate::AddFont(AddFont::Raw(key, bytes, index)));
+    }
+
+    pub fn add_native_font(&mut self, key: FontKey, native_handle: NativeFontHandle) {
+        self.resource_updates
+            .push(ResourceUpdate::AddFont(AddFont::Native(key, native_handle)));
+    }
+
+    pub fn delete_font(&mut self, key: FontKey) {
+        self.resource_updates.push(ResourceUpdate::DeleteFont(key));
+    }
+
+    pub fn add_font_instance(
+        &mut self,
+        key: FontInstanceKey,
+        font_key: FontKey,
+        glyph_size: Au,
+        options: Option<FontInstanceOptions>,
+        platform_options: Option<FontInstancePlatformOptions>,
+        variations: Vec<FontVariation>,
+    ) {
+        self.resource_updates
+            .push(ResourceUpdate::AddFontInstance(AddFontInstance {
+                key,
+                font_key,
+                glyph_size,
+                options,
+                platform_options,
+                variations,
+            }));
+    }
+
+    pub fn delete_font_instance(&mut self, key: FontInstanceKey) {
+        self.resource_updates.push(ResourceUpdate::DeleteFontInstance(key));
+    }
+
+    pub fn merge(&mut self, mut other: Vec<ResourceUpdate>) {
+        self.resource_updates.append(&mut other);
+    }
+
+    pub fn clear(&mut self) {
+        self.resource_updates.clear()
+    }
 }
 
 /// Represents a transaction in the format sent through the channel.
 #[derive(Clone, Deserialize, Serialize)]
 pub struct TransactionMsg {
     pub scene_ops: Vec<SceneMsg>,
     pub frame_ops: Vec<FrameMsg>,
-    pub resource_updates: ResourceUpdates,
+    pub resource_updates: Vec<ResourceUpdate>,
     pub generate_frame: bool,
     pub use_scene_builder_thread: bool,
 }
 
 impl TransactionMsg {
     pub fn is_empty(&self) -> bool {
         !self.generate_frame &&
             self.scene_ops.is_empty() &&
             self.frame_ops.is_empty() &&
-            self.resource_updates.updates.is_empty()
+            self.resource_updates.is_empty()
     }
 
     // TODO: We only need this for a few RenderApi methods which we should remove.
     fn frame_message(msg: FrameMsg) -> Self {
         TransactionMsg {
             scene_ops: Vec::new(),
             frame_ops: vec![msg],
-            resource_updates: ResourceUpdates::new(),
+            resource_updates: Vec::new(),
             generate_frame: false,
             use_scene_builder_thread: false,
         }
     }
 
     fn scene_message(msg: SceneMsg) -> Self {
         TransactionMsg {
             scene_ops: vec![msg],
             frame_ops: Vec::new(),
-            resource_updates: ResourceUpdates::new(),
+            resource_updates: Vec::new(),
             generate_frame: false,
             use_scene_builder_thread: false,
         }
     }
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub struct AddImage {
@@ -593,21 +579,21 @@ pub enum DebugCommand {
     ClearCaches(ClearCache),
     /// Invalidate GPU cache, forcing the update from the CPU mirror.
     InvalidateGpuCache,
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ApiMsg {
     /// Add/remove/update images and fonts.
-    UpdateResources(ResourceUpdates),
+    UpdateResources(Vec<ResourceUpdate>),
     /// Gets the glyph dimensions
     GetGlyphDimensions(
         FontInstanceKey,
-        Vec<GlyphKey>,
+        Vec<GlyphIndex>,
         MsgSender<Vec<Option<GlyphDimensions>>>,
     ),
     /// Gets the glyph indices from a string
     GetGlyphIndices(FontKey, String, MsgSender<Vec<Option<u32>>>),
     /// Adds a new document namespace.
     CloneApi(MsgSender<IdNamespace>),
     /// Adds a new document with given initial size.
     AddDocument(DocumentId, DeviceUintSize, DocumentLayer),
@@ -806,20 +792,20 @@ impl RenderApi {
     /// Gets the dimensions for the supplied glyph keys
     ///
     /// Note: Internally, the internal texture cache doesn't store
     /// 'empty' textures (height or width = 0)
     /// This means that glyph dimensions e.g. for spaces (' ') will mostly be None.
     pub fn get_glyph_dimensions(
         &self,
         font: FontInstanceKey,
-        glyph_keys: Vec<GlyphKey>,
+        glyph_indices: Vec<GlyphIndex>,
     ) -> Vec<Option<GlyphDimensions>> {
         let (tx, rx) = channel::msg_channel().unwrap();
-        let msg = ApiMsg::GetGlyphDimensions(font, glyph_keys, tx);
+        let msg = ApiMsg::GetGlyphDimensions(font, glyph_indices, tx);
         self.api_sender.send(msg).unwrap();
         rx.recv().unwrap()
     }
 
     /// Gets the glyph indices for the supplied string. These
     /// can be used to construct GlyphKeys.
     pub fn get_glyph_indices(&self, font_key: FontKey, text: &str) -> Vec<Option<u32>> {
         let (tx, rx) = channel::msg_channel().unwrap();
@@ -830,18 +816,18 @@ impl RenderApi {
 
     /// Creates an `ImageKey`.
     pub fn generate_image_key(&self) -> ImageKey {
         let new_id = self.next_unique_id();
         ImageKey::new(self.namespace_id, new_id)
     }
 
     /// Add/remove/update resources such as images and fonts.
-    pub fn update_resources(&self, resources: ResourceUpdates) {
-        if resources.updates.is_empty() {
+    pub fn update_resources(&self, resources: Vec<ResourceUpdate>) {
+        if resources.is_empty() {
             return;
         }
         self.api_sender
             .send(ApiMsg::UpdateResources(resources))
             .unwrap();
     }
 
     pub fn send_external_event(&self, evt: ExternalEvent) {
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -367,17 +367,17 @@ pub struct BorderWidths {
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct BorderSide {
     pub color: ColorF,
     pub style: BorderStyle,
 }
 
 #[repr(u32)]
-#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, Hash, Eq)]
 pub enum BorderStyle {
     None = 0,
     Solid = 1,
     Double = 2,
     Dotted = 3,
     Dashed = 4,
     Hidden = 5,
     Groove = 6,
--- a/gfx/webrender_api/src/font.rs
+++ b/gfx/webrender_api/src/font.rs
@@ -89,86 +89,26 @@ pub enum FontTemplate {
 #[repr(u32)]
 #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
 pub enum FontRenderMode {
     Mono = 0,
     Alpha,
     Subpixel,
 }
 
-#[repr(u32)]
-#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
-pub enum SubpixelDirection {
-    None = 0,
-    Horizontal,
-    Vertical,
-}
-
 impl FontRenderMode {
-    // Skia quantizes subpixel offsets into 1/4 increments.
-    // Given the absolute position, return the quantized increment
-    fn subpixel_quantize_offset(&self, pos: f32) -> SubpixelOffset {
-        // Following the conventions of Gecko and Skia, we want
-        // to quantize the subpixel position, such that abs(pos) gives:
-        // [0.0, 0.125) -> Zero
-        // [0.125, 0.375) -> Quarter
-        // [0.375, 0.625) -> Half
-        // [0.625, 0.875) -> ThreeQuarters,
-        // [0.875, 1.0) -> Zero
-        // The unit tests below check for this.
-        let apos = ((pos - pos.floor()) * 8.0) as i32;
-
-        match apos {
-            0 | 7 => SubpixelOffset::Zero,
-            1...2 => SubpixelOffset::Quarter,
-            3...4 => SubpixelOffset::Half,
-            5...6 => SubpixelOffset::ThreeQuarters,
-            _ => unreachable!("bug: unexpected quantized result"),
-        }
-    }
-
     // Combine two font render modes such that the lesser amount of AA limits the AA of the result.
     pub fn limit_by(self, other: FontRenderMode) -> FontRenderMode {
         match (self, other) {
             (FontRenderMode::Subpixel, _) | (_, FontRenderMode::Mono) => other,
             _ => self,
         }
     }
 }
 
-impl SubpixelDirection {
-    // Limit the subpixel direction to what is supported by the render mode.
-    pub fn limit_by(self, render_mode: FontRenderMode) -> SubpixelDirection {
-        match render_mode {
-            FontRenderMode::Mono => SubpixelDirection::None,
-            FontRenderMode::Alpha | FontRenderMode::Subpixel => self,
-        }
-    }
-}
-
-#[repr(u8)]
-#[derive(Hash, Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
-pub enum SubpixelOffset {
-    Zero = 0,
-    Quarter = 1,
-    Half = 2,
-    ThreeQuarters = 3,
-}
-
-impl Into<f64> for SubpixelOffset {
-    fn into(self) -> f64 {
-        match self {
-            SubpixelOffset::Zero => 0.0,
-            SubpixelOffset::Quarter => 0.25,
-            SubpixelOffset::Half => 0.5,
-            SubpixelOffset::ThreeQuarters => 0.75,
-        }
-    }
-}
-
 #[repr(C)]
 #[derive(Clone, Copy, Debug, PartialOrd, Deserialize, Serialize)]
 pub struct FontVariation {
     pub tag: u32,
     pub value: f32,
 }
 
 impl Ord for FontVariation {
@@ -217,65 +157,66 @@ bitflags! {
         // Common flags
         const SYNTHETIC_ITALICS = 1 << 0;
         const SYNTHETIC_BOLD    = 1 << 1;
         const EMBEDDED_BITMAPS  = 1 << 2;
         const SUBPIXEL_BGR      = 1 << 3;
         const TRANSPOSE         = 1 << 4;
         const FLIP_X            = 1 << 5;
         const FLIP_Y            = 1 << 6;
+        const SUBPIXEL_POSITION = 1 << 7;
 
         // Windows flags
         const FORCE_GDI         = 1 << 16;
 
         // Mac flags
         const FONT_SMOOTHING    = 1 << 16;
 
         // FreeType flags
         const FORCE_AUTOHINT    = 1 << 16;
         const NO_AUTOHINT       = 1 << 17;
         const VERTICAL_LAYOUT   = 1 << 18;
+        const LCD_VERTICAL      = 1 << 19;
     }
 }
 
 impl Default for FontInstanceFlags {
     #[cfg(target_os = "windows")]
     fn default() -> FontInstanceFlags {
-        FontInstanceFlags::empty()
+        FontInstanceFlags::SUBPIXEL_POSITION
     }
 
     #[cfg(target_os = "macos")]
     fn default() -> FontInstanceFlags {
+        FontInstanceFlags::SUBPIXEL_POSITION |
         FontInstanceFlags::FONT_SMOOTHING
     }
 
     #[cfg(not(any(target_os = "macos", target_os = "windows")))]
     fn default() -> FontInstanceFlags {
-        FontInstanceFlags::empty()
+        FontInstanceFlags::SUBPIXEL_POSITION
     }
 }
 
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)]
 pub struct FontInstanceOptions {
     pub render_mode: FontRenderMode,
-    pub subpx_dir: SubpixelDirection,
     pub flags: FontInstanceFlags,
     /// When bg_color.a is != 0 and render_mode is FontRenderMode::Subpixel,
     /// the text will be rendered with bg_color.r/g/b as an opaque estimated
     /// background color.
     pub bg_color: ColorU,
 }
 
 impl Default for FontInstanceOptions {
     fn default() -> FontInstanceOptions {
         FontInstanceOptions {
             render_mode: FontRenderMode::Subpixel,
-            subpx_dir: SubpixelDirection::Horizontal,
             flags: Default::default(),
             bg_color: ColorU::new(0, 0, 0, 0),
         }
     }
 }
 
 #[cfg(target_os = "windows")]
 #[repr(C)]
@@ -353,92 +294,17 @@ impl Default for FontInstancePlatformOpt
 pub struct FontInstanceKey(pub IdNamespace, pub u32);
 
 impl FontInstanceKey {
     pub fn new(namespace: IdNamespace, key: u32) -> FontInstanceKey {
         FontInstanceKey(namespace, key)
     }
 }
 
-#[derive(Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
-pub struct GlyphKey {
-    pub index: u32,
-    pub subpixel_offset: SubpixelOffset,
-}
-
-impl GlyphKey {
-    pub fn new(
-        index: u32,
-        point: LayoutPoint,
-        render_mode: FontRenderMode,
-        subpx_dir: SubpixelDirection,
-    ) -> GlyphKey {
-        let pos = match subpx_dir {
-            SubpixelDirection::None => 0.0,
-            SubpixelDirection::Horizontal => point.x,
-            SubpixelDirection::Vertical => point.y,
-        };
-
-        GlyphKey {
-            index,
-            subpixel_offset: render_mode.subpixel_quantize_offset(pos),
-        }
-    }
-}
-
 pub type GlyphIndex = u32;
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct GlyphInstance {
     pub index: GlyphIndex,
     pub point: LayoutPoint,
 }
 
-#[cfg(test)]
-mod test {
-    use super::{FontRenderMode, SubpixelOffset};
-
-    #[test]
-    fn test_subpx_quantize() {
-        let rm = FontRenderMode::Subpixel;
-
-        assert_eq!(rm.subpixel_quantize_offset(0.0), SubpixelOffset::Zero);
-        assert_eq!(rm.subpixel_quantize_offset(-0.0), SubpixelOffset::Zero);
-
-        assert_eq!(rm.subpixel_quantize_offset(0.1), SubpixelOffset::Zero);
-        assert_eq!(rm.subpixel_quantize_offset(0.01), SubpixelOffset::Zero);
-        assert_eq!(rm.subpixel_quantize_offset(0.05), SubpixelOffset::Zero);
-        assert_eq!(rm.subpixel_quantize_offset(0.12), SubpixelOffset::Zero);
-        assert_eq!(rm.subpixel_quantize_offset(0.124), SubpixelOffset::Zero);
-
-        assert_eq!(rm.subpixel_quantize_offset(0.125), SubpixelOffset::Quarter);
-        assert_eq!(rm.subpixel_quantize_offset(0.2), SubpixelOffset::Quarter);
-        assert_eq!(rm.subpixel_quantize_offset(0.25), SubpixelOffset::Quarter);
-        assert_eq!(rm.subpixel_quantize_offset(0.33), SubpixelOffset::Quarter);
-        assert_eq!(rm.subpixel_quantize_offset(0.374), SubpixelOffset::Quarter);
-
-        assert_eq!(rm.subpixel_quantize_offset(0.375), SubpixelOffset::Half);
-        assert_eq!(rm.subpixel_quantize_offset(0.4), SubpixelOffset::Half);
-        assert_eq!(rm.subpixel_quantize_offset(0.5), SubpixelOffset::Half);
-        assert_eq!(rm.subpixel_quantize_offset(0.58), SubpixelOffset::Half);
-        assert_eq!(rm.subpixel_quantize_offset(0.624), SubpixelOffset::Half);
-
-        assert_eq!(rm.subpixel_quantize_offset(0.625), SubpixelOffset::ThreeQuarters);
-        assert_eq!(rm.subpixel_quantize_offset(0.67), SubpixelOffset::ThreeQuarters);
-        assert_eq!(rm.subpixel_quantize_offset(0.7), SubpixelOffset::ThreeQuarters);
-        assert_eq!(rm.subpixel_quantize_offset(0.78), SubpixelOffset::ThreeQuarters);
-        assert_eq!(rm.subpixel_quantize_offset(0.874), SubpixelOffset::ThreeQuarters);
-
-        assert_eq!(rm.subpixel_quantize_offset(0.875), SubpixelOffset::Zero);
-        assert_eq!(rm.subpixel_quantize_offset(0.89), SubpixelOffset::Zero);
-        assert_eq!(rm.subpixel_quantize_offset(0.91), SubpixelOffset::Zero);
-        assert_eq!(rm.subpixel_quantize_offset(0.967), SubpixelOffset::Zero);
-        assert_eq!(rm.subpixel_quantize_offset(0.999), SubpixelOffset::Zero);
-
-        assert_eq!(rm.subpixel_quantize_offset(-1.0), SubpixelOffset::Zero);
-        assert_eq!(rm.subpixel_quantize_offset(1.0), SubpixelOffset::Zero);
-        assert_eq!(rm.subpixel_quantize_offset(1.5), SubpixelOffset::Half);
-        assert_eq!(rm.subpixel_quantize_offset(-1.625), SubpixelOffset::Half);
-        assert_eq!(rm.subpixel_quantize_offset(-4.33), SubpixelOffset::ThreeQuarters);
-
-    }
-}
--- a/gfx/webrender_api/src/image.rs
+++ b/gfx/webrender_api/src/image.rs
@@ -1,17 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 extern crate serde_bytes;
 
 use font::{FontInstanceKey, FontKey, FontTemplate};
 use std::sync::Arc;
-use {DevicePoint, DeviceUintRect, DeviceUintSize, IdNamespace, TileOffset, TileSize};
+use {DevicePoint, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
+use {IdNamespace, TileOffset, TileSize};
 use euclid::size2;
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ImageKey(pub IdNamespace, pub u32);
 
 impl ImageKey {
     pub const DUMMY: Self = ImageKey(IdNamespace(0), 0);
@@ -101,16 +102,23 @@ impl ImageDescriptor {
 
     pub fn compute_stride(&self) -> u32 {
         self.stride.unwrap_or(self.size.width * self.format.bytes_per_pixel())
     }
 
     pub fn compute_total_size(&self) -> u32 {
         self.compute_stride() * self.size.height
     }
+
+    pub fn full_rect(&self) -> DeviceUintRect {
+        DeviceUintRect::new(
+            DeviceUintPoint::zero(),
+            self.size,
+        )
+    }
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize)]
 pub enum ImageData {
     Raw(#[serde(with = "serde_image_data_raw")] Arc<Vec<u8>>),
     Blob(#[serde(with = "serde_image_data_raw")] Arc<BlobImageData>),
     External(ExternalImageData),
 }
--- a/gfx/webrender_api/src/units.rs
+++ b/gfx/webrender_api/src/units.rs
@@ -87,16 +87,18 @@ pub type WorldVector3D = TypedVector3D<f
 pub struct Tiles;
 pub type TileOffset = TypedPoint2D<u16, Tiles>;
 
 /// Scaling ratio from world pixels to device pixels.
 pub type DevicePixelScale = TypedScale<f32, WorldPixel, DevicePixel>;
 /// Scaling ratio from layout to world. Used for cases where we know the layout
 /// is in world space, or specifically want to treat it this way.
 pub type LayoutToWorldScale = TypedScale<f32, LayoutPixel, WorldPixel>;
+/// A complete scaling ratio from layout space to device pixel space.
+pub type LayoutToDeviceScale = TypedScale<f32, LayoutPixel, DevicePixel>;
 
 pub type LayoutTransform = TypedTransform3D<f32, LayoutPixel, LayoutPixel>;
 pub type LayoutToScrollTransform = TypedTransform3D<f32, LayoutPixel, ScrollLayerPixel>;
 pub type ScrollToLayoutTransform = TypedTransform3D<f32, ScrollLayerPixel, LayoutPixel>;
 pub type LayoutToWorldTransform = TypedTransform3D<f32, LayoutPixel, WorldPixel>;
 pub type WorldToLayoutTransform = TypedTransform3D<f32, WorldPixel, LayoutPixel>;
 pub type ScrollToWorldTransform = TypedTransform3D<f32, ScrollLayerPixel, WorldPixel>;
 
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-bb354abbf84602d3d8357c63c4f0b1139ec4deb1
+63c71ca9bbe4dec0ebc9c9bc8ab65b06a6b40641
--- a/gfx/wrench/src/json_frame_writer.rs
+++ b/gfx/wrench/src/json_frame_writer.rs
@@ -106,18 +106,18 @@ impl JsonFrameWriter {
 
         let mut file = fs::File::create(&frame_file_name).unwrap();
 
         let s = serde_json::to_string_pretty(&dl).unwrap();
         file.write_all(&s.into_bytes()).unwrap();
         file.write_all(b"\n").unwrap();
     }
 
-    fn update_resources(&mut self, updates: &ResourceUpdates) {
-        for update in &updates.updates {
+    fn update_resources(&mut self, updates: &[ResourceUpdate]) {
+        for update in updates {
             match *update {
                 ResourceUpdate::AddImage(ref img) => {
                     let stride = img.descriptor.stride.unwrap_or(
                         img.descriptor.size.width * img.descriptor.format.bytes_per_pixel(),
                     );
                     let bytes = match img.data {
                         ImageData::Raw(ref v) => (**v).clone(),
                         ImageData::External(_) | ImageData::Blob(_) => {
--- a/gfx/wrench/src/rawtest.rs
+++ b/gfx/wrench/src/rawtest.rs
@@ -25,76 +25,80 @@ fn size<T: Copy, U>(x: T, y: T) -> Typed
     TypedSize2D::new(x, y)
 }
 
 fn rect<T: Copy, U>(x: T, y: T, width: T, height: T) -> TypedRect<T, U> {
     TypedRect::new(point(x, y), size(width, height))
 }
 
 impl<'a> RawtestHarness<'a> {
-    pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: &'a Receiver<NotifierEvent>) -> Self {
+    pub fn new(wrench: &'a mut Wrench,
+               window: &'a mut WindowWrapper,
+               rx: &'a Receiver<NotifierEvent>) -> Self {
         RawtestHarness {
             wrench,
             rx,
             window,
         }
     }
 
     pub fn run(mut self) {
         self.test_hit_testing();
         self.test_retained_blob_images_test();
         self.test_blob_update_test();
         self.test_blob_update_epoch_test();
         self.test_tile_decomposition();
         self.test_very_large_blob();
+        self.test_offscreen_blob();
         self.test_save_restore();
         self.test_capture();
         self.test_zero_height_window();
     }
 
     fn render_and_get_pixels(&mut self, window_rect: DeviceUintRect) -> Vec<u8> {
         self.rx.recv().unwrap();
         self.wrench.render();
         self.wrench.renderer.read_pixels_rgba8(window_rect)
     }
 
     fn submit_dl(
         &mut self,
         epoch: &mut Epoch,
         layout_size: LayoutSize,
         builder: DisplayListBuilder,
-        resources: Option<ResourceUpdates>
+        resources: &[ResourceUpdate]
     ) {
         let mut txn = Transaction::new();
         let root_background_color = Some(ColorF::new(1.0, 1.0, 1.0, 1.0));
         txn.use_scene_builder_thread();
-        if let Some(resources) = resources {
-            txn.update_resources(resources);
+        if !resources.is_empty() {
+            txn.resource_updates = resources.to_vec();
         }
         txn.set_display_list(
             *epoch,
             root_background_color,
             layout_size,
             builder.finalize(),
             false,
         );
         epoch.0 += 1;
 
         txn.generate_frame();
         self.wrench.api.send_transaction(self.wrench.document_id, txn);
     }
 
+
     fn test_tile_decomposition(&mut self) {
         println!("\ttile decomposition...");
         // This exposes a crash in tile decomposition
         let layout_size = LayoutSize::new(800., 800.);
-        let mut resources = ResourceUpdates::new();
+        let mut txn = Transaction::new();
 
         let blob_img = self.wrench.api.generate_image_key();
-        resources.add_image(
+        txn.add_image(
             blob_img,
             ImageDescriptor::new(151, 56, ImageFormat::BGRA8, true, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
             Some(128),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
@@ -107,26 +111,26 @@ impl<'a> RawtestHarness<'a> {
             size(151.0, 56.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img,
         );
 
         let mut epoch = Epoch(0);
 
-        self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
+        self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
         self.rx.recv().unwrap();
         self.wrench.render();
 
         // Leaving a tiled blob image in the resource cache
         // confuses the `test_capture`. TODO: remove this
-        resources = ResourceUpdates::new();
-        resources.delete_image(blob_img);
-        self.wrench.api.update_resources(resources);
+        txn = Transaction::new();
+        txn.delete_image(blob_img);
+        self.wrench.api.update_resources(txn.resource_updates);
     }
 
     fn test_very_large_blob(&mut self) {
         println!("\tvery large blob...");
 
         assert_eq!(self.wrench.device_pixel_ratio, 1.);
 
         let window_size = self.window.get_inner_size();
@@ -135,20 +139,20 @@ impl<'a> RawtestHarness<'a> {
 
         let window_rect = DeviceUintRect::new(
             DeviceUintPoint::new(0, window_size.height - test_size.height),
             test_size,
         );
 
         // This exposes a crash in tile decomposition
         let layout_size = LayoutSize::new(800., 800.);
-        let mut resources = ResourceUpdates::new();
+        let mut txn = Transaction::new();
 
         let blob_img = self.wrench.api.generate_image_key();
-        resources.add_image(
+        txn.add_image(
             blob_img,
             ImageDescriptor::new(1510, 111256, ImageFormat::BGRA8, false, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
             Some(31),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
@@ -167,69 +171,195 @@ impl<'a> RawtestHarness<'a> {
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img,
         );
         builder.pop_clip_id();
 
         let mut epoch = Epoch(0);
 
-        self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
+        self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
         let called = Arc::new(AtomicIsize::new(0));
         let called_inner = Arc::clone(&called);
 
         self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
             called_inner.fetch_add(1, Ordering::SeqCst);
         });
 
         let pixels = self.render_and_get_pixels(window_rect);
 
         // make sure we didn't request too many blobs
         assert_eq!(called.load(Ordering::SeqCst), 16);
 
         // make sure things are in the right spot
         assert!(
-            pixels[(148 + (window_rect.size.height as usize - 148) * window_rect.size.width as usize) * 4] == 255 &&
-            pixels[(148 + (window_rect.size.height as usize - 148) * window_rect.size.width as usize) * 4 + 1] == 255 &&
-            pixels[(148 + (window_rect.size.height as usize - 148) * window_rect.size.width as usize) * 4 + 2] == 255 &&
-            pixels[(148 + (window_rect.size.height as usize - 148) * window_rect.size.width as usize) * 4 + 3] == 255
+            pixels[(148 +
+                (window_rect.size.height as usize - 148) *
+                    window_rect.size.width as usize) * 4] == 255 &&
+                pixels[(148 +
+                    (window_rect.size.height as usize - 148) *
+                        window_rect.size.width as usize) * 4 + 1] == 255 &&
+                pixels[(148 +
+                    (window_rect.size.height as usize - 148) *
+                        window_rect.size.width as usize) * 4 + 2] == 255 &&
+                pixels[(148 +
+                    (window_rect.size.height as usize - 148) *
+                        window_rect.size.width as usize) * 4 + 3] == 255
         );
         assert!(
-            pixels[(132 + (window_rect.size.height as usize - 148) * window_rect.size.width as usize) * 4] == 50 &&
-            pixels[(132 + (window_rect.size.height as usize - 148) * window_rect.size.width as usize) * 4 + 1] == 50 &&
-            pixels[(132 + (window_rect.size.height as usize - 148) * window_rect.size.width as usize) * 4 + 2] == 150 &&
-            pixels[(132 + (window_rect.size.height as usize - 148) * window_rect.size.width as usize) * 4 + 3] == 255
+            pixels[(132 +
+                (window_rect.size.height as usize - 148) *
+                    window_rect.size.width as usize) * 4] == 50 &&
+                pixels[(132 +
+                    (window_rect.size.height as usize - 148) *
+                        window_rect.size.width as usize) * 4 + 1] == 50 &&
+                pixels[(132 +
+                    (window_rect.size.height as usize - 148) *
+                        window_rect.size.width as usize) * 4 + 2] == 150 &&
+                pixels[(132 +
+                    (window_rect.size.height as usize - 148) *
+                        window_rect.size.width as usize) * 4 + 3] == 255
         );
 
         // Leaving a tiled blob image in the resource cache
         // confuses the `test_capture`. TODO: remove this
-        resources = ResourceUpdates::new();
-        resources.delete_image(blob_img);
-        self.wrench.api.update_resources(resources);
+        txn = Transaction::new();
+        txn.delete_image(blob_img);
+        self.wrench.api.update_resources(txn.resource_updates);
+    }
+
+    fn test_offscreen_blob(&mut self) {
+        println!("\toffscreen blob update.");
+
+        assert_eq!(self.wrench.device_pixel_ratio, 1.);
+
+        let window_size = self.window.get_inner_size();
+
+        let test_size = DeviceUintSize::new(800, 800);
+
+        let window_rect = DeviceUintRect::new(
+            DeviceUintPoint::new(0, window_size.height - test_size.height),
+            test_size,
+        );
+
+        // This exposes a crash in tile decomposition
+        let mut txn = Transaction::new();
+        let layout_size = LayoutSize::new(800., 800.);
+
+        let blob_img = self.wrench.api.generate_image_key();
+        txn.add_image(
+            blob_img,
+            ImageDescriptor::new(1510, 1510, ImageFormat::BGRA8, false, false),
+            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
+            None,
+        );
+
+        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
+
+        let info = LayoutPrimitiveInfo::new(rect(0., 0.0, 1510., 1510.));
+
+        let image_size = size(1510., 1510.);
+
+        // setup some malicious image size parameters
+        builder.push_image(
+            &info,
+            image_size,
+            image_size,
+            ImageRendering::Auto,
+            AlphaType::PremultipliedAlpha,
+            blob_img,
+        );
+
+        let mut epoch = Epoch(0);
+
+        self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
+
+        let original_pixels = self.render_and_get_pixels(window_rect);
+
+        let mut epoch = Epoch(1);
+
+        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
+
+        let info = LayoutPrimitiveInfo::new(rect(-10000., 0.0, 1510., 1510.));
+
+        let image_size = size(1510., 1510.);
+
+        // setup some malicious image size parameters
+        builder.push_image(
+            &info,
+            image_size,
+            image_size,
+            ImageRendering::Auto,
+            AlphaType::PremultipliedAlpha,
+            blob_img,
+        );
+
+        self.submit_dl(&mut epoch, layout_size, builder, &[]);
+
+        let _offscreen_pixels = self.render_and_get_pixels(window_rect);
+
+        let mut txn = Transaction::new();
+
+        txn.update_image(
+            blob_img,
+            ImageDescriptor::new(1510, 1510, ImageFormat::BGRA8, false, false),
+            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
+            Some(rect(10, 10, 100, 100)),
+        );
+
+        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
+
+        let info = LayoutPrimitiveInfo::new(rect(0., 0.0, 1510., 1510.));
+
+        let image_size = size(1510., 1510.);
+
+        // setup some malicious image size parameters
+        builder.push_image(
+            &info,
+            image_size,
+            image_size,
+            ImageRendering::Auto,
+            AlphaType::PremultipliedAlpha,
+            blob_img,
+        );
+
+        let mut epoch = Epoch(2);
+
+        self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
+
+        let pixels = self.render_and_get_pixels(window_rect);
+
+        assert!(pixels == original_pixels);
+
+        // Leaving a tiled blob image in the resource cache
+        // confuses the `test_capture`. TODO: remove this
+        txn = Transaction::new();
+        txn.delete_image(blob_img);
+        self.wrench.api.update_resources(txn.resource_updates);
     }
 
     fn test_retained_blob_images_test(&mut self) {
         println!("\tretained blob images test...");
         let blob_img;
         let window_size = self.window.get_inner_size();
 
         let test_size = DeviceUintSize::new(400, 400);
 
         let window_rect = DeviceUintRect::new(
             DeviceUintPoint::new(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
-        let mut resources = ResourceUpdates::new();
+        let mut txn = Transaction::new();
         {
             let api = &self.wrench.api;
 
             blob_img = api.generate_image_key();
-            resources.add_image(
+            txn.add_image(
                 blob_img,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
                 ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
                 None,
             );
         }
 
         // draw the blob the first time
@@ -242,17 +372,17 @@ impl<'a> RawtestHarness<'a> {
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img,
         );
 
         let mut epoch = Epoch(0);
 
-        self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
+        self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
         let called = Arc::new(AtomicIsize::new(0));
         let called_inner = Arc::clone(&called);
 
         self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
             called_inner.fetch_add(1, Ordering::SeqCst);
         });
 
@@ -269,17 +399,19 @@ impl<'a> RawtestHarness<'a> {
             &info,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img,
         );
 
-        self.submit_dl(&mut epoch, layout_size, builder, None);
+        txn.resource_updates.clear();
+
+        self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
         let pixels_second = self.render_and_get_pixels(window_rect);
 
         // make sure we only requested once
         assert!(called.load(Ordering::SeqCst) == 1);
 
         // use png;
         // png::save_flipped("out1.png", &pixels_first, window_rect.size);
@@ -295,29 +427,29 @@ impl<'a> RawtestHarness<'a> {
 
         let test_size = DeviceUintSize::new(400, 400);
 
         let window_rect = DeviceUintRect::new(
             point(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
-        let mut resources = ResourceUpdates::new();
+        let mut txn = Transaction::new();
         let (blob_img, blob_img2) = {
             let api = &self.wrench.api;
 
             blob_img = api.generate_image_key();
-            resources.add_image(
+            txn.add_image(
                 blob_img,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
                 ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
                 None,
             );
             blob_img2 = api.generate_image_key();
-            resources.add_image(
+            txn.add_image(
                 blob_img2,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
                 ImageData::new_blob_image(blob::serialize_blob(ColorU::new(80, 50, 150, 255))),
                 None,
             );
             (blob_img, blob_img2)
         };
 
@@ -359,53 +491,53 @@ impl<'a> RawtestHarness<'a> {
                 blob_img2,
             );
         };
 
         push_images(&mut builder);
 
         let mut epoch = Epoch(0);
 
-        self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
+        self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let _pixels_first = self.render_and_get_pixels(window_rect);
 
 
         // update and redraw both images
-        let mut resources = ResourceUpdates::new();
-        resources.update_image(
+        let mut txn = Transaction::new();
+        txn.update_image(
             blob_img,
             ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
             Some(rect(100, 100, 100, 100)),
         );
-        resources.update_image(
+        txn.update_image(
             blob_img2,
             ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(59, 50, 150, 255))),
             Some(rect(100, 100, 100, 100)),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         push_images(&mut builder);
-        self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
+        self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let _pixels_second = self.render_and_get_pixels(window_rect);
 
 
         // only update the first image
-        let mut resources = ResourceUpdates::new();
-        resources.update_image(
+        let mut txn = Transaction::new();
+        txn.update_image(
             blob_img,
             ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 150, 150, 255))),
             Some(rect(200, 200, 100, 100)),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         push_images(&mut builder);
-        self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
+        self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let _pixels_third = self.render_and_get_pixels(window_rect);
 
         // the first image should be requested 3 times
         assert_eq!(img1_requested.load(Ordering::SeqCst), 3);
         // the second image should've been requested twice
         assert_eq!(img2_requested.load(Ordering::SeqCst), 2);
     }
 
@@ -415,21 +547,21 @@ impl<'a> RawtestHarness<'a> {
 
         let test_size = DeviceUintSize::new(400, 400);
 
         let window_rect = DeviceUintRect::new(
             point(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
-        let mut resources = ResourceUpdates::new();
+        let mut txn = Transaction::new();
 
         let blob_img = {
             let img = self.wrench.api.generate_image_key();
-            resources.add_image(
+            txn.add_image(
                 img,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
                 ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
                 None,
             );
             img
         };
 
@@ -443,22 +575,22 @@ impl<'a> RawtestHarness<'a> {
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img,
         );
 
         let mut epoch = Epoch(0);
 
-        self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
+        self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let pixels_first = self.render_and_get_pixels(window_rect);
 
         // draw the blob image a second time after updating it with the same color
-        let mut resources = ResourceUpdates::new();
-        resources.update_image(
+        let mut txn = Transaction::new();
+        txn.update_image(
             blob_img,
             ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
             Some(rect(100, 100, 100, 100)),
         );
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
@@ -467,22 +599,22 @@ impl<'a> RawtestHarness<'a> {
             &info,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img,
         );
 
-        self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
+        self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let pixels_second = self.render_and_get_pixels(window_rect);
 
         // draw the blob image a third time after updating it with a different color
-        let mut resources = ResourceUpdates::new();
-        resources.update_image(
+        let mut txn = Transaction::new();
+        txn.update_image(
             blob_img,
             ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 150, 150, 255))),
             Some(rect(200, 200, 100, 100)),
         );
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
@@ -491,17 +623,17 @@ impl<'a> RawtestHarness<'a> {
             &info,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img,
         );
 
-        self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
+        self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let pixels_third = self.render_and_get_pixels(window_rect);
 
         assert!(pixels_first == pixels_second);
         assert!(pixels_first != pixels_third);
     }
 
     // Ensures that content doing a save-restore produces the same results as not
     fn test_save_restore(&mut self) {
@@ -532,23 +664,23 @@ impl<'a> RawtestHarness<'a> {
                 builder.save();
                 let clip = builder.define_clip(
                     rect(80., 80., 90., 90.),
                     None::<ComplexClipRegion>,
                     None
                 );
                 builder.push_clip_id(clip);
                 builder.push_rect(&PrimitiveInfo::new(rect(110., 110., 50., 50.)),
-                              ColorF::new(0.0, 1.0, 0.0, 1.0));
+                                  ColorF::new(0.0, 1.0, 0.0, 1.0));
                 builder.push_shadow(&PrimitiveInfo::new(rect(100., 100., 100., 100.)),
-                    Shadow {
-                        offset: LayoutVector2D::new(1.0, 1.0),
-                        blur_radius: 1.0,
-                        color: ColorF::new(0.0, 0.0, 0.0, 1.0),
-                    });
+                                    Shadow {
+                                        offset: LayoutVector2D::new(1.0, 1.0),
+                                        blur_radius: 1.0,
+                                        color: ColorF::new(0.0, 0.0, 0.0, 1.0),
+                                    });
                 builder.push_line(&PrimitiveInfo::new(rect(110., 110., 50., 2.)),
                                   0.0, LineOrientation::Horizontal,
                                   &ColorF::new(0.0, 0.0, 0.0, 1.0), LineStyle::Solid);
                 builder.restore();
             }
 
             {
                 builder.save();
@@ -562,17 +694,19 @@ impl<'a> RawtestHarness<'a> {
                                   ColorF::new(0.0, 0.0, 1.0, 1.0));
 
                 builder.pop_clip_id();
                 builder.clear_save();
             }
 
             builder.pop_clip_id();
 
-            self.submit_dl(&mut Epoch(0), layout_size, builder, None);
+            let txn = Transaction::new();
+
+            self.submit_dl(&mut Epoch(0), layout_size, builder, &txn.resource_updates);
 
             self.render_and_get_pixels(window_rect)
         };
 
         let first = do_test(false);
         let second = do_test(true);
 
         assert_eq!(first, second);
@@ -585,19 +719,19 @@ impl<'a> RawtestHarness<'a> {
         let dim = self.window.get_inner_size();
         let window_rect = DeviceUintRect::new(
             point(0, dim.height - layout_size.height as u32),
             size(layout_size.width as u32, layout_size.height as u32),
         );
 
         // 1. render some scene
 
-        let mut resources = ResourceUpdates::new();
+        let mut txn = Transaction::new();
         let image = self.wrench.api.generate_image_key();
-        resources.add_image(
+        txn.add_image(
             image,
             ImageDescriptor::new(1, 1, ImageFormat::BGRA8, true, false),
             ImageData::new(vec![0xFF, 0, 0, 0xFF]),
             None,
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
@@ -663,17 +797,18 @@ impl<'a> RawtestHarness<'a> {
     fn test_zero_height_window(&mut self) {
         println!("\tzero height test...");
 
         let layout_size = LayoutSize::new(120.0, 0.0);
         let window_size = DeviceUintSize::new(layout_size.width as u32, layout_size.height as u32);
         let doc_id = self.wrench.api.add_document(window_size, 1);
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
-        let info = LayoutPrimitiveInfo::new(LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(100.0, 100.0)));
+        let info = LayoutPrimitiveInfo::new(LayoutRect::new(LayoutPoint::zero(),
+                                                            LayoutSize::new(100.0, 100.0)));
         builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
 
         let mut txn = Transaction::new();
         txn.set_root_pipeline(self.wrench.root_pipeline_id);
         txn.set_display_list(
             Epoch(1),
             Some(ColorF::new(1.0, 0.0, 0.0, 1.0)),
             layout_size,
@@ -704,17 +839,17 @@ impl<'a> RawtestHarness<'a> {
         // Add a simple 100x100 rectangle at 100,0.
         let mut info = LayoutPrimitiveInfo::new(LayoutRect::new(
             LayoutPoint::new(100., 0.),
             LayoutSize::new(100., 100.)
         ));
         info.tag = Some((0, 2));
         builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
-        let make_rounded_complex_clip = | rect: &LayoutRect, radius: f32 | -> ComplexClipRegion {
+        let make_rounded_complex_clip = |rect: &LayoutRect, radius: f32| -> ComplexClipRegion {
             ComplexClipRegion::new(
                 *rect,
                 BorderRadius::uniform_size(LayoutSize::new(radius, radius)),
                 ClipMode::Clip
             )
         };
 
 
@@ -738,32 +873,33 @@ impl<'a> RawtestHarness<'a> {
         ));
         let mut info = LayoutPrimitiveInfo::new(rect);
         info.tag = Some((0, 5));
         builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
         builder.pop_clip_id();
 
 
         let mut epoch = Epoch(0);
-        self.submit_dl(&mut epoch, layout_size, builder, None);
+        let txn = Transaction::new();
+        self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
         // We render to ensure that the hit tester is up to date with the current scene.
         self.rx.recv().unwrap();
         self.wrench.render();
 
-        let hit_test = | point: WorldPoint | -> HitTestResult {
+        let hit_test = |point: WorldPoint| -> HitTestResult {
             self.wrench.api.hit_test(
                 self.wrench.document_id,
                 None,
                 point,
                 HitTestFlags::FIND_ALL,
             )
         };
 
-        let assert_hit_test = | point: WorldPoint, tags: Vec<ItemTag> | {
+        let assert_hit_test = |point: WorldPoint, tags: Vec<ItemTag>| {
             let result = hit_test(point);
             assert_eq!(result.items.len(), tags.len());
 
             for (hit_test_item, item_b) in result.items.iter().zip(tags.iter()) {
                 assert_eq!(hit_test_item.tag, *item_b);
             }
         };
 
@@ -774,17 +910,17 @@ impl<'a> RawtestHarness<'a> {
         assert_hit_test(WorldPoint::new(100., 450.), Vec::new());
 
         // The top left corner of the scene should only contain the background.
         assert_hit_test(WorldPoint::new(50., 50.), vec![(0, 1)]);
 
         // The middle of the normal rectangle should be hit.
         assert_hit_test(WorldPoint::new(150., 50.), vec![(0, 2), (0, 1)]);
 
-        let test_rounded_rectangle = | point: WorldPoint, size: WorldSize, tag: ItemTag | {
+        let test_rounded_rectangle = |point: WorldPoint, size: WorldSize, tag: ItemTag| {
             // The cut out corners of the rounded rectangle should not be hit.
             let top_left = point + WorldVector2D::new(5., 5.);
             let bottom_right = point + size.to_vector() - WorldVector2D::new(5., 5.);
 
             assert_hit_test(
                 WorldPoint::new(point.x + (size.width / 2.), point.y + (size.height / 2.)),
                 vec![tag, (0, 1)]
             );
--- a/gfx/wrench/src/ron_frame_writer.rs
+++ b/gfx/wrench/src/ron_frame_writer.rs
@@ -82,18 +82,18 @@ impl RonFrameWriter {
 
         let mut file = fs::File::create(&frame_file_name).unwrap();
 
         let s = ron::ser::to_string_pretty(&dl, Default::default()).unwrap();
         file.write_all(&s.into_bytes()).unwrap();
         file.write_all(b"\n").unwrap();
     }
 
-    fn update_resources(&mut self, updates: &ResourceUpdates) {
-        for update in &updates.updates {
+    fn update_resources(&mut self, updates: &[ResourceUpdate]) {
+        for update in updates {
             match *update {
                 ResourceUpdate::AddImage(ref img) => {
                     let bytes = match img.data {
                         ImageData::Raw(ref v) => (**v).clone(),
                         ImageData::External(_) | ImageData::Blob(_) => {
                             return;
                         }
                     };
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -275,44 +275,31 @@ impl Wrench {
         self.api.send_transaction(self.document_id, txn);
         self.set_title("");
     }
 
     pub fn layout_simple_ascii(
         &mut self,
         font_key: FontKey,
         instance_key: FontInstanceKey,
-        render_mode: Option<FontRenderMode>,
         text: &str,
         size: Au,
         origin: LayoutPoint,
         flags: FontInstanceFlags,
     ) -> (Vec<u32>, Vec<LayoutPoint>, LayoutRect) {
         // Map the string codepoints to glyph indices in this font.
         // Just drop any glyph that isn't present in this font.
         let indices: Vec<u32> = self.api
             .get_glyph_indices(font_key, text)
             .iter()
             .filter_map(|idx| *idx)
             .collect();
 
-        let render_mode = render_mode.unwrap_or(<FontInstanceOptions as Default>::default().render_mode);
-        let subpx_dir = SubpixelDirection::Horizontal.limit_by(render_mode);
-
         // Retrieve the metrics for each glyph.
-        let mut keys = Vec::new();
-        for glyph_index in &indices {
-            keys.push(GlyphKey::new(
-                *glyph_index,
-                LayoutPoint::zero(),
-                render_mode,
-                subpx_dir,
-            ));
-        }
-        let metrics = self.api.get_glyph_dimensions(instance_key, keys);
+        let metrics = self.api.get_glyph_dimensions(instance_key, indices.clone());
 
         let mut bounding_rect = LayoutRect::zero();
         let mut positions = Vec::new();
 
         let mut cursor = origin;
         let direction = if flags.contains(FontInstanceFlags::TRANSPOSE) {
             LayoutVector2D::new(
                 0.0,
@@ -380,19 +367,19 @@ impl Wrench {
             self.window_size.width as f32,
             self.window_size.height as f32,
         )
     }
 
     #[cfg(target_os = "windows")]
     pub fn font_key_from_native_handle(&mut self, descriptor: &NativeFontHandle) -> FontKey {
         let key = self.api.generate_font_key();
-        let mut resources = ResourceUpdates::new();
-        resources.add_native_font(key, descriptor.clone());
-        self.api.update_resources(resources);
+        let mut txn = Transaction::new();
+        txn.add_native_font(key, descriptor.clone());
+        self.api.update_resources(txn.resource_updates);
         key
     }
 
     #[cfg(target_os = "windows")]
     pub fn font_key_from_name(&mut self, font_name: &str) -> FontKey {
         let system_fc = dwrote::FontCollection::system();
         let family = system_fc.get_font_family_by_name(font_name).unwrap();
         let font = family.get_first_matching_font(
@@ -451,49 +438,49 @@ impl Wrench {
 
     #[cfg(target_os = "android")]
     pub fn font_key_from_name(&mut self, font_name: &str) -> FontKey {
         unimplemented!()
     }
 
     pub fn font_key_from_bytes(&mut self, bytes: Vec<u8>, index: u32) -> FontKey {
         let key = self.api.generate_font_key();
-        let mut update = ResourceUpdates::new();
-        update.add_raw_font(key, bytes, index);
-        self.api.update_resources(update);
+        let mut txn = Transaction::new();
+        txn.add_raw_font(key, bytes, index);
+        self.api.update_resources(txn.resource_updates);
         key
     }
 
     pub fn add_font_instance(&mut self,
         font_key: FontKey,
         size: Au,
         flags: FontInstanceFlags,
         render_mode: Option<FontRenderMode>,
         bg_color: Option<ColorU>,
     ) -> FontInstanceKey {
         let key = self.api.generate_font_instance_key();
-        let mut update = ResourceUpdates::new();
+        let mut txn = Transaction::new();
         let mut options: FontInstanceOptions = Default::default();
         options.flags |= flags;
         if let Some(render_mode) = render_mode {
             options.render_mode = render_mode;
         }
         if let Some(bg_color) = bg_color {
             options.bg_color = bg_color;
         }
-        update.add_font_instance(key, font_key, size, Some(options), None, Vec::new());
-        self.api.update_resources(update);
+        txn.add_font_instance(key, font_key, size, Some(options), None, Vec::new());
+        self.api.update_resources(txn.resource_updates);
         key
     }
 
     #[allow(dead_code)]
     pub fn delete_font_instance(&mut self, key: FontInstanceKey) {
-        let mut update = ResourceUpdates::new();
-        update.delete_font_instance(key);
-        self.api.update_resources(update);
+        let mut txn = Transaction::new();
+        txn.delete_font_instance(key);
+        self.api.update_resources(txn.resource_updates);
     }
 
     pub fn update(&mut self, dim: DeviceUintSize) {
         if dim != self.window_size {
             self.window_size = dim;
         }
     }
 
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -225,27 +225,27 @@ impl YamlFrameReader {
             font_render_mode: None,
             image_map: HashMap::new(),
             clip_id_map: HashMap::new(),
             allow_mipmaps: false,
         }
     }
 
     pub fn deinit(mut self, wrench: &mut Wrench) {
-        let mut updates = ResourceUpdates::new();
+        let mut txn = Transaction::new();
 
         for (_, font_instance) in self.font_instances.drain() {
-            updates.delete_font_instance(font_instance);
+            txn.delete_font_instance(font_instance);
         }
 
         for (_, font) in self.fonts.drain() {
-            updates.delete_font(font);
+            txn.delete_font(font);
         }
 
-        wrench.api.update_resources(updates);
+        wrench.api.update_resources(txn.resource_updates);
     }
 
     pub fn yaml_path(&self) -> &PathBuf {
         &self.yaml_path
     }
 
     pub fn new_from_args(args: &clap::ArgMatches) -> YamlFrameReader {
         let yaml_file = args.value_of("INPUT").map(|s| PathBuf::from(s)).unwrap();
@@ -506,19 +506,19 @@ impl YamlFrameReader {
                     _ => {
                         panic!("Failed to load image {:?}", file.to_str());
                     }
                 }
             }
         };
         let tiling = tiling.map(|tile_size| tile_size as u16);
         let image_key = wrench.api.generate_image_key();
-        let mut resources = ResourceUpdates::new();
-        resources.add_image(image_key, descriptor, image_data, tiling);
-        wrench.api.update_resources(resources);
+        let mut txn = Transaction::new();
+        txn.add_image(image_key, descriptor, image_data, tiling);
+        wrench.api.update_resources(txn.resource_updates);
         let val = (
             image_key,
             LayoutSize::new(descriptor.size.width as f32, descriptor.size.height as f32),
         );
         self.image_map.insert(key, val);
         val
     }
 
@@ -1205,17 +1205,16 @@ impl YamlFrameReader {
         } else {
             let text = item["text"].as_str().unwrap();
             let origin = item["origin"]
                 .as_point()
                 .expect("origin required for text without glyphs");
             let (glyph_indices, glyph_positions, bounds) = wrench.layout_simple_ascii(
                 font_key,
                 font_instance_key,
-                self.font_render_mode,
                 text,
                 size,
                 origin,
                 flags,
             );
 
             let glyphs = glyph_indices
                 .iter()
--- a/gfx/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wrench/src/yaml_frame_writer.rs
@@ -498,18 +498,18 @@ impl YamlFrameWriter {
             frame_file_name.push(format!("frame-{}.yaml", current_shown_frame));
             let mut file = fs::File::create(&frame_file_name).unwrap();
             file.write_all(&sb).unwrap();
         }
 
         scene.finish_display_list(self.pipeline_id.unwrap(), dl);
     }
 
-    fn update_resources(&mut self, updates: &ResourceUpdates) {
-        for update in &updates.updates {
+    fn update_resources(&mut self, updates: &[ResourceUpdate]) {
+        for update in updates {
             match *update {
                 ResourceUpdate::AddImage(ref img) => {
                     if let Some(ref data) = self.images.get(&img.key) {
                           if data.path.is_some() {
                               return;
                           }
                     }
 
@@ -540,17 +540,18 @@ impl YamlFrameWriter {
                         assert_eq!(data.width, img.descriptor.size.width);
                         assert_eq!(data.height, img.descriptor.size.height);
                         assert_eq!(data.format, img.descriptor.format);
 
                         if let ImageData::Raw(ref bytes) = img.data {
                             data.path = None;
                             data.bytes = Some((**bytes).clone());
                         } else {
-                            // Other existing image types only make sense within the gecko integration.
+                            // Other existing image types only make sense
+                            // within the gecko integration.
                             println!(
                                 "Wrench only supports updating buffer images ({}).",
                                 "ignoring update command"
                             );
                         }
                     }
                 }
                 ResourceUpdate::DeleteImage(img) => {