Bug 1429806 - Update webrender to commit eb9e1702df4b6dc036b649b3dd32ccc4bfbe43bf. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 16 Jan 2018 12:28:30 -0500
changeset 720978 fd33d6399d617e75ece67055b4a4b07fb497c834
parent 720975 9be7249e74fd7f6d9163b59d3386ed01038197a0
child 720979 3b9ff3bf2466219bb16fa5db0d4a2277045b8134
push id95717
push userkgupta@mozilla.com
push dateTue, 16 Jan 2018 17:35:10 +0000
reviewersjrmuizel
bugs1429806
milestone59.0a1
Bug 1429806 - Update webrender to commit eb9e1702df4b6dc036b649b3dd32ccc4bfbe43bf. r?jrmuizel MozReview-Commit-ID: 7m0P5kgiWZF
gfx/doc/README.webrender
gfx/webrender/Cargo.toml
gfx/webrender/examples/alpha_perf.rs
gfx/webrender/examples/basic.rs
gfx/webrender/examples/blob.rs
gfx/webrender/examples/common/boilerplate.rs
gfx/webrender/examples/frame_output.rs
gfx/webrender/examples/image_resize.rs
gfx/webrender/examples/texture_cache_stress.rs
gfx/webrender/examples/yuv.rs
gfx/webrender/res/brush.glsl
gfx/webrender/res/brush_image.glsl
gfx/webrender/res/cs_blur.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/shared_border.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/border.rs
gfx/webrender/src/box_shadow.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/debug_render.rs
gfx/webrender/src/device.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/gpu_types.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/platform/macos/font.rs
gfx/webrender/src/platform/unix/font.rs
gfx/webrender/src/platform/windows/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/profiler.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/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_api/src/font.rs
gfx/webrender_api/src/image.rs
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -170,9 +170,9 @@ 2. Sometimes autoland tip has changed en
    has an env var you can set to do this). In theory you can get the same
    result by resolving the conflict manually but Cargo.lock files are usually not
    trivial to merge by hand. If it's just the third_party/rust dir that has conflicts
    you can delete it and run |mach vendor rust| again to repopulate it.
 
 -------------------------------------------------------------------------------
 
 The version of WebRender currently in the tree is:
-722e8b104bf99d29d61ecb1fe456eebe89ad81fd
+eb9e1702df4b6dc036b649b3dd32ccc4bfbe43bf
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -30,17 +30,17 @@ bitflags = "1.0"
 thread_profiler = "0.1.1"
 plane-split = "0.7"
 smallvec = "0.6"
 ws = { optional = true, version = "0.7.3" }
 serde_json = { optional = true, version = "1.0" }
 serde = { optional = true, version = "1.0", features = ["serde_derive"] }
 image = { optional = true, version = "0.17" }
 base64 = { optional = true, version = "0.3.0" }
-ron = { optional = true, version = "0.1.3" }
+ron = { optional = true, version = "0.1.5" }
 
 [dev-dependencies]
 angle = {git = "https://github.com/servo/angle", branch = "servo"}
 env_logger = "0.4"
 rand = "0.3"                # for the benchmarks
 servo-glutin = "0.13"     # for the example apps
 
 [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/examples/alpha_perf.rs
@@ -0,0 +1,86 @@
+/* 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 euclid;
+extern crate gleam;
+extern crate glutin;
+extern crate webrender;
+
+#[path = "common/boilerplate.rs"]
+mod boilerplate;
+
+use boilerplate::{Example, HandyDandyRectBuilder};
+use std::cmp;
+use webrender::api::*;
+
+struct App {
+    rect_count: usize,
+}
+
+impl Example for App {
+    fn render(
+        &mut self,
+        _api: &RenderApi,
+        builder: &mut DisplayListBuilder,
+        _resources: &mut ResourceUpdates,
+        _framebuffer_size: DeviceUintSize,
+        _pipeline_id: PipelineId,
+        _document_id: DocumentId,
+    ) {
+        let bounds = (0, 0).to(1920, 1080);
+        let info = LayoutPrimitiveInfo {
+            local_clip: LocalClip::Rect(bounds),
+            .. LayoutPrimitiveInfo::new(bounds)
+        };
+
+        builder.push_stacking_context(
+            &info,
+            ScrollPolicy::Scrollable,
+            None,
+            TransformStyle::Flat,
+            None,
+            MixBlendMode::Normal,
+            Vec::new(),
+        );
+
+        for _ in 0 .. self.rect_count {
+            builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 0.05));
+        }
+
+        builder.pop_stacking_context();
+    }
+
+    fn on_event(
+        &mut self,
+        event: glutin::Event,
+        _api: &RenderApi,
+        _document_id: DocumentId
+    ) -> bool {
+        match event {
+            glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
+                match key {
+                    glutin::VirtualKeyCode::Right => {
+                        self.rect_count += 1;
+                        println!("rects = {}", self.rect_count);
+                    }
+                    glutin::VirtualKeyCode::Left => {
+                        self.rect_count = cmp::max(self.rect_count, 1) - 1;
+                        println!("rects = {}", self.rect_count);
+                    }
+                    _ => {}
+                };
+            }
+            _ => (),
+        }
+
+        true
+    }
+}
+
+fn main() {
+    let mut app = App {
+        rect_count: 1,
+    };
+    boilerplate::main_wrapper(&mut app, None);
+}
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -207,17 +207,17 @@ impl Example for App {
             None,
             MixBlendMode::Normal,
             Vec::new(),
         );
 
         let image_mask_key = api.generate_image_key();
         resources.add_image(
             image_mask_key,
-            ImageDescriptor::new(2, 2, ImageFormat::A8, true),
+            ImageDescriptor::new(2, 2, ImageFormat::R8, true),
             ImageData::new(vec![0, 80, 180, 255]),
             None,
         );
         let mask = ImageMask {
             image: image_mask_key,
             rect: (75, 75).by(100, 100),
             repeat: false,
         };
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -80,17 +80,17 @@ fn render_blob(
 
             match descriptor.format {
                 api::ImageFormat::BGRA8 => {
                     texels.push(color.b * checker + tc);
                     texels.push(color.g * checker + tc);
                     texels.push(color.r * checker + tc);
                     texels.push(color.a * checker + tc);
                 }
-                api::ImageFormat::A8 => {
+                api::ImageFormat::R8 => {
                     texels.push(color.a * checker + tc);
                 }
                 _ => {
                     return Err(api::BlobImageError::Other(
                         format!("Usupported image format {:?}", descriptor.format),
                     ));
                 }
             }
@@ -260,25 +260,27 @@ impl Example for App {
         );
 
         let info = api::LayoutPrimitiveInfo::new((30, 30).by(500, 500));
         builder.push_image(
             &info,
             api::LayoutSize::new(500.0, 500.0),
             api::LayoutSize::new(0.0, 0.0),
             api::ImageRendering::Auto,
+            api::AlphaType::PremultipliedAlpha,
             blob_img1,
         );
 
         let info = api::LayoutPrimitiveInfo::new((600, 600).by(200, 200));
         builder.push_image(
             &info,
             api::LayoutSize::new(200.0, 200.0),
             api::LayoutSize::new(0.0, 0.0),
             api::ImageRendering::Auto,
+            api::AlphaType::PremultipliedAlpha,
             blob_img2,
         );
 
         builder.pop_stacking_context();
     }
 }
 
 fn main() {
--- a/gfx/webrender/examples/common/boilerplate.rs
+++ b/gfx/webrender/examples/common/boilerplate.rs
@@ -59,16 +59,19 @@ impl HandyDandyRectBuilder for (i32, i32
             LayoutSize::new(w as f32, h as f32),
         )
     }
 }
 
 pub trait Example {
     const TITLE: &'static str = "WebRender Sample App";
     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,
         framebuffer_size: DeviceUintSize,
         pipeline_id: PipelineId,
         document_id: DocumentId,
@@ -98,16 +101,17 @@ pub fn main_wrapper<E: Example>(
         Some(PathBuf::from(&args[1]))
     } else {
         None
     };
 
     let window = glutin::WindowBuilder::new()
         .with_title(E::TITLE)
         .with_multitouch()
+        .with_dimensions(E::WIDTH, E::HEIGHT)
         .with_gl(glutin::GlRequest::GlThenGles {
             opengl_version: (3, 2),
             opengles_version: (3, 0),
         })
         .build()
         .unwrap();
 
     unsafe {
@@ -222,16 +226,23 @@ pub fn main_wrapper<E: Example>(
                     _,
                     Some(glutin::VirtualKeyCode::B),
                 ) => {
                     renderer.toggle_debug_flags(webrender::DebugFlags::ALPHA_PRIM_DBG);
                 }
                 glutin::Event::KeyboardInput(
                     glutin::ElementState::Pressed,
                     _,
+                    Some(glutin::VirtualKeyCode::S),
+                ) => {
+                    renderer.toggle_debug_flags(webrender::DebugFlags::COMPACT_PROFILER);
+                }
+                glutin::Event::KeyboardInput(
+                    glutin::ElementState::Pressed,
+                    _,
                     Some(glutin::VirtualKeyCode::Q),
                 ) => {
                     renderer.toggle_debug_flags(webrender::DebugFlags::GPU_TIME_QUERIES
                         | webrender::DebugFlags::GPU_SAMPLE_QUERIES);
                 }
                 glutin::Event::KeyboardInput(
                     glutin::ElementState::Pressed,
                     _,
--- a/gfx/webrender/examples/frame_output.rs
+++ b/gfx/webrender/examples/frame_output.rs
@@ -159,16 +159,17 @@ impl Example for App {
             Vec::new(),
         );
 
         builder.push_image(
             &info,
             info.rect.size,
             LayoutSize::zero(),
             ImageRendering::Auto,
+            AlphaType::PremultipliedAlpha,
             self.external_image_key.unwrap()
         );
 
         builder.pop_stacking_context();
     }
 
     fn get_image_handlers(
         &mut self,
--- a/gfx/webrender/examples/image_resize.rs
+++ b/gfx/webrender/examples/image_resize.rs
@@ -54,28 +54,30 @@ impl Example for App {
             LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size),
             bounds,
         );
         builder.push_image(
             &info,
             image_size,
             LayoutSize::zero(),
             ImageRendering::Auto,
+            AlphaType::PremultipliedAlpha,
             self.image_key,
         );
 
         let info = LayoutPrimitiveInfo::with_clip_rect(
             LayoutRect::new(LayoutPoint::new(250.0, 100.0), image_size),
             bounds,
         );
         builder.push_image(
             &info,
             image_size,
             LayoutSize::zero(),
             ImageRendering::Pixelated,
+            AlphaType::PremultipliedAlpha,
             self.image_key,
         );
 
         builder.pop_stacking_context();
     }
 
     fn on_event(&mut self, event: glutin::Event, api: &RenderApi, document_id: DocumentId) -> bool {
         match event {
--- a/gfx/webrender/examples/texture_cache_stress.rs
+++ b/gfx/webrender/examples/texture_cache_stress.rs
@@ -142,46 +142,49 @@ impl Example for App {
                 bounds,
             );
 
             builder.push_image(
                 &info,
                 image_size,
                 LayoutSize::zero(),
                 ImageRendering::Auto,
+                AlphaType::PremultipliedAlpha,
                 *key,
             );
         }
 
         if let Some(image_key) = self.image_key {
             let image_size = LayoutSize::new(100.0, 100.0);
             let info = LayoutPrimitiveInfo::with_clip_rect(
                 LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size),
                 bounds,
             );
             builder.push_image(
                 &info,
                 image_size,
                 LayoutSize::zero(),
                 ImageRendering::Auto,
+                AlphaType::PremultipliedAlpha,
                 image_key,
             );
         }
 
         let swap_key = self.swap_keys[self.swap_index];
         let image_size = LayoutSize::new(64.0, 64.0);
         let info = LayoutPrimitiveInfo::with_clip_rect(
             LayoutRect::new(LayoutPoint::new(100.0, 400.0), image_size),
             bounds,
         );
         builder.push_image(
             &info,
             image_size,
             LayoutSize::zero(),
             ImageRendering::Auto,
+            AlphaType::PremultipliedAlpha,
             swap_key,
         );
         self.swap_index = 1 - self.swap_index;
 
         builder.pop_stacking_context();
     }
 
     fn on_event(
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -187,35 +187,35 @@ impl Example for App {
         );
 
         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(
             yuv_chanel1,
-            ImageDescriptor::new(100, 100, ImageFormat::A8, true),
+            ImageDescriptor::new(100, 100, ImageFormat::R8, true),
             ImageData::new(vec![127; 100 * 100]),
             None,
         );
         resources.add_image(
             yuv_chanel2,
             ImageDescriptor::new(100, 100, ImageFormat::RG8, true),
             ImageData::new(vec![0; 100 * 100 * 2]),
             None,
         );
         resources.add_image(
             yuv_chanel2_1,
-            ImageDescriptor::new(100, 100, ImageFormat::A8, true),
+            ImageDescriptor::new(100, 100, ImageFormat::R8, true),
             ImageData::new(vec![127; 100 * 100]),
             None,
         );
         resources.add_image(
             yuv_chanel3,
-            ImageDescriptor::new(100, 100, ImageFormat::A8, true),
+            ImageDescriptor::new(100, 100, ImageFormat::R8, true),
             ImageData::new(vec![127; 100 * 100]),
             None,
         );
 
         let info = LayoutPrimitiveInfo::with_clip_rect(
             LayoutRect::new(LayoutPoint::new(100.0, 0.0), LayoutSize::new(100.0, 100.0)),
             bounds,
         );
--- a/gfx/webrender/res/brush.glsl
+++ b/gfx/webrender/res/brush.glsl
@@ -18,29 +18,31 @@ void brush_vs(
 struct BrushInstance {
     int picture_address;
     int prim_address;
     int clip_chain_rect_index;
     int scroll_node_id;
     int clip_address;
     int z;
     int segment_index;
+    int edge_mask;
     ivec2 user_data;
 };
 
 BrushInstance load_brush() {
     BrushInstance bi;
 
     bi.picture_address = aData0.x;
     bi.prim_address = aData0.y;
     bi.clip_chain_rect_index = aData0.z / 65536;
     bi.scroll_node_id = aData0.z % 65536;
     bi.clip_address = aData0.w;
     bi.z = aData1.x;
-    bi.segment_index = aData1.y;
+    bi.segment_index = aData1.y & 0xffff;
+    bi.edge_mask = aData1.y >> 16;
     bi.user_data = aData1.zw;
 
     return bi;
 }
 
 struct BrushPrimitive {
     RectWithSize local_rect;
     RectWithSize local_clip_rect;
@@ -120,17 +122,17 @@ void main(void) {
             //           the alpha pass, even on non-transformed
             //           items. For now, just ensure it has no
             //           effect. We can tidy this up as we move
             //           more items to be brush shaders.
 #ifdef WR_FEATURE_ALPHA_PASS
             vLocalBounds = vec4(vec2(-1000000.0), vec2(1000000.0));
 #endif
         } else {
-            bvec4 edge_mask = notEqual(int(segment_data[1].x) & ivec4(1, 2, 4, 8), ivec4(0));
+            bvec4 edge_mask = notEqual(brush.edge_mask & ivec4(1, 2, 4, 8), ivec4(0));
             vi = write_transform_vertex(
                 local_segment_rect,
                 brush_prim.local_rect,
                 brush_prim.local_clip_rect,
                 mix(vec4(0.0), vec4(1.0), edge_mask),
                 float(brush.z),
                 scroll_node,
                 pic_task
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#define VECS_PER_SPECIFIC_BRUSH 0
+#define VECS_PER_SPECIFIC_BRUSH 1
 
 #include shared,prim_shared,brush
 
 #ifdef WR_FEATURE_ALPHA_PASS
 varying vec2 vLocalPos;
 #endif
 
 varying vec3 vUv;
@@ -20,46 +20,62 @@ flat varying vec4 vParams;
 flat varying vec4 vColor;
 #endif
 
 #define BRUSH_IMAGE_SIMPLE      0
 #define BRUSH_IMAGE_NINEPATCH   1
 #define BRUSH_IMAGE_MIRROR      2
 
 #ifdef WR_VERTEX_SHADER
+
+struct Picture {
+    vec4 color;
+};
+
+Picture fetch_picture(int address) {
+    vec4 data = fetch_from_resource_cache_1(address);
+    return Picture(data);
+}
+
 void brush_vs(
     int prim_address,
     vec2 local_pos,
     RectWithSize local_rect,
     ivec2 user_data,
     PictureTask pic_task
 ) {
-    // TODO(gw): For now, this brush_image shader is only
-    //           being used to draw items from the intermediate
-    //           surface cache (render tasks). In the future
-    //           we can expand this to support items from
-    //           the normal texture cache and unify this
-    //           with the normal image shader.
-    BlurTask blur_task = fetch_blur_task(user_data.x);
-    vUv.z = blur_task.common_data.texture_layer_index;
     vImageKind = user_data.y;
 
-#if defined WR_FEATURE_COLOR_TARGET
-    vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
-#elif defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
+    // TODO(gw): There's quite a bit of code duplication here,
+    //           depending on which variation of brush image
+    //           this is being used for. This is because only
+    //           box-shadow pictures are currently supported
+    //           as texture cacheable items. Once we port the
+    //           drop-shadows and text-shadows to be cacheable,
+    //           most of this code can be merged together.
+#if defined WR_FEATURE_COLOR_TARGET || defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
+    BlurTask blur_task = fetch_blur_task(user_data.x);
+    vUv.z = blur_task.common_data.texture_layer_index;
     vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
-    vColor = blur_task.color;
-#else
-    vec2 texture_size = vec2(textureSize(sColor1, 0).xy);
+#if defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
     vColor = blur_task.color;
 #endif
-
     vec2 uv0 = blur_task.common_data.task_rect.p0;
     vec2 src_size = blur_task.common_data.task_rect.size * blur_task.scale_factor;
     vec2 uv1 = uv0 + blur_task.common_data.task_rect.size;
+#else
+    Picture pic = fetch_picture(prim_address);
+    ImageResource uv_rect = fetch_image_resource(user_data.x);
+    vec2 texture_size = vec2(textureSize(sColor1, 0).xy);
+    vColor = pic.color;
+    vec2 uv0 = uv_rect.uv_rect.xy;
+    vec2 uv1 = uv_rect.uv_rect.zw;
+    vec2 src_size = (uv1 - uv0) * uv_rect.user_data.x;
+    vUv.z = uv_rect.layer;
+#endif
 
     // TODO(gw): In the future we'll probably draw these as segments
     //           with the brush shader. When that occurs, we can
     //           modify the UVs for each segment in the VS, and the
     //           FS can become a simple shader that doesn't need
     //           to adjust the UVs.
 
     switch (vImageKind) {
--- a/gfx/webrender/res/cs_blur.glsl
+++ b/gfx/webrender/res/cs_blur.glsl
@@ -15,17 +15,16 @@ flat varying int vBlurRadius;
 // by the dir field in the blur command.
 
 #define DIR_HORIZONTAL  0
 #define DIR_VERTICAL    1
 
 in int aBlurRenderTaskAddress;
 in int aBlurSourceTaskAddress;
 in int aBlurDirection;
-in vec4 aBlurRegion;
 
 void main(void) {
     BlurTask blur_task = fetch_blur_task(aBlurRenderTaskAddress);
     RenderTaskCommonData src_task = fetch_render_task_common_data(aBlurSourceTaskAddress);
 
     RectWithSize src_rect = src_task.task_rect;
     RectWithSize target_rect = blur_task.common_data.task_rect;
 
@@ -46,23 +45,16 @@ void main(void) {
             vOffsetScale = vec2(0.0, 1.0 / texture_size.y);
             break;
     }
 
     vUvRect = vec4(src_rect.p0 + vec2(0.5),
                    src_rect.p0 + src_rect.size - vec2(0.5));
     vUvRect /= texture_size.xyxy;
 
-    if (aBlurRegion.z > 0.0) {
-        vec4 blur_region = aBlurRegion * uDevicePixelRatio;
-        src_rect = RectWithSize(src_rect.p0 + blur_region.xy, blur_region.zw);
-        target_rect.p0 = target_rect.p0 + blur_region.xy;
-        target_rect.size = blur_region.zw;
-    }
-
     vec2 pos = target_rect.p0 + target_rect.size * aPosition.xy;
 
     vec2 uv0 = src_rect.p0 / texture_size;
     vec2 uv1 = (src_rect.p0 + src_rect.size) / texture_size;
     vUv.xy = mix(uv0, uv1, aPosition.xy);
 
     gl_Position = uTransform * vec4(pos, 0.0, 1.0);
 }
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -689,27 +689,28 @@ struct GlyphResource {
 GlyphResource fetch_glyph_resource(int address) {
     vec4 data[2] = fetch_from_resource_cache_2(address);
     return GlyphResource(data[0], data[1].x, data[1].yz, data[1].w);
 }
 
 struct ImageResource {
     vec4 uv_rect;
     float layer;
+    vec3 user_data;
 };
 
 ImageResource fetch_image_resource(int address) {
     //Note: number of blocks has to match `renderer::BLOCKS_PER_UV_RECT`
     vec4 data[2] = fetch_from_resource_cache_2(address);
-    return ImageResource(data[0], data[1].x);
+    return ImageResource(data[0], data[1].x, data[1].yzw);
 }
 
 ImageResource fetch_image_resource_direct(ivec2 address) {
     vec4 data[2] = fetch_from_resource_cache_2_direct(address);
-    return ImageResource(data[0], data[1].x);
+    return ImageResource(data[0], data[1].x, data[1].yzw);
 }
 
 struct TextRun {
     vec4 color;
     vec4 bg_color;
     vec2 offset;
 };
 
--- a/gfx/webrender/res/shared_border.glsl
+++ b/gfx/webrender/res/shared_border.glsl
@@ -42,17 +42,17 @@ vec4 get_effective_border_widths(Border 
 
             // The CSS spec doesn't define what width each of the segments
             // in a style: double border should be. It only says that the
             // sum of the segments should be equal to the total border
             // width. We pick to make the segments (almost) equal thirds
             // for now - we can adjust this if we find other browsers pick
             // different values in some cases.
             // SEE: https://drafts.csswg.org/css-backgrounds-3/#double
-            return floor(0.5 + border.widths / 3.0);
+            return max(floor(0.5 + border.widths / 3.0), 1.0);
         case BORDER_STYLE_GROOVE:
         case BORDER_STYLE_RIDGE:
             return floor(0.5 + border.widths * 0.5);
         default:
             return border.widths;
     }
 }
 
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -1,30 +1,30 @@
 /* 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::{DeviceIntRect, DeviceIntSize, ImageKey, LayerToWorldScale};
+use api::{AlphaType, DeviceIntRect, DeviceIntSize, ImageKey, LayerToWorldScale};
 use api::{ExternalImageType, FilterOp, ImageRendering, LayerRect};
 use api::{SubpixelDirection, TileOffset, YuvColorSpace, YuvFormat};
 use api::{LayerToWorldTransform, WorldPixel};
-use border::{BorderCornerInstance, BorderCornerSide};
+use border::{BorderCornerInstance, BorderCornerSide, BorderEdgeKind};
 use clip::{ClipSource, ClipStore};
 use clip_scroll_tree::{CoordinateSystemId};
 use euclid::{TypedTransform3D, vec3};
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{BrushImageKind, BrushInstance, ClipChainRectIndex};
 use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex, PictureType};
 use gpu_types::{CompositePrimitiveInstance, PrimitiveInstance, SimplePrimitiveInstance};
 use internal_types::{FastHashMap, SourceTexture};
-use picture::{PictureCompositeMode, PictureKind, PicturePrimitive};
+use picture::{PictureCompositeMode, PictureKind, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
-use prim_store::{BrushPrimitive, BrushKind, DeferredResolve, PrimitiveRun};
+use prim_store::{BrushPrimitive, BrushKind, DeferredResolve, EdgeAaSegmentMask, PrimitiveRun};
 use render_task::{ClipWorkItem};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind};
 use render_task::{RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{GlyphFetchResult, ResourceCache};
 use std::{usize, f32, i32};
 use tiling::{RenderTargetContext, RenderTargetKind};
@@ -104,17 +104,17 @@ impl BatchTextures {
                 SourceTexture::CacheA8,
                 SourceTexture::Invalid,
             ],
         }
     }
 
     pub fn color(texture: SourceTexture) -> Self {
         BatchTextures {
-            colors: [texture, SourceTexture::Invalid, SourceTexture::Invalid],
+            colors: [texture, texture, SourceTexture::Invalid],
         }
     }
 }
 
 #[derive(Debug)]
 pub struct AlphaPrimitiveBatch {
     pub key: BatchKey,
     pub instances: Vec<PrimitiveInstance>,
@@ -339,16 +339,17 @@ impl BatchList {
         key: BatchKey,
         item_bounding_rect: &DeviceIntRect,
     ) -> &mut Vec<PrimitiveInstance> {
         match key.blend_mode {
             BlendMode::None => {
                 self.opaque_batch_list
                     .get_suitable_batch(key, item_bounding_rect)
             }
+            BlendMode::Alpha |
             BlendMode::PremultipliedAlpha |
             BlendMode::PremultipliedDestOut |
             BlendMode::SubpixelConstantTextColor(..) |
             BlendMode::SubpixelVariableTextColor |
             BlendMode::SubpixelWithBgColor |
             BlendMode::SubpixelDualSource => {
                 self.alpha_batch_list
                     .get_suitable_batch(key, item_bounding_rect)
@@ -360,27 +361,25 @@ impl BatchList {
         self.opaque_batch_list.finalize()
     }
 }
 
 /// Encapsulates the logic of building batches for items that are blended.
 pub struct AlphaBatcher {
     pub batch_list: BatchList,
     pub text_run_cache_prims: FastHashMap<SourceTexture, Vec<PrimitiveInstance>>,
-    pub line_cache_prims: Vec<PrimitiveInstance>,
     glyph_fetch_buffer: Vec<GlyphFetchResult>,
 }
 
 impl AlphaBatcher {
     pub fn new(screen_size: DeviceIntSize) -> Self {
         AlphaBatcher {
             batch_list: BatchList::new(screen_size),
             glyph_fetch_buffer: Vec::new(),
             text_run_cache_prims: FastHashMap::default(),
-            line_cache_prims: Vec::new(),
         }
     }
 
     pub fn build(
         &mut self,
         tasks: &[RenderTaskId],
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
@@ -463,17 +462,22 @@ impl AlphaBatcher {
             let key = BatchKey::new(
                 BatchKind::SplitComposite,
                 BlendMode::PremultipliedAlpha,
                 BatchTextures::no_texture(),
             );
             let pic_metadata = &ctx.prim_store.cpu_metadata[prim_index.0];
             let pic = &ctx.prim_store.cpu_pictures[pic_metadata.cpu_prim_index.0];
             let batch = self.batch_list.get_suitable_batch(key, pic_metadata.screen_rect.as_ref().expect("bug"));
-            let source_task_address = render_tasks.get_task_address(pic.render_task_id.expect("bug"));
+
+            let render_task_id = match pic.surface {
+                Some(PictureSurface::RenderTask(render_task_id)) => render_task_id,
+                Some(PictureSurface::TextureCache(..)) | None => panic!("BUG: unexpected surface in splitting"),
+            };
+            let source_task_address = render_tasks.get_task_address(render_task_id);
             let gpu_address = gpu_handle.as_int(gpu_cache);
 
             let instance = CompositePrimitiveInstance::new(
                 task_address,
                 source_task_address,
                 RenderTaskAddress(0),
                 gpu_address,
                 0,
@@ -641,47 +645,23 @@ impl AlphaBatcher {
                                     0,
                                 ));
                             }
                         }
                     }
                 }
 
                 let batch = self.batch_list.get_suitable_batch(edge_key, item_bounding_rect);
-                for border_segment in 0 .. 4 {
-                    batch.push(base_instance.build(border_segment, 0, 0));
-                }
-            }
-            PrimitiveKind::Line => {
-                let base_instance = BrushInstance {
-                    picture_address: task_address,
-                    prim_address: prim_cache_address,
-                    clip_chain_rect_index,
-                    scroll_id,
-                    clip_task_address,
-                    z,
-                    segment_index: 0,
-                    user_data0: 0,
-                    user_data1: 0,
-                };
-
-                let instance = PrimitiveInstance::from(base_instance);
-
-                match pic_type {
-                    PictureType::TextShadow => {
-                        self.line_cache_prims.push(instance);
+                for (border_segment, instance_kind) in border_cpu.edges.iter().enumerate() {
+                    match *instance_kind {
+                        BorderEdgeKind::None => {},
+                        _ => {
+                          batch.push(base_instance.build(border_segment as i32, 0, 0));
+                        }
                     }
-                    PictureType::Image => {
-                        let kind =
-                            BatchKind::Brush(BrushBatchKind::Line);
-                        let key = BatchKey::new(kind, blend_mode, no_textures);
-                        let batch = self.batch_list.get_suitable_batch(key, item_bounding_rect);
-                        batch.push(instance);
-                    }
-                    PictureType::BoxShadow => unreachable!(),
                 }
             }
             PrimitiveKind::Image => {
                 let image_cpu = &ctx.prim_store.cpu_images[prim_metadata.cpu_prim_index.0];
 
                 let (color_texture_id, uv_address) = resolve_image(
                     image_cpu.image_key,
                     image_cpu.image_rendering,
@@ -801,17 +781,17 @@ impl AlphaBatcher {
                             let blend_mode = match glyph_format {
                                 GlyphFormat::Subpixel |
                                 GlyphFormat::TransformedSubpixel => {
                                     if text_cpu.font.bg_color.a != 0 {
                                         BlendMode::SubpixelWithBgColor
                                     } else if ctx.use_dual_source_blending {
                                         BlendMode::SubpixelDualSource
                                     } else {
-                                        BlendMode::SubpixelConstantTextColor(text_cpu.font.color.into())
+                                        BlendMode::SubpixelConstantTextColor(text_cpu.get_color())
                                     }
                                 }
                                 GlyphFormat::Alpha |
                                 GlyphFormat::TransformedAlpha |
                                 GlyphFormat::Bitmap |
                                 GlyphFormat::ColorBitmap => BlendMode::PremultipliedAlpha,
                             };
 
@@ -828,18 +808,49 @@ impl AlphaBatcher {
                         }
                     },
                 );
             }
             PrimitiveKind::Picture => {
                 let picture =
                     &ctx.prim_store.cpu_pictures[prim_metadata.cpu_prim_index.0];
 
-                match picture.render_task_id {
-                    Some(cache_task_id) => {
+                match picture.surface {
+                    Some(PictureSurface::TextureCache(ref cache_item)) => {
+                        match picture.kind {
+                            PictureKind::TextShadow { .. } |
+                            PictureKind::Image { .. } => {
+                                panic!("BUG: only supported as render tasks for now");
+                            }
+                            PictureKind::BoxShadow { image_kind, .. } => {
+                                let textures = BatchTextures::color(cache_item.texture_id);
+                                let kind = BatchKind::Brush(
+                                    BrushBatchKind::Image(
+                                        BrushImageSourceKind::from_render_target_kind(picture.target_kind())),
+                                );
+                                let key = BatchKey::new(kind, blend_mode, textures);
+                                let batch = self.batch_list.get_suitable_batch(key, item_bounding_rect);
+
+                                let instance = BrushInstance {
+                                    picture_address: task_address,
+                                    prim_address: prim_cache_address,
+                                    clip_chain_rect_index,
+                                    scroll_id,
+                                    clip_task_address,
+                                    z,
+                                    segment_index: 0,
+                                    edge_flags: EdgeAaSegmentMask::empty(),
+                                    user_data0: cache_item.uv_rect_handle.as_int(gpu_cache),
+                                    user_data1: image_kind as i32,
+                                };
+                                batch.push(PrimitiveInstance::from(instance));
+                            }
+                        }
+                    }
+                    Some(PictureSurface::RenderTask(cache_task_id)) => {
                         let cache_task_address = render_tasks.get_task_address(cache_task_id);
                         let textures = BatchTextures::render_target_cache();
 
                         match picture.kind {
                             PictureKind::TextShadow { .. } => {
                                 let kind = BatchKind::Brush(
                                     BrushBatchKind::Image(
                                         BrushImageSourceKind::from_render_target_kind(picture.target_kind())),
@@ -850,41 +861,24 @@ impl AlphaBatcher {
                                 let instance = BrushInstance {
                                     picture_address: task_address,
                                     prim_address: prim_cache_address,
                                     clip_chain_rect_index,
                                     scroll_id,
                                     clip_task_address,
                                     z,
                                     segment_index: 0,
+                                    edge_flags: EdgeAaSegmentMask::empty(),
                                     user_data0: cache_task_address.0 as i32,
                                     user_data1: BrushImageKind::Simple as i32,
                                 };
                                 batch.push(PrimitiveInstance::from(instance));
                             }
-                            PictureKind::BoxShadow { image_kind, .. } => {
-                                let kind = BatchKind::Brush(
-                                    BrushBatchKind::Image(
-                                        BrushImageSourceKind::from_render_target_kind(picture.target_kind())),
-                                );
-                                let key = BatchKey::new(kind, blend_mode, textures);
-                                let batch = self.batch_list.get_suitable_batch(key, item_bounding_rect);
-
-                                let instance = BrushInstance {
-                                    picture_address: task_address,
-                                    prim_address: prim_cache_address,
-                                    clip_chain_rect_index,
-                                    scroll_id,
-                                    clip_task_address,
-                                    z,
-                                    segment_index: 0,
-                                    user_data0: cache_task_address.0 as i32,
-                                    user_data1: image_kind as i32,
-                                };
-                                batch.push(PrimitiveInstance::from(instance));
+                            PictureKind::BoxShadow { .. } => {
+                                panic!("BUG: should be handled as a texture cache surface");
                             }
                             PictureKind::Image {
                                 composite_mode,
                                 secondary_render_task_id,
                                 is_in_3d_context,
                                 reference_frame_id,
                                 real_local_rect,
                                 ..
@@ -909,17 +903,17 @@ impl AlphaBatcher {
                                 }
 
                                 // Depending on the composite mode of the picture, we generate the
                                 // old style Composite primitive instances. In the future, we'll
                                 // remove these and pass them through the brush batching pipeline.
                                 // This will allow us to unify some of the shaders, apply clip masks
                                 // when compositing pictures, and also correctly apply pixel snapping
                                 // to picture compositing operations.
-                                let source_id = picture.render_task_id.expect("no source!?");
+                                let source_id = cache_task_id;
 
                                 match composite_mode.expect("bug: only composites here") {
                                     PictureCompositeMode::Filter(filter) => {
                                         match filter {
                                             FilterOp::Blur(..) => {
                                                 let src_task_address = render_tasks.get_task_address(source_id);
                                                 let key = BatchKey::new(
                                                     BatchKind::HardwareComposite,
@@ -949,16 +943,17 @@ impl AlphaBatcher {
                                                 let instance = BrushInstance {
                                                     picture_address: task_address,
                                                     prim_address: prim_cache_address,
                                                     clip_chain_rect_index,
                                                     scroll_id,
                                                     clip_task_address,
                                                     z,
                                                     segment_index: 0,
+                                                    edge_flags: EdgeAaSegmentMask::empty(),
                                                     user_data0: cache_task_address.0 as i32,
                                                     user_data1: BrushImageKind::Simple as i32,
                                                 };
 
                                                 {
                                                     let batch = self.batch_list.get_suitable_batch(key, item_bounding_rect);
                                                     batch.push(PrimitiveInstance::from(instance));
                                                 }
@@ -1239,16 +1234,17 @@ impl AlphaBatcher {
         let base_instance = BrushInstance {
             picture_address: task_address,
             prim_address: prim_cache_address,
             clip_chain_rect_index,
             scroll_id,
             clip_task_address,
             z,
             segment_index: 0,
+            edge_flags: EdgeAaSegmentMask::empty(),
             user_data0: 0,
             user_data1: 0,
         };
 
         match brush.segment_desc {
             Some(ref segment_desc) => {
                 let opaque_batch = self.batch_list.opaque_batch_list.get_suitable_batch(
                     brush.get_batch_key(
@@ -1270,16 +1266,17 @@ impl AlphaBatcher {
                                          (!is_inner && transform_kind == TransformedRectKind::Complex);
 
                     let clip_task_address = segment
                         .clip_task_id
                         .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
 
                     let instance = PrimitiveInstance::from(BrushInstance {
                         segment_index: i as i32,
+                        edge_flags: segment.edge_flags,
                         clip_task_address,
                         ..base_instance
                     });
 
                     if needs_blending {
                         alpha_batch.push(instance);
                     } else {
                         opaque_batch.push(instance);
@@ -1292,16 +1289,23 @@ impl AlphaBatcher {
             }
         }
     }
 }
 
 impl BrushPrimitive {
     fn get_batch_key(&self, blend_mode: BlendMode) -> BatchKey {
         match self.kind {
+            BrushKind::Line { .. } => {
+                BatchKey::new(
+                    BatchKind::Brush(BrushBatchKind::Line),
+                    blend_mode,
+                    BatchTextures::no_texture(),
+                )
+            }
             BrushKind::Solid { .. } => {
                 BatchKey::new(
                     BatchKind::Brush(BrushBatchKind::Solid),
                     blend_mode,
                     BatchTextures::no_texture(),
                 )
             }
             BrushKind::Clear => {
@@ -1334,28 +1338,35 @@ impl AlphaBatchHelpers for PrimitiveStor
     ) -> BlendMode {
         let needs_blending = !metadata.opacity.is_opaque || metadata.clip_task_id.is_some() ||
             transform_kind == TransformedRectKind::Complex;
 
         match metadata.prim_kind {
             // Can only resolve the TextRun's blend mode once glyphs are fetched.
             PrimitiveKind::TextRun => BlendMode::PremultipliedAlpha,
             PrimitiveKind::Border |
-            PrimitiveKind::Image |
             PrimitiveKind::YuvImage |
             PrimitiveKind::AlignedGradient |
             PrimitiveKind::AngleGradient |
             PrimitiveKind::RadialGradient |
-            PrimitiveKind::Line |
             PrimitiveKind::Brush |
             PrimitiveKind::Picture => if needs_blending {
                 BlendMode::PremultipliedAlpha
             } else {
                 BlendMode::None
             },
+            PrimitiveKind::Image => if needs_blending {
+                let image_cpu = &self.cpu_images[metadata.cpu_prim_index.0];
+                match image_cpu.alpha_type {
+                    AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
+                    AlphaType::Alpha => BlendMode::Alpha,
+                }
+            } else {
+                BlendMode::None
+            },
         }
     }
 }
 
 fn resolve_image(
     image_key: ImageKey,
     image_rendering: ImageRendering,
     tile_offset: Option<TileOffset>,
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -144,16 +144,24 @@ impl NormalBorderHelpers for NormalBorde
             // If both edges are none or hidden, no corner is needed.
             (BorderStyle::None, BorderStyle::None) |
             (BorderStyle::None, BorderStyle::Hidden) |
             (BorderStyle::Hidden, BorderStyle::None) |
             (BorderStyle::Hidden, BorderStyle::Hidden) => {
                 BorderCornerKind::None
             }
 
+            // If one of the edges is none or hidden, we just draw one style.
+            (BorderStyle::None, _) |
+            (_, BorderStyle::None) |
+            (BorderStyle::Hidden, _) |
+            (_, BorderStyle::Hidden) => {
+                BorderCornerKind::Clip(BorderCornerInstance::Single)
+            }
+
             // If both borders are solid, we can draw them with a simple rectangle if
             // both the colors match and there is no radius.
             (BorderStyle::Solid, BorderStyle::Solid) => {
                 if edge0.color == edge1.color && radius.width == 0.0 && radius.height == 0.0 {
                     BorderCornerKind::Solid
                 } else {
                     BorderCornerKind::Clip(BorderCornerInstance::Single)
                 }
@@ -232,22 +240,22 @@ pub fn ensure_no_corner_overlap(
     rect: &LayerRect,
 ) {
     let mut ratio = 1.0;
     let top_left_radius = &mut radius.top_left;
     let top_right_radius = &mut radius.top_right;
     let bottom_right_radius = &mut radius.bottom_right;
     let bottom_left_radius = &mut radius.bottom_left;
 
-    let sum = top_left_radius.width + bottom_left_radius.width;
+    let sum = top_left_radius.width + top_right_radius.width;
     if rect.size.width < sum {
         ratio = f32::min(ratio, rect.size.width / sum);
     }
 
-    let sum = top_right_radius.width + bottom_right_radius.width;
+    let sum = bottom_left_radius.width + bottom_right_radius.width;
     if rect.size.width < sum {
         ratio = f32::min(ratio, rect.size.width / sum);
     }
 
     let sum = top_left_radius.height + bottom_left_radius.height;
     if rect.size.height < sum {
         ratio = f32::min(ratio, rect.size.height / sum);
     }
@@ -275,32 +283,34 @@ pub fn ensure_no_corner_overlap(
 impl FrameBuilder {
     fn add_normal_border_primitive(
         &mut self,
         info: &LayerPrimitiveInfo,
         border: &NormalBorder,
         widths: &BorderWidths,
         clip_and_scroll: ClipAndScrollInfo,
         corner_instances: [BorderCornerInstance; 4],
+        edges: [BorderEdgeKind; 4],
         clip_sources: Vec<ClipSource>,
     ) {
         let radius = &border.radius;
         let left = &border.left;
         let right = &border.right;
         let top = &border.top;
         let bottom = &border.bottom;
 
         // These colors are used during inset/outset scaling.
         let left_color = left.border_color(1.0, 2.0 / 3.0, 0.3, 0.7).premultiplied();
         let top_color = top.border_color(1.0, 2.0 / 3.0, 0.3, 0.7).premultiplied();
         let right_color = right.border_color(2.0 / 3.0, 1.0, 0.7, 0.3).premultiplied();
         let bottom_color = bottom.border_color(2.0 / 3.0, 1.0, 0.7, 0.3).premultiplied();
 
         let prim_cpu = BorderPrimitiveCpu {
             corner_instances,
+            edges,
 
             // TODO(gw): In the future, we will build these on demand
             //           from the deserialized display list, rather
             //           than creating it immediately.
             gpu_blocks: [
                 [
                     pack_as_float(left.style as u32),
                     pack_as_float(top.style as u32),
@@ -531,16 +541,17 @@ impl FrameBuilder {
             }
 
             self.add_normal_border_primitive(
                 info,
                 &border,
                 widths,
                 clip_and_scroll,
                 corner_instances,
+                edges,
                 extra_clips,
             );
         }
     }
 }
 
 pub trait BorderSideHelpers {
     fn border_color(
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.rs
@@ -78,16 +78,19 @@ impl FrameBuilder {
             border_radius,
             spread_amount,
         );
         let shadow_rect = prim_info.rect
             .translate(box_offset)
             .inflate(spread_amount, spread_amount);
 
         if blur_radius == 0.0 {
+            if box_offset.x == 0.0 && box_offset.y == 0.0 && spread_amount == 0.0 {
+                return;
+            }
             let mut clips = Vec::new();
 
             let fast_info = match clip_mode {
                 BoxShadowClipMode::Outset => {
                     // TODO(gw): Add a fast path for ClipOut + zero border radius!
                     clips.push(ClipSource::new_rounded_rect(
                         prim_info.rect,
                         border_radius,
@@ -237,17 +240,16 @@ impl FrameBuilder {
                         PrimitiveContainer::Brush(brush_prim),
                     );
 
                     // Create a box shadow picture and add the mask primitive to it.
                     let pic_rect = shadow_rect.inflate(blur_offset, blur_offset);
                     let mut pic_prim = PicturePrimitive::new_box_shadow(
                         blur_radius,
                         *color,
-                        Vec::new(),
                         clip_mode,
                         image_kind,
                         cache_key,
                         pipeline_id,
                     );
                     pic_prim.add_primitive(
                         brush_prim_index,
                         clip_and_scroll
@@ -296,17 +298,17 @@ impl FrameBuilder {
                     // edge to prevent edge get blurred after downscled.
                     let mut adjusted_blur_std_deviation = blur_radius * 0.5;
                     let mut inflate_size = 1.0;
                     while adjusted_blur_std_deviation > MAX_BLUR_STD_DEVIATION {
                         adjusted_blur_std_deviation *= 0.5;
                         inflate_size *= 2.0;
                     }
 
-                    let brush_rect = brush_rect.inflate(inflate_size, inflate_size);
+                    let brush_rect = brush_rect.inflate(inflate_size + box_offset.x.abs(), inflate_size + box_offset.y.abs());
                     let brush_prim = BrushPrimitive::new(
                         BrushKind::Mask {
                             clip_mode: brush_clip_mode,
                             kind: BrushMaskKind::RoundedRect(clip_rect, shadow_radius),
                         },
                         None,
                     );
                     let brush_info = LayerPrimitiveInfo::new(brush_rect);
@@ -316,33 +318,32 @@ impl FrameBuilder {
                         PrimitiveContainer::Brush(brush_prim),
                     );
 
                     // Create a box shadow picture primitive and add
                     // the brush primitive to it.
                     let mut pic_prim = PicturePrimitive::new_box_shadow(
                         blur_radius,
                         *color,
-                        Vec::new(),
                         BoxShadowClipMode::Inset,
                         // TODO(gw): Make use of optimization for inset.
                         BrushImageKind::NinePatch,
                         cache_key,
                         pipeline_id,
                     );
                     pic_prim.add_primitive(
                         brush_prim_index,
                         clip_and_scroll
                     );
 
                     // Draw the picture one pixel outside the original
                     // rect to account for the inflate above. This
                     // extra edge will be clipped by the local clip
                     // rect set below.
-                    let pic_rect = prim_info.rect.inflate(inflate_size, inflate_size);
+                    let pic_rect = prim_info.rect.inflate(inflate_size + box_offset.x.abs(), inflate_size + box_offset.y.abs());
                     let pic_info = LayerPrimitiveInfo::with_clip_rect(
                         pic_rect,
                         prim_info.rect
                     );
 
                     // Add a normal clip to ensure nothing gets drawn
                     // outside the primitive rect.
                     if !border_radius.is_zero() {
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,27 +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::{ClipId, DeviceIntRect, DevicePixelScale, LayerPixel, LayerPoint, LayerRect, LayerSize};
+use api::{ClipId, DevicePixelScale, LayerPixel, LayerPoint, LayerRect, LayerSize};
 use api::{LayerToWorldTransform, LayerTransform, LayerVector2D, LayoutTransform, LayoutVector2D};
 use api::{PipelineId, PropertyBinding, ScrollClamping, ScrollEventPhase, ScrollLocation};
 use api::{ScrollSensitivity, StickyOffsetBounds, WorldPoint};
 use clip::{ClipSourcesHandle, ClipStore};
 use clip_scroll_tree::{CoordinateSystemId, TransformUpdateState};
 use euclid::SideOffsets2D;
 use geometry::ray_intersects_rect;
 use gpu_cache::GpuCache;
 use gpu_types::{ClipScrollNodeIndex, ClipScrollNodeData};
-use render_task::{ClipChain, ClipChainNode, ClipWorkItem};
+use render_task::{ClipChain, ClipWorkItem};
 use resource_cache::ResourceCache;
 use scene::SceneProperties;
 use spring::{DAMPING, STIFFNESS, Spring};
-use std::rc::Rc;
 use util::{MatrixHelpers, TransformOrOffset, TransformedRectKind};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
 
 #[cfg(not(target_os = "macos"))]
 const CAN_OVERSCROLL: bool = false;
 
@@ -102,20 +101,18 @@ pub struct ClipScrollNode {
     pub parent: Option<ClipId>,
 
     /// Child layers
     pub children: Vec<ClipId>,
 
     /// The type of this node and any data associated with that node type.
     pub node_type: NodeType,
 
-    /// The node in the chain of clips that are necessary to clip display items
-    /// that have this ClipScrollNode as their clip parent. This will be used to
-    /// generate clip tasks.
-    pub clip_chain_node: ClipChain,
+    /// The ClipChain that will be used if this node is used as the 'clipping node.'
+    pub clip_chain: Option<ClipChain>,
 
     /// True if this node is transformed by an invertible transform.  If not, display items
     /// transformed by this node will not be displayed and display items not transformed by this
     /// node will not be clipped by clips that are transformed by this node.
     pub invertible: bool,
 
     /// The axis-aligned coordinate system id of this node.
     pub coordinate_system_id: CoordinateSystemId,
@@ -140,17 +137,17 @@ impl ClipScrollNode {
         ClipScrollNode {
             local_viewport_rect: *rect,
             world_viewport_transform: LayerToWorldTransform::identity(),
             world_content_transform: LayerToWorldTransform::identity(),
             parent: parent_id,
             children: Vec::new(),
             pipeline_id,
             node_type: node_type,
-            clip_chain_node: None,
+            clip_chain: None,
             invertible: true,
             coordinate_system_id: CoordinateSystemId(0),
             coordinate_system_relative_transform: TransformOrOffset::zero(),
             node_data_index: ClipScrollNodeIndex(0),
         }
     }
 
     pub fn new_scroll_frame(
@@ -267,17 +264,17 @@ impl ClipScrollNode {
         scrolling.started_bouncing_back = false;
         true
     }
 
     pub fn mark_uninvertible(&mut self) {
         self.invertible = false;
         self.world_content_transform = LayerToWorldTransform::identity();
         self.world_viewport_transform = LayerToWorldTransform::identity();
-        self.clip_chain_node = None;
+        self.clip_chain = None;
     }
 
     pub fn push_gpu_node_data(&mut self, node_data: &mut Vec<ClipScrollNodeData>) {
         if !self.invertible {
             node_data.push(ClipScrollNodeData::invalid());
             return;
         }
 
@@ -295,17 +292,16 @@ impl ClipScrollNode {
         // Write the data that will be made available to the GPU for this node.
         node_data.push(data);
     }
 
     pub fn update(
         &mut self,
         state: &mut TransformUpdateState,
         next_coordinate_system_id: &mut CoordinateSystemId,
-        screen_rect: &DeviceIntRect,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         scene_properties: &SceneProperties,
     ) {
         // If any of our parents was not rendered, we are not rendered either and can just
         // quit here.
@@ -324,42 +320,35 @@ impl ClipScrollNode {
                 self.mark_uninvertible();
                 return;
             }
             _ => self.invertible = true,
         }
 
         self.update_clip_work_item(
             state,
-            screen_rect,
             device_pixel_scale,
             clip_store,
             resource_cache,
             gpu_cache,
         );
     }
 
     pub fn update_clip_work_item(
         &mut self,
         state: &mut TransformUpdateState,
-        screen_rect: &DeviceIntRect,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
     ) {
-        let current_clip_chain = state.parent_clip_chain.clone();
-        let combined_outer_screen_rect = current_clip_chain.as_ref().map_or(
-            *screen_rect, |clip| clip.combined_outer_screen_rect,
-        );
-
         let clip_sources_handle = match self.node_type {
             NodeType::Clip(ref handle) => handle,
             _ => {
-                self.clip_chain_node = current_clip_chain.clone();
+                self.clip_chain = Some(state.parent_clip_chain.clone());
                 self.invertible = true;
                 return;
             }
         };
 
         let clip_sources = clip_store.get_mut(clip_sources_handle);
         clip_sources.update(gpu_cache, resource_cache);
         let (screen_inner_rect, screen_outer_rect) =
@@ -371,37 +360,36 @@ impl ClipScrollNode {
         let screen_outer_rect = screen_outer_rect.expect("Clipping node didn't have outer rect.");
         let local_outer_rect = clip_sources.local_outer_rect.expect(
             "Clipping node didn't have outer rect."
         );
 
         // If this clip's inner rectangle completely surrounds the existing clip
         // chain's outer rectangle, we can discard this clip entirely since it isn't
         // going to affect anything.
-        if screen_inner_rect.contains_rect(&combined_outer_screen_rect) {
-            self.clip_chain_node = current_clip_chain;
+        if screen_inner_rect.contains_rect(&state.parent_clip_chain.combined_outer_screen_rect) {
+            self.clip_chain = Some(state.parent_clip_chain.clone());
             return;
         }
 
         let work_item = ClipWorkItem {
             scroll_node_data_index: self.node_data_index,
             clip_sources: clip_sources_handle.weak(),
             coordinate_system_id: state.current_coordinate_system_id,
         };
 
-        let clip_chain_node = ClipChainNode::new(
+        let clip_chain = state.parent_clip_chain.new_with_added_node(
             work_item,
             self.coordinate_system_relative_transform.apply(&local_outer_rect),
             screen_outer_rect,
             screen_inner_rect,
-            current_clip_chain
         );
 
-        self.clip_chain_node = Some(Rc::new(clip_chain_node));
-        state.parent_clip_chain = self.clip_chain_node.clone();
+        self.clip_chain = Some(clip_chain.clone());
+        state.parent_clip_chain = clip_chain;
     }
 
     pub fn update_transform(
         &mut self,
         state: &mut TransformUpdateState,
         next_coordinate_system_id: &mut CoordinateSystemId,
         scene_properties: &SceneProperties,
     ) {
@@ -608,17 +596,16 @@ impl ClipScrollNode {
                                          &info.horizontal_offset_bounds);
 
         sticky_offset
     }
 
     pub fn prepare_state_for_children(&self, state: &mut TransformUpdateState) {
         if !self.invertible {
             state.invertible = false;
-            state.parent_clip_chain = None;
             return;
         }
 
         // The transformation we are passing is the transformation of the parent
         // reference frame and the offset is the accumulated offset of all the nodes
         // between us and the parent reference frame. If we are a reference frame,
         // we need to reset both these values.
         match self.node_type {
@@ -773,26 +760,16 @@ impl ClipScrollNode {
     }
 
     pub fn is_overscrolling(&self) -> bool {
         match self.node_type {
             NodeType::ScrollFrame(ref state) => state.overscroll_amount() != LayerVector2D::zero(),
             _ => false,
         }
     }
-
-    pub fn is_visible(&self) -> bool {
-        if !self.invertible {
-            return false;
-        }
-        match self.clip_chain_node {
-            Some(ref node) if node.combined_outer_screen_rect.is_empty() => false,
-            _ => true,
-        }
-    }
 }
 
 #[derive(Copy, Clone, Debug)]
 pub struct ScrollingState {
     pub offset: LayerVector2D,
     pub spring: Spring,
     pub started_bouncing_back: bool,
     pub bouncing_back: bool,
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.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::{ClipId, DeviceIntRect, DevicePixelScale, LayerPoint, LayerRect};
-use api::{LayerToWorldTransform, LayerVector2D, PipelineId, ScrollClamping, ScrollEventPhase};
-use api::{PropertyBinding, LayoutTransform, ScrollLayerState, ScrollLocation, WorldPoint};
+use api::{ClipId, ClipChainId, DeviceIntRect, DevicePixelScale, LayerPoint, LayerRect};
+use api::{LayerToWorldTransform, LayerVector2D, LayoutTransform, PipelineId, PropertyBinding};
+use api::{ScrollClamping, ScrollEventPhase, ScrollLayerState, ScrollLocation, WorldPoint};
 use clip::ClipStore;
 use clip_scroll_node::{ClipScrollNode, NodeType, ScrollingState, StickyFrameInfo};
 use gpu_cache::GpuCache;
 use gpu_types::{ClipScrollNodeIndex, ClipScrollNodeData};
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
 use render_task::ClipChain;
 use resource_cache::ResourceCache;
@@ -35,18 +35,33 @@ impl CoordinateSystemId {
         CoordinateSystemId(id + 1)
     }
 
     pub fn advance(&mut self) {
         self.0 += 1;
     }
 }
 
+struct ClipChainDescriptor {
+    id: ClipChainId,
+    parent: Option<ClipChainId>,
+    clips: Vec<ClipId>,
+}
+
 pub struct ClipScrollTree {
     pub nodes: FastHashMap<ClipId, ClipScrollNode>,
+
+    /// A Vec of all descriptors that describe ClipChains in the order in which they are
+    /// encountered during display list flattening. ClipChains are expected to never be
+    /// the children of ClipChains later in the list.
+    clip_chains_descriptors: Vec<ClipChainDescriptor>,
+
+    /// A HashMap of built ClipChains that are described by `clip_chains_descriptors`.
+    pub clip_chains: FastHashMap<ClipChainId, ClipChain>,
+
     pub pending_scroll_offsets: FastHashMap<ClipId, (LayerPoint, ScrollClamping)>,
 
     /// The ClipId of the currently scrolling node. Used to allow the same
     /// node to scroll even if a touch operation leaves the boundaries of that node.
     pub currently_scrolling_node_id: Option<ClipId>,
 
     /// The current frame id, used for giving a unique id to all new dynamically
     /// added frames and clips. The ClipScrollTree increments this by one every
@@ -89,16 +104,18 @@ pub struct TransformUpdateState {
     pub invertible: bool,
 }
 
 impl ClipScrollTree {
     pub fn new() -> Self {
         let dummy_pipeline = PipelineId::dummy();
         ClipScrollTree {
             nodes: FastHashMap::default(),
+            clip_chains_descriptors: Vec::new(),
+            clip_chains: FastHashMap::default(),
             pending_scroll_offsets: FastHashMap::default(),
             currently_scrolling_node_id: None,
             root_reference_frame_id: ClipId::root_reference_frame(dummy_pipeline),
             topmost_scrolling_node_id: ClipId::root_scroll_node(dummy_pipeline),
             current_new_node_item: 1,
             pipelines_to_discard: FastHashSet::default(),
         }
     }
@@ -238,16 +255,18 @@ impl ClipScrollTree {
             }
 
             if let NodeType::ScrollFrame(scrolling) = old_node.node_type {
                 scroll_states.insert(layer_id, scrolling);
             }
         }
 
         self.pipelines_to_discard.clear();
+        self.clip_chains.clear();
+        self.clip_chains_descriptors.clear();
         scroll_states
     }
 
     pub fn scroll_node(&mut self, origin: LayerPoint, id: ClipId, clamp: ScrollClamping) -> bool {
         if self.nodes.is_empty() {
             self.pending_scroll_offsets.insert(id, (origin, clamp));
             return false;
         }
@@ -354,42 +373,42 @@ impl ClipScrollTree {
             parent_reference_frame_transform: LayerToWorldTransform::create_translation(
                 pan.x,
                 pan.y,
                 0.0,
             ),
             parent_accumulated_scroll_offset: LayerVector2D::zero(),
             nearest_scrolling_ancestor_offset: LayerVector2D::zero(),
             nearest_scrolling_ancestor_viewport: LayerRect::zero(),
-            parent_clip_chain: None,
+            parent_clip_chain: ClipChain::empty(screen_rect),
             current_coordinate_system_id: CoordinateSystemId::root(),
             coordinate_system_relative_transform: TransformOrOffset::zero(),
             invertible: true,
         };
         let mut next_coordinate_system_id = state.current_coordinate_system_id.next();
         self.update_node(
             root_reference_frame_id,
             &mut state,
             &mut next_coordinate_system_id,
-            screen_rect,
             device_pixel_scale,
             clip_store,
             resource_cache,
             gpu_cache,
             node_data,
             scene_properties,
         );
+
+        self.build_clip_chains(screen_rect);
     }
 
     fn update_node(
         &mut self,
         layer_id: ClipId,
         state: &mut TransformUpdateState,
         next_coordinate_system_id: &mut CoordinateSystemId,
-        screen_rect: &DeviceIntRect,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         gpu_node_data: &mut Vec<ClipScrollNodeData>,
         scene_properties: &SceneProperties,
     ) {
         // TODO(gw): This is an ugly borrow check workaround to clone these.
@@ -402,51 +421,67 @@ impl ClipScrollTree {
             };
 
             // We set this early so that we can use it to populate the ClipChain.
             node.node_data_index = ClipScrollNodeIndex(gpu_node_data.len() as u32);
 
             node.update(
                 &mut state,
                 next_coordinate_system_id,
-                screen_rect,
                 device_pixel_scale,
                 clip_store,
                 resource_cache,
                 gpu_cache,
                 scene_properties,
             );
 
             node.push_gpu_node_data(gpu_node_data);
 
             if node.children.is_empty() {
                 return;
             }
 
-
             node.prepare_state_for_children(&mut state);
             node.children.clone()
         };
 
         for child_node_id in node_children {
             self.update_node(
                 child_node_id,
                 &mut state,
                 next_coordinate_system_id,
-                screen_rect,
                 device_pixel_scale,
                 clip_store,
                 resource_cache,
                 gpu_cache,
                 gpu_node_data,
                 scene_properties,
             );
         }
     }
 
+    pub fn build_clip_chains(&mut self, screen_rect: &DeviceIntRect) {
+        for descriptor in &self.clip_chains_descriptors {
+            let mut chain = match descriptor.parent {
+                Some(id) => self.clip_chains[&id].clone(),
+                None => ClipChain::empty(screen_rect),
+            };
+
+            for clip_id in &descriptor.clips {
+                if let Some(ref node_chain) = self.nodes[&clip_id].clip_chain {
+                    if let Some(ref nodes) = node_chain.nodes {
+                        chain.add_node((**nodes).clone());
+                    }
+                }
+            }
+
+            self.clip_chains.insert(descriptor.id, chain);
+        }
+    }
+
     pub fn tick_scrolling_bounce_animations(&mut self) {
         for (_, node) in &mut self.nodes {
             node.tick_scrolling_bounce_animation()
         }
     }
 
     pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
         // TODO(gw): These are all independent - can be run through thread pool if it shows up
@@ -507,16 +542,25 @@ impl ClipScrollTree {
             parent_id,
             frame_rect,
             sticky_frame_info,
             id.pipeline_id(),
         );
         self.add_node(node, id);
     }
 
+    pub fn add_clip_chain_descriptor(
+        &mut self,
+        id: ClipChainId,
+        parent: Option<ClipChainId>,
+        clips: Vec<ClipId>
+    ) {
+        self.clip_chains_descriptors.push(ClipChainDescriptor { id, parent, clips });
+    }
+
     pub fn add_node(&mut self, node: ClipScrollNode, id: ClipId) {
         // When the parent node is None this means we are adding the root.
         match node.parent {
             Some(parent_id) => self.nodes.get_mut(&parent_id).unwrap().add_child(id),
             None => self.root_reference_frame_id = id,
         }
 
         debug_assert!(!self.nodes.contains_key(&id));
@@ -607,9 +651,17 @@ impl ClipScrollTree {
         pipeline_id: Option<PipelineId>,
         point: &LayerPoint
     ) -> WorldPoint {
         pipeline_id.and_then(|id| self.nodes.get(&ClipId::root_reference_frame(id)))
                    .map(|node| node.world_viewport_transform.transform_point2d(point))
                    .unwrap_or_else(|| WorldPoint::new(point.x, point.y))
 
     }
+
+    pub fn get_clip_chain(&self, id: &ClipId) -> Option<&ClipChain> {
+        match id {
+            &ClipId::ClipChain(clip_chain_id) => Some(&self.clip_chains[&clip_chain_id]),
+            _ => self.nodes[id].clip_chain.as_ref(),
+        }
+    }
+
 }
--- a/gfx/webrender/src/debug_render.rs
+++ b/gfx/webrender/src/debug_render.rs
@@ -116,17 +116,17 @@ impl DebugRenderer {
         let line_vao = device.create_vao(&DESC_COLOR);
         let tri_vao = device.create_vao(&DESC_COLOR);
 
         let mut font_texture = device.create_texture(TextureTarget::Array);
         device.init_texture(
             &mut font_texture,
             debug_font_data::BMP_WIDTH,
             debug_font_data::BMP_HEIGHT,
-            ImageFormat::A8,
+            ImageFormat::R8,
             TextureFilter::Linear,
             None,
             1,
             Some(&debug_font_data::FONT_BITMAP),
         );
 
         DebugRenderer {
             font_vertices: Vec::new(),
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -7,25 +7,29 @@ use api::{ColorF, ImageDescriptor, Image
 use api::{DeviceIntPoint, DeviceIntRect, DeviceUintRect, DeviceUintSize};
 use euclid::Transform3D;
 use gleam::gl;
 use internal_types::{FastHashMap, RenderTargetInfo};
 use smallvec::SmallVec;
 use std::cell::RefCell;
 use std::fs::File;
 use std::io::Read;
-use std::iter::repeat;
 use std::marker::PhantomData;
 use std::mem;
 use std::ops::Add;
 use std::path::PathBuf;
 use std::ptr;
 use std::rc::Rc;
 use std::thread;
 
+// Apparently, in some cases calling `glTexImage3D` with
+// similar parameters that the texture already has confuses
+// Angle when running with optimizations.
+const WORK_AROUND_TEX_IMAGE: bool = cfg!(windows);
+
 #[derive(Debug, Copy, Clone, PartialEq, Ord, Eq, PartialOrd)]
 pub struct FrameId(usize);
 
 impl FrameId {
     pub fn new(value: usize) -> FrameId {
         FrameId(value)
     }
 }
@@ -33,22 +37,16 @@ impl FrameId {
 impl Add<usize> for FrameId {
     type Output = FrameId;
 
     fn add(self, other: usize) -> FrameId {
         FrameId(self.0 + other)
     }
 }
 
-#[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
-const GL_FORMAT_A: gl::GLuint = gl::RED;
-
-#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
-const GL_FORMAT_A: gl::GLuint = gl::ALPHA;
-
 const GL_FORMAT_BGRA_GL: gl::GLuint = gl::BGRA;
 
 const GL_FORMAT_BGRA_GLES: gl::GLuint = gl::BGRA_EXT;
 
 const SHADER_VERSION_GL: &str = "#version 150\n";
 const SHADER_VERSION_GLES: &str = "#version 300 es\n";
 
 const SHADER_KIND_VERTEX: &str = "#define WR_VERTEX_SHADER\n";
@@ -452,27 +450,31 @@ impl Texture {
     }
 
     pub fn get_format(&self) -> ImageFormat {
         self.format
     }
 
     pub fn get_bpp(&self) -> u32 {
         match self.format {
-            ImageFormat::A8 => 1,
+            ImageFormat::R8 => 1,
             ImageFormat::BGRA8 => 4,
             ImageFormat::RG8 => 2,
             ImageFormat::RGBAF32 => 16,
             ImageFormat::Invalid => unreachable!(),
         }
     }
 
     pub fn has_depth(&self) -> bool {
         self.depth_rb.is_some()
     }
+
+    pub fn get_rt_info(&self) -> Option<&RenderTargetInfo> {
+        self.render_target.as_ref()
+    }
 }
 
 impl Drop for Texture {
     fn drop(&mut self) {
         debug_assert!(thread::panicking() || self.id == 0);
     }
 }
 
@@ -957,17 +959,17 @@ impl Device {
         texture.width = new_size.width;
         texture.height = new_size.height;
         let rt_info = texture.render_target
             .clone()
             .expect("Only renderable textures are expected for resize here");
 
         self.bind_texture(DEFAULT_TEXTURE, texture);
         self.set_texture_parameters(texture.target, texture.filter);
-        self.update_texture_storage(texture, &rt_info, true);
+        self.update_texture_storage(texture, &rt_info, true, false);
 
         let rect = DeviceIntRect::new(DeviceIntPoint::zero(), old_size.to_i32());
         for (read_fbo, &draw_fbo) in old_fbos.into_iter().zip(&texture.fbo_ids) {
             self.bind_read_target_impl(read_fbo);
             self.bind_draw_target_impl(draw_fbo);
             self.blit_render_target(rect, rect);
             self.delete_fbo(read_fbo);
         }
@@ -979,110 +981,110 @@ impl Device {
         &mut self,
         texture: &mut Texture,
         width: u32,
         height: u32,
         format: ImageFormat,
         filter: TextureFilter,
         render_target: Option<RenderTargetInfo>,
         layer_count: i32,
+
         pixels: Option<&[u8]>,
     ) {
         debug_assert!(self.inside_frame);
 
-        let resized = texture.width != width ||
-            texture.height != height ||
-            texture.format != format;
+        let is_resized = texture.width != width || texture.height != height;
+        let is_format_changed = texture.format != format;
 
         texture.format = format;
         texture.width = width;
         texture.height = height;
         texture.filter = filter;
         texture.layer_count = layer_count;
         texture.render_target = render_target;
 
         self.bind_texture(DEFAULT_TEXTURE, texture);
         self.set_texture_parameters(texture.target, filter);
 
         match render_target {
             Some(info) => {
                 assert!(pixels.is_none());
-                self.update_texture_storage(texture, &info, resized);
+                self.update_texture_storage(texture, &info, is_resized, is_format_changed);
             }
             None => {
                 let (internal_format, gl_format) = gl_texture_formats_for_image_format(self.gl(), format);
                 let type_ = gl_type_for_texture_format(format);
 
-                let expanded_data: Vec<u8>;
-                let actual_pixels = if pixels.is_some() && format == ImageFormat::A8 &&
-                    cfg!(any(target_arch = "arm", target_arch = "aarch64"))
-                {
-                    expanded_data = pixels
-                        .unwrap()
-                        .iter()
-                        .flat_map(|&byte| repeat(byte).take(4))
-                        .collect();
-                    Some(expanded_data.as_slice())
-                } else {
-                    pixels
-                };
-
                 match texture.target {
                     gl::TEXTURE_2D_ARRAY => {
                         self.gl.tex_image_3d(
                             gl::TEXTURE_2D_ARRAY,
                             0,
                             internal_format as gl::GLint,
                             width as gl::GLint,
                             height as gl::GLint,
                             layer_count,
                             0,
                             gl_format,
                             type_,
-                            actual_pixels,
+                            pixels,
                         );
                     }
                     gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => {
                         self.gl.tex_image_2d(
                             texture.target,
                             0,
                             internal_format as gl::GLint,
                             width as gl::GLint,
                             height as gl::GLint,
                             0,
                             gl_format,
                             type_,
-                            actual_pixels,
+                            pixels,
                         );
                     }
                     _ => panic!("BUG: Unexpected texture target!"),
                 }
             }
         }
     }
 
     /// Updates the texture storage for the texture, creating FBOs as required.
     fn update_texture_storage(
         &mut self,
         texture: &mut Texture,
         rt_info: &RenderTargetInfo,
         is_resized: bool,
+        is_format_changed: bool,
     ) {
         assert!(texture.layer_count > 0);
 
         let needed_layer_count = texture.layer_count - texture.fbo_ids.len() as i32;
-        let allocate_color = needed_layer_count != 0 || is_resized;
+        let allocate_color = needed_layer_count != 0 || is_resized || is_format_changed;
 
         if allocate_color {
             let (internal_format, gl_format) =
                 gl_texture_formats_for_image_format(&*self.gl, texture.format);
             let type_ = gl_type_for_texture_format(texture.format);
 
             match texture.target {
                 gl::TEXTURE_2D_ARRAY => {
+                    if WORK_AROUND_TEX_IMAGE {
+                        // reset the contents before resizing
+                        self.gl.tex_image_3d(
+                            texture.target,
+                            0,
+                            gl::RGBA32F as _,
+                            2, 2, 1,
+                            0,
+                            gl::RGBA,
+                            gl::FLOAT,
+                            None,
+                        )
+                    }
                     self.gl.tex_image_3d(
                         texture.target,
                         0,
                         internal_format as _,
                         texture.width as _,
                         texture.height as _,
                         texture.layer_count,
                         0,
@@ -1133,37 +1135,38 @@ impl Device {
         };
 
         if allocate_depth {
             if rt_info.has_depth {
                 self.gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
                 self.gl.renderbuffer_storage(
                     gl::RENDERBUFFER,
                     gl::DEPTH_COMPONENT24,
-                    texture.width as gl::GLsizei,
-                    texture.height as gl::GLsizei,
+                    texture.width as _,
+                    texture.height as _,
                 );
             } else {
                 self.gl.delete_renderbuffers(&[depth_rb]);
                 depth_rb = 0;
                 texture.depth_rb = None;
             }
         }
 
         if allocate_color || allocate_depth {
+            let original_bound_fbo = self.bound_draw_fbo;
             for (fbo_index, &fbo_id) in texture.fbo_ids.iter().enumerate() {
                 self.bind_external_draw_target(fbo_id);
                 match texture.target {
                     gl::TEXTURE_2D_ARRAY => {
                         self.gl.framebuffer_texture_layer(
                             gl::DRAW_FRAMEBUFFER,
                             gl::COLOR_ATTACHMENT0,
                             texture.id,
                             0,
-                            fbo_index as gl::GLint,
+                            fbo_index as _,
                         )
                     }
                     _ => {
                         assert_eq!(fbo_index, 0);
                         self.gl.framebuffer_texture_2d(
                             gl::DRAW_FRAMEBUFFER,
                             gl::COLOR_ATTACHMENT0,
                             texture.target,
@@ -1175,19 +1178,17 @@ impl Device {
 
                 self.gl.framebuffer_renderbuffer(
                     gl::DRAW_FRAMEBUFFER,
                     gl::DEPTH_ATTACHMENT,
                     gl::RENDERBUFFER,
                     depth_rb,
                 );
             }
-            // restore the previous FBO
-            let bound_fbo = self.bound_draw_fbo;
-            self.bind_external_draw_target(bound_fbo);
+            self.bind_external_draw_target(original_bound_fbo);
         }
     }
 
     pub fn blit_render_target(&mut self, src_rect: DeviceIntRect, dest_rect: DeviceIntRect) {
         debug_assert!(self.inside_frame);
 
         self.gl.blit_framebuffer(
             src_rect.origin.x,
@@ -1854,16 +1855,22 @@ impl Device {
     pub fn set_blend(&self, enable: bool) {
         if enable {
             self.gl.enable(gl::BLEND);
         } else {
             self.gl.disable(gl::BLEND);
         }
     }
 
+    pub fn set_blend_mode_alpha(&self) {
+        self.gl.blend_func_separate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA,
+                                    gl::ONE, gl::ONE);
+        self.gl.blend_equation(gl::FUNC_ADD);
+    }
+
     pub fn set_blend_mode_premultiplied_alpha(&self) {
         self.gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
         self.gl.blend_equation(gl::FUNC_ADD);
     }
 
     pub fn set_blend_mode_premultiplied_dest_out(&self) {
         self.gl.blend_func(gl::ZERO, gl::ONE_MINUS_SRC_ALPHA);
         self.gl.blend_equation(gl::FUNC_ADD);
@@ -1919,21 +1926,17 @@ impl Device {
 }
 
 /// return (gl_internal_format, gl_format)
 fn gl_texture_formats_for_image_format(
     gl: &gl::Gl,
     format: ImageFormat,
 ) -> (gl::GLint, gl::GLuint) {
     match format {
-        ImageFormat::A8 => if cfg!(any(target_arch = "arm", target_arch = "aarch64")) {
-            (get_gl_format_bgra(gl) as gl::GLint, get_gl_format_bgra(gl))
-        } else {
-            (GL_FORMAT_A as gl::GLint, GL_FORMAT_A)
-        },
+        ImageFormat::R8 => (gl::RED as gl::GLint, gl::RED),
         ImageFormat::BGRA8 => match gl.get_type() {
             gl::GlType::Gl => (gl::RGBA as gl::GLint, get_gl_format_bgra(gl)),
             gl::GlType::Gles => (get_gl_format_bgra(gl) as gl::GLint, get_gl_format_bgra(gl)),
         },
         ImageFormat::RGBAF32 => (gl::RGBA32F as gl::GLint, gl::RGBA),
         ImageFormat::RG8 => (gl::RG8 as gl::GLint, gl::RG),
         ImageFormat::Invalid => unreachable!(),
     }
@@ -2048,17 +2051,17 @@ impl<'a, T> TextureUploader<'a, T> {
             }
         }
     }
 }
 
 impl<'a> UploadTarget<'a> {
     fn update_impl(&mut self, chunk: UploadChunk) {
         let (gl_format, bpp, data_type) = match self.texture.format {
-            ImageFormat::A8 => (GL_FORMAT_A, 1, gl::UNSIGNED_BYTE),
+            ImageFormat::R8 => (gl::RED, 1, gl::UNSIGNED_BYTE),
             ImageFormat::BGRA8 => (get_gl_format_bgra(self.gl), 4, gl::UNSIGNED_BYTE),
             ImageFormat::RG8 => (gl::RG, 2, gl::UNSIGNED_BYTE),
             ImageFormat::RGBAF32 => (gl::RGBA, 16, gl::FLOAT),
             ImageFormat::Invalid => unreachable!(),
         };
 
         let row_length = match chunk.stride {
             Some(value) => value / bpp,
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -69,16 +69,34 @@ impl<'a> FlattenContext<'a> {
             .pipelines
             .get(&pipeline_id)
             .expect("No display list?")
             .display_list
             .get(complex_clips)
             .collect()
     }
 
+    fn get_clip_chain_items(
+        &self,
+        pipeline_id: PipelineId,
+        items: ItemRange<ClipId>,
+    ) -> Vec<ClipId> {
+        if items.is_empty() {
+            return vec![];
+        }
+
+        self.scene
+            .pipelines
+            .get(&pipeline_id)
+            .expect("No display list?")
+            .display_list
+            .get(items)
+            .collect()
+    }
+
     fn flatten_root(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
         frame_size: &LayoutSize,
         root_reference_frame_id: ClipId,
         root_scroll_frame_id: ClipId,
     ) {
@@ -400,16 +418,17 @@ impl<'a> FlattenContext<'a> {
                         self.builder.add_image(
                             clip_and_scroll,
                             &prim_info,
                             &info.stretch_size,
                             &info.tile_spacing,
                             None,
                             info.image_key,
                             info.image_rendering,
+                            info.alpha_type,
                             None,
                         );
                     }
                 }
             }
             SpecificDisplayItem::YuvImage(ref info) => {
                 self.builder.add_yuv_image(
                     clip_and_scroll,
@@ -553,16 +572,20 @@ impl<'a> FlattenContext<'a> {
                 );
                 self.flatten_clip(
                     pipeline_id,
                     &clip_and_scroll.scroll_node_id,
                     &info.id,
                     clip_region,
                 );
             }
+            SpecificDisplayItem::ClipChain(ref info) => {
+                let items = self.get_clip_chain_items(pipeline_id, item.clip_chain_items());
+                self.clip_scroll_tree.add_clip_chain_descriptor(info.id, info.parent, items);
+            },
             SpecificDisplayItem::ScrollFrame(ref info) => {
                 let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
                 let clip_region = ClipRegion::create_for_clip_node(
                     *item.local_clip().clip_rect(),
                     complex_clips,
                     info.image_mask,
                     &reference_frame_relative_offset,
                 );
@@ -910,16 +933,17 @@ impl<'a> FlattenContext<'a> {
             self.builder.add_image(
                 clip_and_scroll,
                 &prim_info,
                 &stretched_size,
                 &info.tile_spacing,
                 None,
                 info.image_key,
                 info.image_rendering,
+                info.alpha_type,
                 Some(tile_offset),
             );
         }
     }
 }
 
 /// Frame context contains the information required to update
 /// (e.g. scroll) a renderer frame builder (`FrameBuilder`).
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{BorderDetails, BorderDisplayItem, BuiltDisplayList, ClipAndScrollInfo, ClipId, ColorF};
-use api::{ColorU, DeviceIntPoint, DevicePixelScale, DeviceUintPoint, DeviceUintRect};
+use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayList, ClipAndScrollInfo, ClipId};
+use api::{ColorF, ColorU, DeviceIntPoint, DevicePixelScale, DeviceUintPoint, DeviceUintRect};
 use api::{DeviceUintSize, DocumentLayer, ExtendMode, FontRenderMode, GlyphInstance, GlyphOptions};
 use api::{GradientStop, HitTestFlags, HitTestItem, HitTestResult, ImageKey, ImageRendering};
 use api::{ItemRange, ItemTag, LayerPoint, LayerPrimitiveInfo, LayerRect, LayerSize};
 use api::{LayerTransform, LayerVector2D, LayoutTransform, LayoutVector2D, LineOrientation};
 use api::{LineStyle, LocalClip, PipelineId, PremultipliedColorF, PropertyBinding, RepeatMode};
 use api::{ScrollSensitivity, Shadow, TileOffset, TransformStyle, WorldPoint, YuvColorSpace};
 use api::YuvData;
 use app_units::Au;
@@ -17,29 +17,28 @@ use clip::{ClipRegion, ClipSource, ClipS
 use clip_scroll_node::{ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use euclid::{SideOffsets2D, vec2};
 use frame::FrameId;
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCache;
 use gpu_types::{ClipScrollNodeData, PictureType};
 use internal_types::{FastHashMap, FastHashSet, RenderPassIndex};
-use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive};
+use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive, PictureSurface};
 use prim_store::{BrushKind, BrushPrimitive, TexelRect, YuvImagePrimitiveCpu};
-use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, LinePrimitive, PrimitiveKind};
+use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, PrimitiveKind};
 use prim_store::{PrimitiveContainer, PrimitiveIndex, SpecificPrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu};
 use prim_store::{BrushSegmentDescriptor, TextRunPrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
-use render_task::{ClearMode, RenderTask, RenderTaskId, RenderTaskTree};
+use render_task::{ClearMode, ClipChain, RenderTask, RenderTaskId, RenderTaskTree};
 use resource_cache::ResourceCache;
 use scene::{ScenePipeline, SceneProperties};
 use std::{mem, usize, f32};
-use tiling::{CompositeOps, Frame};
-use tiling::{RenderPass, RenderTargetKind};
+use tiling::{CompositeOps, Frame, RenderPass, RenderTargetKind};
 use tiling::{RenderTargetContext, ScrollbarPrimitive};
 use util::{self, MaxRect, pack_as_float, RectHelpers, recycle_vec};
 
 #[derive(Debug)]
 pub struct ScrollbarInfo(pub ClipId, pub LayerRect);
 
 /// Properties of a stacking context that are maintained
 /// during creation of the scene. These structures are
@@ -124,31 +123,31 @@ pub struct FrameBuilder {
     /// A temporary stack of stacking context properties, used only
     /// during scene building.
     sc_stack: Vec<StackingContext>,
 }
 
 pub struct PrimitiveContext<'a> {
     pub device_pixel_scale: DevicePixelScale,
     pub display_list: &'a BuiltDisplayList,
-    pub clip_node: &'a ClipScrollNode,
+    pub clip_chain: Option<&'a ClipChain>,
     pub scroll_node: &'a ClipScrollNode,
 }
 
 impl<'a> PrimitiveContext<'a> {
     pub fn new(
         device_pixel_scale: DevicePixelScale,
         display_list: &'a BuiltDisplayList,
-        clip_node: &'a ClipScrollNode,
+        clip_chain: Option<&'a ClipChain>,
         scroll_node: &'a ClipScrollNode,
     ) -> Self {
         PrimitiveContext {
             device_pixel_scale,
             display_list,
-            clip_node,
+            clip_chain,
             scroll_node,
         }
     }
 }
 
 impl FrameBuilder {
     pub fn empty() -> Self {
         FrameBuilder {
@@ -814,52 +813,62 @@ impl FrameBuilder {
         &mut self,
         clip_and_scroll: ClipAndScrollInfo,
         info: &LayerPrimitiveInfo,
         wavy_line_thickness: f32,
         orientation: LineOrientation,
         line_color: &ColorF,
         style: LineStyle,
     ) {
-        let line = LinePrimitive {
-            wavy_line_thickness,
-            color: line_color.premultiplied(),
-            style,
-            orientation,
-        };
+        let line = BrushPrimitive::new(
+            BrushKind::Line {
+                wavy_line_thickness,
+                color: line_color.premultiplied(),
+                style,
+                orientation,
+            },
+            None,
+        );
 
         let mut fast_shadow_prims = Vec::new();
         for (idx, &(shadow_prim_index, _)) in self.shadow_prim_stack.iter().enumerate() {
             let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
             let picture = &self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
             match picture.kind {
                 PictureKind::TextShadow { offset, color, blur_radius, .. } if blur_radius == 0.0 => {
                     fast_shadow_prims.push((idx, offset, color));
                 }
                 _ => {}
             }
         }
 
         for (idx, shadow_offset, shadow_color) in fast_shadow_prims {
-            let mut line = line.clone();
-            line.color = shadow_color.premultiplied();
+            let line = BrushPrimitive::new(
+                BrushKind::Line {
+                    wavy_line_thickness,
+                    color: shadow_color.premultiplied(),
+                    style,
+                    orientation,
+                },
+                None,
+            );
             let mut info = info.clone();
             info.rect = info.rect.translate(&shadow_offset);
             let prim_index = self.create_primitive(
                 &info,
                 Vec::new(),
-                PrimitiveContainer::Line(line),
+                PrimitiveContainer::Brush(line),
             );
             self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
         }
 
         let prim_index = self.create_primitive(
             &info,
             Vec::new(),
-            PrimitiveContainer::Line(line),
+            PrimitiveContainer::Brush(line),
         );
 
         if line_color.a > 0.0 {
             if self.shadow_prim_stack.is_empty() {
                 self.add_primitive_to_hit_testing_list(&info, clip_and_scroll);
                 self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
             } else {
                 self.pending_shadow_contents.push((prim_index, clip_and_scroll, *info));
@@ -1083,16 +1092,17 @@ impl FrameBuilder {
                     self.add_image(
                         clip_and_scroll,
                         &info,
                         &segment.stretch_size,
                         &segment.tile_spacing,
                         Some(segment.sub_rect),
                         border.image_key,
                         ImageRendering::Auto,
+                        AlphaType::PremultipliedAlpha,
                         None,
                     );
                 }
             }
             BorderDetails::Normal(ref border) => {
                 self.add_normal_border(info, border, &border_item.widths, clip_and_scroll);
             }
             BorderDetails::Gradient(ref border) => for segment in create_segments(border.outset) {
@@ -1278,18 +1288,20 @@ impl FrameBuilder {
         }
 
         // TODO(gw): Use a proper algorithm to select
         // whether this item should be rendered with
         // subpixel AA!
         let mut render_mode = self.config
             .default_font_render_mode
             .limit_by(font.render_mode);
+        let mut flags = font.flags;
         if let Some(options) = glyph_options {
             render_mode = render_mode.limit_by(options.render_mode);
+            flags |= options.flags;
         }
 
         // There are some conditions under which we can't use
         // subpixel text rendering, even if enabled.
         if render_mode == FontRenderMode::Subpixel {
             // text on a picture that has filters
             // (e.g. opacity) can't use sub-pixel.
             // TODO(gw): It's possible we can relax this in
@@ -1304,17 +1316,17 @@ impl FrameBuilder {
 
         let prim_font = FontInstance::new(
             font.font_key,
             font.size,
             *text_color,
             font.bg_color,
             render_mode,
             font.subpx_dir,
-            font.flags,
+            flags,
             font.platform_options,
             font.variations.clone(),
         );
         let prim = TextRunPrimitiveCpu {
             font: prim_font,
             glyph_range,
             glyph_count,
             glyph_gpu_blocks: Vec::new(),
@@ -1406,16 +1418,17 @@ impl FrameBuilder {
         &mut self,
         clip_and_scroll: ClipAndScrollInfo,
         info: &LayerPrimitiveInfo,
         stretch_size: &LayerSize,
         tile_spacing: &LayerSize,
         sub_rect: Option<TexelRect>,
         image_key: ImageKey,
         image_rendering: ImageRendering,
+        alpha_type: AlphaType,
         tile: Option<TileOffset>,
     ) {
         let sub_rect_block = sub_rect.unwrap_or(TexelRect::invalid()).into();
 
         // If the tile spacing is the same as the rect size,
         // then it is effectively zero. We use this later on
         // in prim_store to detect if an image can be considered
         // opaque.
@@ -1425,16 +1438,17 @@ impl FrameBuilder {
             *tile_spacing
         };
 
         let prim_cpu = ImagePrimitiveCpu {
             image_key,
             image_rendering,
             tile_offset: tile,
             tile_spacing,
+            alpha_type,
             gpu_blocks: [
                 [
                     stretch_size.width,
                     stretch_size.height,
                     tile_spacing.width,
                     tile_spacing.height,
                 ].into(),
                 sub_rect_block,
@@ -1577,17 +1591,17 @@ impl FrameBuilder {
         let display_list = &pipelines
             .get(&root_clip_scroll_node.pipeline_id)
             .expect("No display list?")
             .display_list;
 
         let root_prim_context = PrimitiveContext::new(
             device_pixel_scale,
             display_list,
-            root_clip_scroll_node,
+            root_clip_scroll_node.clip_chain.as_ref(),
             root_clip_scroll_node,
         );
 
         let mut child_tasks = Vec::new();
         self.prim_store.reset_prim_visibility();
         self.prim_store.prepare_prim_runs(
             &prim_run_cmds,
             root_clip_scroll_node.pipeline_id,
@@ -1615,22 +1629,22 @@ impl FrameBuilder {
         let root_render_task = RenderTask::new_picture(
             None,
             PrimitiveIndex(0),
             RenderTargetKind::Color,
             ContentOrigin::Screen(DeviceIntPoint::zero()),
             PremultipliedColorF::TRANSPARENT,
             ClearMode::Transparent,
             child_tasks,
-            None,
             PictureType::Image,
         );
 
-        pic.render_task_id = Some(render_tasks.add(root_render_task));
-        pic.render_task_id
+        let render_task_id = render_tasks.add(root_render_task);
+        pic.surface = Some(PictureSurface::RenderTask(render_task_id));
+        Some(render_task_id)
     }
 
     fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache) {
         static SCROLLBAR_PADDING: f32 = 8.0;
 
         for scrollbar_prim in &self.scrollbar_prims {
             let metadata = &mut self.prim_store.cpu_metadata[scrollbar_prim.prim_index.0];
             let scroll_frame = &clip_scroll_tree.nodes[&scrollbar_prim.clip_id];
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -109,16 +109,28 @@ impl FontTransform {
     pub fn synthesize_italics(&self, skew_factor: f32) -> Self {
         FontTransform::new(
             self.scale_x,
             self.skew_x - self.scale_x * skew_factor,
             self.skew_y,
             self.scale_y - self.skew_y * skew_factor,
         )
     }
+
+    pub fn swap_xy(&self) -> Self {
+        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)
+    }
 }
 
 impl<'a> From<&'a LayerToWorldTransform> for FontTransform {
     fn from(xform: &'a LayerToWorldTransform) -> Self {
         FontTransform::new(xform.m11, xform.m21, xform.m12, xform.m22)
     }
 }
 
@@ -215,18 +227,16 @@ pub enum GlyphFormat {
     TransformedSubpixel,
     Bitmap,
     ColorBitmap,
 }
 
 impl GlyphFormat {
     pub fn ignore_color(self) -> Self {
         match self {
-            GlyphFormat::Subpixel => GlyphFormat::Alpha,
-            GlyphFormat::TransformedSubpixel => GlyphFormat::TransformedAlpha,
             GlyphFormat::ColorBitmap => GlyphFormat::Bitmap,
             _ => self,
         }
     }
 }
 
 pub struct RasterizedGlyph {
     pub top: f32,
@@ -395,17 +405,17 @@ impl GlyphRasterizer {
                                     width: glyph_info.size.width,
                                     height: glyph_info.size.height,
                                     stride: None,
                                     format: ImageFormat::BGRA8,
                                     is_opaque: false,
                                     offset: 0,
                                 },
                                 TextureFilter::Linear,
-                                ImageData::Raw(glyph_info.glyph_bytes.clone()),
+                                Some(ImageData::Raw(glyph_info.glyph_bytes.clone())),
                                 [glyph_info.offset.x, glyph_info.offset.y, glyph_info.scale],
                                 None,
                                 gpu_cache,
                             );
                         }
                     }
                 }
                 Entry::Vacant(..) => {
@@ -518,17 +528,17 @@ impl GlyphRasterizer {
                             width: glyph.width,
                             height: glyph.height,
                             stride: None,
                             format: ImageFormat::BGRA8,
                             is_opaque: false,
                             offset: 0,
                         },
                         TextureFilter::Linear,
-                        ImageData::Raw(glyph_bytes.clone()),
+                        Some(ImageData::Raw(glyph_bytes.clone())),
                         [glyph.left, -glyph.top, glyph.scale],
                         None,
                         gpu_cache,
                     );
                     Some(CachedGlyphInfo {
                         texture_cache_handle,
                         glyph_bytes,
                         size: DeviceUintSize::new(glyph.width, glyph.height),
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -1,14 +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::{LayerRect, LayerToWorldTransform};
+use api::{LayerToWorldTransform};
 use gpu_cache::GpuCacheAddress;
+use prim_store::EdgeAaSegmentMask;
 use render_task::RenderTaskAddress;
 
 // Contains type that must exactly match the same structures declared in GLSL.
 
 #[repr(i32)]
 #[derive(Debug, Copy, Clone)]
 pub enum BlurDirection {
     Horizontal = 0,
@@ -16,17 +17,16 @@ pub enum BlurDirection {
 }
 
 #[derive(Debug)]
 #[repr(C)]
 pub struct BlurInstance {
     pub task_address: RenderTaskAddress,
     pub src_task_address: RenderTaskAddress,
     pub blur_direction: BlurDirection,
-    pub region: LayerRect,
 }
 
 /// 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)]
 #[repr(C)]
 pub struct ClipMaskInstance {
@@ -150,30 +150,31 @@ impl From<CompositePrimitiveInstance> fo
 pub struct BrushInstance {
     pub picture_address: RenderTaskAddress,
     pub prim_address: GpuCacheAddress,
     pub clip_chain_rect_index: ClipChainRectIndex,
     pub scroll_id: ClipScrollNodeIndex,
     pub clip_task_address: RenderTaskAddress,
     pub z: i32,
     pub segment_index: i32,
+    pub edge_flags: EdgeAaSegmentMask,
     pub user_data0: i32,
     pub user_data1: i32,
 }
 
 impl From<BrushInstance> for PrimitiveInstance {
     fn from(instance: BrushInstance) -> PrimitiveInstance {
         PrimitiveInstance {
             data: [
                 instance.picture_address.0 as i32,
                 instance.prim_address.as_int(),
                 ((instance.clip_chain_rect_index.0 as i32) << 16) | instance.scroll_id.0 as i32,
                 instance.clip_task_address.0 as i32,
                 instance.z,
-                instance.segment_index,
+                instance.segment_index | ((instance.edge_flags.bits() as i32) << 16),
                 instance.user_data0,
                 instance.user_data1,
             ]
         }
     }
 }
 
 // Defines how a brush image is stretched onto the primitive.
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -3,20 +3,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ColorF, ClipAndScrollInfo, FilterOp, MixBlendMode};
 use api::{DeviceIntPoint, DeviceIntRect, LayerToWorldScale, PipelineId};
 use api::{BoxShadowClipMode, LayerPoint, LayerRect, LayerVector2D, Shadow};
 use api::{ClipId, PremultipliedColorF};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowCacheKey};
 use frame_builder::PrimitiveContext;
-use gpu_cache::GpuDataRequest;
+use gpu_cache::{GpuCache, GpuDataRequest};
 use gpu_types::{BrushImageKind, PictureType};
 use prim_store::{PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
-use render_task::{ClearMode, RenderTask, RenderTaskId, RenderTaskTree};
+use render_task::{ClearMode, RenderTask, RenderTaskCacheKey};
+use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskTree};
+use resource_cache::{CacheItem, ResourceCache};
 use scene::{FilterOpHelpers, SceneProperties};
 use tiling::RenderTargetKind;
 
 /*
  A picture represents a dynamically rendered image. It consists of:
 
  * A number of primitives that are drawn onto the picture.
  * A composite operation describing how to composite this
@@ -52,17 +54,16 @@ pub enum PictureKind {
         offset: LayerVector2D,
         color: ColorF,
         blur_radius: f32,
         content_rect: LayerRect,
     },
     BoxShadow {
         blur_radius: f32,
         color: ColorF,
-        blur_regions: Vec<LayerRect>,
         clip_mode: BoxShadowClipMode,
         image_kind: BrushImageKind,
         content_rect: LayerRect,
         cache_key: BoxShadowCacheKey,
     },
     Image {
         // If a mix-blend-mode, contains the render task for
         // the readback of the framebuffer that we use to sample
@@ -83,21 +84,31 @@ pub enum PictureKind {
         // The original reference frame ID for this picture.
         // It is only different if this is part of a 3D
         // rendering context.
         reference_frame_id: ClipId,
         real_local_rect: LayerRect,
     },
 }
 
+// The type of surface that a picture can be drawn to.
+// RenderTask surfaces are not retained across frames.
+// TextureCache surfaces are stored across frames, and
+// also shared between display lists.
+#[derive(Debug)]
+pub enum PictureSurface {
+    RenderTask(RenderTaskId),
+    TextureCache(CacheItem),
+}
+
 #[derive(Debug)]
 pub struct PicturePrimitive {
     // If this picture is drawn to an intermediate surface,
-    // the associated render task.
-    pub render_task_id: Option<RenderTaskId>,
+    // the associated target information.
+    pub surface: Option<PictureSurface>,
 
     // Details specific to this type of picture.
     pub kind: PictureKind,
 
     // List of primitive runs that make up this picture.
     pub runs: Vec<PrimitiveRun>,
 
     // The pipeline that the primitives on this picture belong to.
@@ -108,17 +119,17 @@ pub struct PicturePrimitive {
     // unconditionally draw them.
     pub cull_children: bool,
 }
 
 impl PicturePrimitive {
     pub fn new_text_shadow(shadow: Shadow, pipeline_id: PipelineId) -> Self {
         PicturePrimitive {
             runs: Vec::new(),
-            render_task_id: None,
+            surface: None,
             kind: PictureKind::TextShadow {
                 offset: shadow.offset,
                 color: shadow.color,
                 blur_radius: shadow.blur_radius,
                 content_rect: LayerRect::zero(),
             },
             pipeline_id,
             cull_children: false,
@@ -144,29 +155,27 @@ impl PicturePrimitive {
             }
             _ => true
         }
     }
 
     pub fn new_box_shadow(
         blur_radius: f32,
         color: ColorF,
-        blur_regions: Vec<LayerRect>,
         clip_mode: BoxShadowClipMode,
         image_kind: BrushImageKind,
         cache_key: BoxShadowCacheKey,
         pipeline_id: PipelineId,
     ) -> Self {
         PicturePrimitive {
             runs: Vec::new(),
-            render_task_id: None,
+            surface: None,
             kind: PictureKind::BoxShadow {
                 blur_radius,
                 color,
-                blur_regions,
                 clip_mode,
                 image_kind,
                 content_rect: LayerRect::zero(),
                 cache_key,
             },
             pipeline_id,
             cull_children: false,
         }
@@ -176,17 +185,17 @@ impl PicturePrimitive {
         composite_mode: Option<PictureCompositeMode>,
         is_in_3d_context: bool,
         pipeline_id: PipelineId,
         reference_frame_id: ClipId,
         frame_output_pipeline_id: Option<PipelineId>,
     ) -> Self {
         PicturePrimitive {
             runs: Vec::new(),
-            render_task_id: None,
+            surface: None,
             kind: PictureKind::Image {
                 secondary_render_task_id: None,
                 composite_mode,
                 is_in_3d_context,
                 frame_output_pipeline_id,
                 reference_frame_id,
                 real_local_rect: LayerRect::zero(),
             },
@@ -298,16 +307,18 @@ impl PicturePrimitive {
         &mut self,
         prim_index: PrimitiveIndex,
         prim_context: &PrimitiveContext,
         render_tasks: &mut RenderTaskTree,
         prim_screen_rect: &DeviceIntRect,
         prim_local_rect: &LayerRect,
         child_tasks: Vec<RenderTaskId>,
         parent_tasks: &mut Vec<RenderTaskId>,
+        resource_cache: &mut ResourceCache,
+        gpu_cache: &mut GpuCache,
     ) {
         let content_scale = LayerToWorldScale::new(1.0) * prim_context.device_pixel_scale;
 
         match self.kind {
             PictureKind::Image {
                 ref mut secondary_render_task_id,
                 composite_mode,
                 ..
@@ -318,131 +329,132 @@ impl PicturePrimitive {
                         let picture_task = RenderTask::new_picture(
                             Some(prim_screen_rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             content_origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             child_tasks,
-                            None,
                             PictureType::Image,
                         );
 
                         let blur_std_deviation = blur_radius * prim_context.device_pixel_scale.0;
                         let picture_task_id = render_tasks.add(picture_task);
 
-                        let blur_render_task = RenderTask::new_blur(
+                        let (blur_render_task, _) = RenderTask::new_blur(
                             blur_std_deviation,
                             picture_task_id,
                             render_tasks,
                             RenderTargetKind::Color,
-                            &[],
                             ClearMode::Transparent,
                             PremultipliedColorF::TRANSPARENT,
-                            None,
                         );
 
-                        let blur_render_task_id = render_tasks.add(blur_render_task);
-                        self.render_task_id = Some(blur_render_task_id);
+                        let render_task_id = render_tasks.add(blur_render_task);
+                        parent_tasks.push(render_task_id);
+                        self.surface = Some(PictureSurface::RenderTask(render_task_id));
                     }
                     Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, color))) => {
                         let rect = (prim_local_rect.translate(&-offset) * content_scale).round().to_i32();
                         let picture_task = RenderTask::new_picture(
                             Some(rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             ContentOrigin::Screen(rect.origin),
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             child_tasks,
-                            None,
                             PictureType::Image,
                         );
 
                         let blur_std_deviation = blur_radius * prim_context.device_pixel_scale.0;
                         let picture_task_id = render_tasks.add(picture_task);
 
-                        let blur_render_task = RenderTask::new_blur(
+                        let (blur_render_task, _) = RenderTask::new_blur(
                             blur_std_deviation.round(),
                             picture_task_id,
                             render_tasks,
                             RenderTargetKind::Color,
-                            &[],
                             ClearMode::Transparent,
                             color.premultiplied(),
-                            None,
                         );
 
                         *secondary_render_task_id = Some(picture_task_id);
-                        self.render_task_id = Some(render_tasks.add(blur_render_task));
+
+                        let render_task_id = render_tasks.add(blur_render_task);
+                        parent_tasks.push(render_task_id);
+                        self.surface = Some(PictureSurface::RenderTask(render_task_id));
                     }
                     Some(PictureCompositeMode::MixBlend(..)) => {
                         let picture_task = RenderTask::new_picture(
                             Some(prim_screen_rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             content_origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             child_tasks,
-                            None,
                             PictureType::Image,
                         );
 
                         let readback_task_id = render_tasks.add(RenderTask::new_readback(*prim_screen_rect));
 
                         *secondary_render_task_id = Some(readback_task_id);
                         parent_tasks.push(readback_task_id);
 
-                        self.render_task_id = Some(render_tasks.add(picture_task));
+                        let render_task_id = render_tasks.add(picture_task);
+                        parent_tasks.push(render_task_id);
+                        self.surface = Some(PictureSurface::RenderTask(render_task_id));
                     }
                     Some(PictureCompositeMode::Filter(filter)) => {
                         // If this filter is not currently going to affect
                         // the picture, just collapse this picture into the
                         // current render task. This most commonly occurs
                         // when opacity == 1.0, but can also occur on other
                         // filters and be a significant performance win.
                         if filter.is_noop() {
                             parent_tasks.extend(child_tasks);
-                            self.render_task_id = None;
+                            self.surface = None;
                         } else {
                             let picture_task = RenderTask::new_picture(
                                 Some(prim_screen_rect.size),
                                 prim_index,
                                 RenderTargetKind::Color,
                                 content_origin,
                                 PremultipliedColorF::TRANSPARENT,
                                 ClearMode::Transparent,
                                 child_tasks,
-                                None,
                                 PictureType::Image,
                             );
 
-                            self.render_task_id = Some(render_tasks.add(picture_task));
+                            let render_task_id = render_tasks.add(picture_task);
+                            parent_tasks.push(render_task_id);
+                            self.surface = Some(PictureSurface::RenderTask(render_task_id));
                         }
                     }
                     Some(PictureCompositeMode::Blit) => {
                         let picture_task = RenderTask::new_picture(
                             Some(prim_screen_rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             content_origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             child_tasks,
-                            None,
                             PictureType::Image,
                         );
 
-                        self.render_task_id = Some(render_tasks.add(picture_task));
+                        let render_task_id = render_tasks.add(picture_task);
+                        parent_tasks.push(render_task_id);
+                        self.surface = Some(PictureSurface::RenderTask(render_task_id));
                     }
                     None => {
                         parent_tasks.extend(child_tasks);
-                        self.render_task_id = None;
+                        self.surface = None;
                     }
                 }
             }
             PictureKind::TextShadow { blur_radius, color, content_rect, .. } => {
                 // This is a shadow element. Create a render task that will
                 // render the text run to a target, and then apply a gaussian
                 // blur to that text run in order to build the actual primitive
                 // which will be blitted to the framebuffer.
@@ -463,96 +475,118 @@ impl PicturePrimitive {
                 let picture_task = RenderTask::new_picture(
                     Some(cache_size),
                     prim_index,
                     RenderTargetKind::Color,
                     ContentOrigin::Local(content_rect.origin),
                     color.premultiplied(),
                     ClearMode::Transparent,
                     Vec::new(),
-                    None,
                     PictureType::TextShadow,
                 );
 
                 let picture_task_id = render_tasks.add(picture_task);
 
-                let render_task = RenderTask::new_blur(
+                let (blur_render_task, _) = RenderTask::new_blur(
                     blur_std_deviation,
                     picture_task_id,
                     render_tasks,
                     RenderTargetKind::Color,
-                    &[],
                     ClearMode::Transparent,
                     color.premultiplied(),
-                    None,
                 );
 
-                self.render_task_id = Some(render_tasks.add(render_task));
+                let render_task_id = render_tasks.add(blur_render_task);
+                parent_tasks.push(render_task_id);
+                self.surface = Some(PictureSurface::RenderTask(render_task_id));
             }
-            PictureKind::BoxShadow { blur_radius, clip_mode, ref blur_regions, color, content_rect, cache_key, .. } => {
+            PictureKind::BoxShadow { blur_radius, clip_mode, color, content_rect, cache_key, .. } => {
                 // TODO(gw): Rounding the content rect here to device pixels is not
                 // technically correct. Ideally we should ceil() here, and ensure that
                 // the extra part pixel in the case of fractional sizes is correctly
                 // handled. For now, just use rounding which passes the existing
                 // Gecko tests.
                 let cache_size = (content_rect.size * content_scale).round().to_i32();
 
-                // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
-                // "the image that would be generated by applying to the shadow a
-                // Gaussian blur with a standard deviation equal to half the blur radius."
-                let device_radius = (blur_radius * prim_context.device_pixel_scale.0).round();
-                let blur_std_deviation = device_radius * 0.5;
+                // Request the texture cache item for this box-shadow key. If it
+                // doesn't exist in the cache, the closure is invoked to build
+                // a render task chain to draw the cacheable result.
+                let cache_item = resource_cache.request_render_task(
+                    RenderTaskCacheKey {
+                        size: cache_size,
+                        kind: RenderTaskCacheKeyKind::BoxShadow(cache_key),
+                    },
+                    gpu_cache,
+                    render_tasks,
+                    |render_tasks| {
+                        // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
+                        // "the image that would be generated by applying to the shadow a
+                        // Gaussian blur with a standard deviation equal to half the blur radius."
+                        let device_radius = (blur_radius * prim_context.device_pixel_scale.0).round();
+                        let blur_std_deviation = device_radius * 0.5;
+
+                        let blur_clear_mode = match clip_mode {
+                            BoxShadowClipMode::Outset => {
+                                ClearMode::One
+                            }
+                            BoxShadowClipMode::Inset => {
+                                ClearMode::Zero
+                            }
+                        };
 
-                let blur_clear_mode = match clip_mode {
-                    BoxShadowClipMode::Outset => {
-                        ClearMode::One
-                    }
-                    BoxShadowClipMode::Inset => {
-                        ClearMode::Zero
+                        let picture_task = RenderTask::new_picture(
+                            Some(cache_size),
+                            prim_index,
+                            RenderTargetKind::Alpha,
+                            ContentOrigin::Local(content_rect.origin),
+                            color.premultiplied(),
+                            ClearMode::Zero,
+                            Vec::new(),
+                            PictureType::BoxShadow,
+                        );
+
+                        let picture_task_id = render_tasks.add(picture_task);
+
+                        let (blur_render_task, scale_factor) = RenderTask::new_blur(
+                            blur_std_deviation,
+                            picture_task_id,
+                            render_tasks,
+                            RenderTargetKind::Alpha,
+                            blur_clear_mode,
+                            color.premultiplied(),
+                        );
+
+                        let root_task_id = render_tasks.add(blur_render_task);
+                        parent_tasks.push(root_task_id);
+
+                        // TODO(gw): Remove the nastiness with having to pass
+                        //           the scale factor through the texture cache
+                        //           item user data. This will disappear once
+                        //           the brush_image shader is updated to draw
+                        //           segments, since the scale factor will not
+                        //           be used at all then during drawing.
+                        (root_task_id, [scale_factor, 0.0, 0.0])
                     }
-                };
-
-                let picture_task = RenderTask::new_picture(
-                    Some(cache_size),
-                    prim_index,
-                    RenderTargetKind::Alpha,
-                    ContentOrigin::Local(content_rect.origin),
-                    color.premultiplied(),
-                    ClearMode::Zero,
-                    Vec::new(),
-                    Some(cache_key),
-                    PictureType::BoxShadow,
                 );
 
-                let picture_task_id = render_tasks.add(picture_task);
-
-                let render_task = RenderTask::new_blur(
-                    blur_std_deviation,
-                    picture_task_id,
-                    render_tasks,
-                    RenderTargetKind::Alpha,
-                    blur_regions,
-                    blur_clear_mode,
-                    color.premultiplied(),
-                    Some(cache_key),
-                );
-
-                self.render_task_id = Some(render_tasks.add(render_task));
+                self.surface = Some(PictureSurface::TextureCache(cache_item));
             }
         }
-
-        if let Some(render_task_id) = self.render_task_id {
-            parent_tasks.push(render_task_id);
-        }
     }
 
-    pub fn write_gpu_blocks(&self, _request: &mut GpuDataRequest) {
-        // TODO(gw): We'll need to write the GPU blocks
-        //           here specific to a brush primitive
-        //           once we start drawing pictures as brushes!
+    pub fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
+        match self.kind {
+            PictureKind::TextShadow { .. } |
+            PictureKind::Image { .. } => {
+                request.push([0.0; 4]);
+            }
+            PictureKind::BoxShadow { color, .. } => {
+                request.push(color.premultiplied());
+            }
+        }
     }
 
     pub fn target_kind(&self) -> RenderTargetKind {
         match self.kind {
             PictureKind::TextShadow { .. } => RenderTargetKind::Color,
             PictureKind::BoxShadow { .. } => RenderTargetKind::Alpha,
             PictureKind::Image { .. } => RenderTargetKind::Color,
         }
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -112,17 +112,16 @@ fn get_glyph_metrics(
         bounds.size.width += extra_width;
     }
     if advance.width > 0.0 {
         advance.width += extra_width;
     }
 
     if let Some(transform) = transform {
         bounds = bounds.apply_transform(transform);
-        advance = advance.apply_transform(transform);
     }
 
     // First round out to pixel boundaries
     // CG Origin is bottom left
     let mut left = bounds.origin.x.floor() as i32;
     let mut bottom = bounds.origin.y.floor() as i32;
     let mut right = (bounds.origin.x + bounds.size.width + x_offset).ceil() as i32;
     let mut top = (bounds.origin.y + bounds.size.height + y_offset).ceil() as i32;
@@ -356,18 +355,33 @@ impl FontContext {
         font: &FontInstance,
         key: &GlyphKey,
     ) -> Option<GlyphDimensions> {
         self.get_ct_font(font.font_key, font.size, &font.variations)
             .and_then(|ref ct_font| {
                 let glyph = key.index as CGGlyph;
                 let bitmap = is_bitmap_font(ct_font);
                 let (x_offset, y_offset) = if bitmap { (0.0, 0.0) } else { font.get_subpx_offset(key) };
-                let transform = if font.flags.contains(FontInstanceFlags::SYNTHETIC_ITALICS) {
-                    let shape = FontTransform::identity().synthesize_italics(OBLIQUE_SKEW_FACTOR);
+                let transform = if font.flags.intersects(FontInstanceFlags::SYNTHETIC_ITALICS |
+                                                         FontInstanceFlags::TRANSPOSE |
+                                                         FontInstanceFlags::FLIP_X |
+                                                         FontInstanceFlags::FLIP_Y) {
+                    let mut shape = FontTransform::identity();
+                    if font.flags.contains(FontInstanceFlags::FLIP_X) {
+                        shape = shape.flip_x();
+                    }
+                    if font.flags.contains(FontInstanceFlags::FLIP_Y) {
+                        shape = shape.flip_y();
+                    }
+                    if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
+                        shape = shape.swap_xy();
+                    }
+                    if font.flags.contains(FontInstanceFlags::SYNTHETIC_ITALICS) {
+                        shape = shape.synthesize_italics(OBLIQUE_SKEW_FACTOR);
+                    }
                     Some(CGAffineTransform {
                         a: shape.scale_x as f64,
                         b: -shape.skew_y as f64,
                         c: -shape.skew_x as f64,
                         d: shape.scale_y as f64,
                         tx: 0.0,
                         ty: 0.0,
                     })
@@ -478,16 +492,25 @@ impl FontContext {
         };
 
         let bitmap = is_bitmap_font(&ct_font);
         let (mut shape, (x_offset, y_offset)) = if bitmap {
             (FontTransform::identity(), (0.0, 0.0))
         } else {
             (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key))
         };
+        if font.flags.contains(FontInstanceFlags::FLIP_X) {
+            shape = shape.flip_x();
+        }
+        if font.flags.contains(FontInstanceFlags::FLIP_Y) {
+            shape = shape.flip_y();
+        }
+        if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
+            shape = shape.swap_xy();
+        }
         if font.flags.contains(FontInstanceFlags::SYNTHETIC_ITALICS) {
             shape = shape.synthesize_italics(OBLIQUE_SKEW_FACTOR);
         }
         let transform = if !shape.is_identity() {
             Some(CGAffineTransform {
                 a: shape.scale_x as f64,
                 b: -shape.skew_y as f64,
                 c: -shape.skew_x as f64,
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -100,16 +100,48 @@ fn skew_bitmap(bitmap: &[u8], width: usi
             dest[1] = ((prev_px[1] + 128) >> 8) as u8;
             dest[2] = ((prev_px[2] + 128) >> 8) as u8;
             dest[3] = ((prev_px[3] + 128) >> 8) as u8;
         }
     }
     (skew_buffer, skew_width, left + skew_min as i32)
 }
 
+fn transpose_bitmap(bitmap: &[u8], width: usize, height: usize) -> Vec<u8> {
+    let mut transposed = vec![0u8; width * height * 4];
+    for (y, row) in bitmap.chunks(width * 4).enumerate() {
+        let mut offset = y * 4;
+        for src in row.chunks(4) {
+            transposed[offset .. offset + 4].copy_from_slice(src);
+            offset += height * 4;
+        }
+    }
+    transposed
+}
+
+fn flip_bitmap_x(bitmap: &mut [u8], width: usize, height: usize) {
+    assert!(bitmap.len() == width * height * 4);
+    let pixels = unsafe { slice::from_raw_parts_mut(bitmap.as_mut_ptr() as *mut u32, width * height) };
+    for row in pixels.chunks_mut(width) {
+        row.reverse();
+    }
+}
+
+fn flip_bitmap_y(bitmap: &mut [u8], width: usize, height: usize) {
+    assert!(bitmap.len() == width * height * 4);
+    let pixels = unsafe { slice::from_raw_parts_mut(bitmap.as_mut_ptr() as *mut u32, width * height) };
+    for y in 0 .. height / 2 {
+        let low_row = y * width;
+        let high_row = (height - 1 - y) * width;
+        for x in 0 .. width {
+            pixels.swap(low_row + x, high_row + x);
+        }
+    }
+}
+
 impl FontContext {
     pub fn new() -> FontContext {
         let mut lib: FT_Library = ptr::null_mut();
 
         // Using an LCD filter may add one full pixel to each side if support is built in.
         // As of FreeType 2.8.1, an LCD filter is always used regardless of settings
         // if support for the patent-encumbered LCD filter algorithms is not built in.
         // Thus, the only reasonable way to guess padding is to unconditonally add it if
@@ -245,16 +277,25 @@ impl FontContext {
         let face_flags = unsafe { (*face.face).face_flags };
         let mut result = if (face_flags & (FT_FACE_FLAG_FIXED_SIZES as FT_Long)) != 0 &&
                             (face_flags & (FT_FACE_FLAG_SCALABLE as FT_Long)) == 0 &&
                             (load_flags & FT_LOAD_NO_BITMAP) == 0 {
             unsafe { FT_Set_Transform(face.face, ptr::null_mut(), ptr::null_mut()) };
             self.choose_bitmap_size(face.face, req_size * y_scale)
         } else {
             let mut shape = font.transform.invert_scale(x_scale, y_scale);
+            if font.flags.contains(FontInstanceFlags::FLIP_X) {
+                shape = shape.flip_x();
+            }
+            if font.flags.contains(FontInstanceFlags::FLIP_Y) {
+                shape = shape.flip_y();
+            }
+            if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
+                shape = shape.swap_xy();
+            }
             if font.flags.contains(FontInstanceFlags::SYNTHETIC_ITALICS) {
                 shape = shape.synthesize_italics(OBLIQUE_SKEW_FACTOR);
             };
             let mut ft_shape = FT_Matrix {
                 xx: (shape.scale_x * 65536.0) as FT_Fixed,
                 xy: (shape.skew_x * -65536.0) as FT_Fixed,
                 yx: (shape.skew_y * -65536.0) as FT_Fixed,
                 yy: (shape.scale_y * 65536.0) as FT_Fixed,
@@ -386,16 +427,28 @@ impl FontContext {
                     width = (x1.ceil() - x0.floor()) as u32;
                     height = (y1.ceil() - y0.floor()) as u32;
                     advance *= scale;
                     if font.flags.contains(FontInstanceFlags::SYNTHETIC_ITALICS) {
                         let (skew_min, skew_max) = get_skew_bounds(top - height as i32, top);
                         left += skew_min as i32;
                         width += (skew_max - skew_min) as u32;
                     }
+                    if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
+                        mem::swap(&mut width, &mut height);
+                        mem::swap(&mut left, &mut top);
+                        left -= width as i32;
+                        top += height as i32;
+                    }
+                    if font.flags.contains(FontInstanceFlags::FLIP_X) {
+                        left = -(left + width as i32);
+                    }
+                    if font.flags.contains(FontInstanceFlags::FLIP_Y) {
+                        top = -(top - height as i32);
+                    }
                 }
                 Some(GlyphDimensions {
                     left,
                     top,
                     width,
                     height,
                     advance,
                 })
@@ -565,17 +618,17 @@ impl FontContext {
             "Rasterizing {:?} as {:?} with dimensions {:?}",
             key,
             font.render_mode,
             dimensions
         );
 
         let bitmap = unsafe { &(*slot).bitmap };
         let pixel_mode = unsafe { mem::transmute(bitmap.pixel_mode as u32) };
-        let (mut actual_width, actual_height) = match pixel_mode {
+        let (mut actual_width, mut actual_height) = match pixel_mode {
             FT_Pixel_Mode::FT_PIXEL_MODE_LCD => {
                 assert!(bitmap.width % 3 == 0);
                 ((bitmap.width / 3) as usize, bitmap.rows as usize)
             }
             FT_Pixel_Mode::FT_PIXEL_MODE_LCD_V => {
                 assert!(bitmap.rows % 3 == 0);
                 (bitmap.width as usize, (bitmap.rows / 3) as usize)
             }
@@ -672,16 +725,31 @@ impl FontContext {
             FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => {
                 if font.flags.contains(FontInstanceFlags::SYNTHETIC_ITALICS) {
                     let (skew_buffer, skew_width, skew_left) =
                         skew_bitmap(&final_buffer, actual_width, actual_height, left, top);
                     final_buffer = skew_buffer;
                     actual_width = skew_width;
                     left = skew_left;
                 }
+                if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
+                    final_buffer = transpose_bitmap(&final_buffer, actual_width, actual_height);
+                    mem::swap(&mut actual_width, &mut actual_height);
+                    mem::swap(&mut left, &mut top);
+                    left -= actual_width as i32;
+                    top += actual_height as i32;
+                }
+                if font.flags.contains(FontInstanceFlags::FLIP_X) {
+                    flip_bitmap_x(&mut final_buffer, actual_width, actual_height);
+                    left = -(left + actual_width as i32);
+                }
+                if font.flags.contains(FontInstanceFlags::FLIP_Y) {
+                    flip_bitmap_y(&mut final_buffer, actual_width, actual_height);
+                    top = -(top - actual_height as i32);
+                }
             }
             FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => {
                 unsafe {
                     left += (*slot).bitmap_left;
                     top += (*slot).bitmap_top - height as i32;
                 }
             }
             _ => {}
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -231,18 +231,33 @@ impl FontContext {
 
     pub fn get_glyph_dimensions(
         &mut self,
         font: &FontInstance,
         key: &GlyphKey,
     ) -> Option<GlyphDimensions> {
         let size = font.size.to_f32_px();
         let bitmaps = is_bitmap_font(font);
-        let transform = if font.flags.contains(FontInstanceFlags::SYNTHETIC_ITALICS) {
-            let shape = FontTransform::identity().synthesize_italics(OBLIQUE_SKEW_FACTOR);
+        let transform = if font.flags.intersects(FontInstanceFlags::SYNTHETIC_ITALICS |
+                                                 FontInstanceFlags::TRANSPOSE |
+                                                 FontInstanceFlags::FLIP_X |
+                                                 FontInstanceFlags::FLIP_Y) {
+            let mut shape = FontTransform::identity();
+            if font.flags.contains(FontInstanceFlags::FLIP_X) {
+                shape = shape.flip_x();
+            }
+            if font.flags.contains(FontInstanceFlags::FLIP_Y) {
+                shape = shape.flip_y();
+            }
+            if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
+                shape = shape.swap_xy();
+            }
+            if font.flags.contains(FontInstanceFlags::SYNTHETIC_ITALICS) {
+                shape = shape.synthesize_italics(OBLIQUE_SKEW_FACTOR);
+            }
             Some(dwrote::DWRITE_MATRIX {
                 m11: shape.scale_x,
                 m12: shape.skew_y,
                 m21: shape.skew_x,
                 m22: shape.scale_y,
                 dx: 0.0,
                 dy: 0.0,
             })
@@ -354,16 +369,25 @@ impl FontContext {
         let (.., y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
         let size = (font.size.to_f64_px() * y_scale) as f32;
         let bitmaps = is_bitmap_font(font);
         let (mut shape, (x_offset, y_offset)) = if bitmaps {
             (FontTransform::identity(), (0.0, 0.0))
         } else {
             (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key))
         };
+        if font.flags.contains(FontInstanceFlags::FLIP_X) {
+            shape = shape.flip_x();
+        }
+        if font.flags.contains(FontInstanceFlags::FLIP_Y) {
+            shape = shape.flip_y();
+        }
+        if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
+            shape = shape.swap_xy();
+        }
         if font.flags.contains(FontInstanceFlags::SYNTHETIC_ITALICS) {
             shape = shape.synthesize_italics(OBLIQUE_SKEW_FACTOR);
         }
         let transform = if !shape.is_identity() || (x_offset, y_offset) != (0.0, 0.0) {
             Some(dwrote::DWRITE_MATRIX {
                 m11: shape.scale_x,
                 m12: shape.skew_y,
                 m21: shape.skew_x,
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,33 +1,33 @@
 /* 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, BuiltDisplayList, ClipAndScrollInfo, ClipId, ClipMode, ColorF, ColorU};
-use api::{DeviceIntRect, DevicePixelScale, DevicePoint};
+use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipAndScrollInfo, ClipId, ClipMode};
+use api::{ColorF, ColorU, DeviceIntRect, DevicePixelScale, DevicePoint};
 use api::{ComplexClipRegion, ExtendMode, FontRenderMode};
 use api::{GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag};
 use api::{LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LayerVector2D, LineOrientation};
 use api::{LineStyle, PipelineId, PremultipliedColorF, TileOffset, WorldToLayerTransform};
 use api::{YuvColorSpace, YuvFormat};
-use border::BorderCornerInstance;
+use border::{BorderCornerInstance, BorderEdgeKind};
 use clip_scroll_tree::{CoordinateSystemId, ClipScrollTree};
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipSource, ClipSourcesHandle, ClipStore};
 use frame_builder::PrimitiveContext;
 use glyph_rasterizer::{FontInstance, FontTransform};
 use internal_types::{FastHashMap};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::{ClipChainRectIndex, ClipScrollNodeData};
 use picture::{PictureKind, PicturePrimitive};
 use profiler::FrameProfileCounters;
-use render_task::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipWorkItem, RenderTask};
-use render_task::{RenderTaskId, RenderTaskTree};
+use render_task::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipWorkItem};
+use render_task::{RenderTask, RenderTaskId, RenderTaskTree};
 use renderer::{BLOCKS_PER_UV_RECT, MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use segment::SegmentBuilder;
 use std::{mem, usize};
 use std::rc::Rc;
 use util::{MatrixHelpers, calculate_screen_bounding_rect, pack_as_float};
 use util::recycle_vec;
@@ -146,17 +146,16 @@ pub struct PrimitiveIndex(pub usize);
 pub enum PrimitiveKind {
     TextRun,
     Image,
     YuvImage,
     Border,
     AlignedGradient,
     AngleGradient,
     RadialGradient,
-    Line,
     Picture,
     Brush,
 }
 
 impl GpuCacheHandle {
     pub fn as_int(&self, gpu_cache: &GpuCache) -> i32 {
         gpu_cache.get_address(self).as_int()
     }
@@ -207,16 +206,22 @@ pub enum BrushKind {
     Mask {
         clip_mode: ClipMode,
         kind: BrushMaskKind,
     },
     Solid {
         color: ColorF,
     },
     Clear,
+    Line {
+        color: PremultipliedColorF,
+        wavy_line_thickness: f32,
+        style: LineStyle,
+        orientation: LineOrientation,
+    }
 }
 
 impl BrushKind {
     fn is_solid(&self) -> bool {
         match *self {
             BrushKind::Solid { .. } => true,
             _ => false,
         }
@@ -326,47 +331,36 @@ impl BrushPrimitive {
                 ]);
                 request.push([
                     radii.bottom_right.width,
                     radii.bottom_right.height,
                     radii.bottom_left.width,
                     radii.bottom_left.height,
                 ]);
             }
+            BrushKind::Line { color, wavy_line_thickness, style, orientation } => {
+                request.push(color);
+                request.push([
+                    wavy_line_thickness,
+                    pack_as_float(style as u32),
+                    pack_as_float(orientation as u32),
+                    0.0,
+                ]);
+            }
         }
     }
 }
 
-#[derive(Debug, Clone)]
-#[repr(C)]
-pub struct LinePrimitive {
-    pub color: PremultipliedColorF,
-    pub wavy_line_thickness: f32,
-    pub style: LineStyle,
-    pub orientation: LineOrientation,
-}
-
-impl LinePrimitive {
-    fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
-        request.push(self.color);
-        request.push([
-            self.wavy_line_thickness,
-            pack_as_float(self.style as u32),
-            pack_as_float(self.orientation as u32),
-            0.0,
-        ]);
-    }
-}
-
 #[derive(Debug)]
 pub struct ImagePrimitiveCpu {
     pub image_key: ImageKey,
     pub image_rendering: ImageRendering,
     pub tile_offset: Option<TileOffset>,
     pub tile_spacing: LayerSize,
+    pub alpha_type: AlphaType,
     // TODO(gw): Build on demand
     pub gpu_blocks: [GpuBlockData; BLOCKS_PER_UV_RECT],
 }
 
 impl ToGpuBlocks for ImagePrimitiveCpu {
     fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
         request.extend_from_slice(&self.gpu_blocks);
     }
@@ -388,16 +382,17 @@ impl ToGpuBlocks for YuvImagePrimitiveCp
     fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
         request.push(self.gpu_block);
     }
 }
 
 #[derive(Debug)]
 pub struct BorderPrimitiveCpu {
     pub corner_instances: [BorderCornerInstance; 4],
+    pub edges: [BorderEdgeKind; 4],
     pub gpu_blocks: [GpuBlockData; 8],
 }
 
 impl ToGpuBlocks for BorderPrimitiveCpu {
     fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
         request.extend_from_slice(&self.gpu_blocks);
     }
 }
@@ -674,16 +669,24 @@ impl TextRunPrimitiveCpu {
         }
         font
     }
 
     pub fn is_shadow(&self) -> bool {
         self.shadow_color.a != 0
     }
 
+    pub fn get_color(&self) -> ColorF {
+        ColorF::from(if self.is_shadow() {
+            self.shadow_color
+        } else {
+            self.font.color
+        })
+    }
+
     fn prepare_for_render(
         &mut self,
         resource_cache: &mut ResourceCache,
         device_pixel_scale: DevicePixelScale,
         transform: Option<&LayerToWorldTransform>,
         display_list: &BuiltDisplayList,
         gpu_cache: &mut GpuCache,
     ) {
@@ -723,24 +726,19 @@ impl TextRunPrimitiveCpu {
                 self.glyph_gpu_blocks.push(gpu_block);
             }
         }
 
         resource_cache.request_glyphs(font, &self.glyph_keys, gpu_cache);
     }
 
     fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
+        request.push(self.get_color().premultiplied());
+        // this is the only case where we need to provide plain color to GPU
         let bg_color = ColorF::from(self.font.bg_color);
-        let color = ColorF::from(if self.is_shadow() {
-            self.shadow_color
-        } else {
-            self.font.color
-        });
-        request.push(color.premultiplied());
-        // this is the only case where we need to provide plain color to GPU
         request.extend_from_slice(&[
             GpuBlockData { data: [bg_color.r, bg_color.g, bg_color.b, 1.0] }
         ]);
         request.push([
             self.offset.x,
             self.offset.y,
             0.0,
             0.0,
@@ -939,62 +937,58 @@ pub enum PrimitiveContainer {
     TextRun(TextRunPrimitiveCpu),
     Image(ImagePrimitiveCpu),
     YuvImage(YuvImagePrimitiveCpu),
     Border(BorderPrimitiveCpu),
     AlignedGradient(GradientPrimitiveCpu),
     AngleGradient(GradientPrimitiveCpu),
     RadialGradient(RadialGradientPrimitiveCpu),
     Picture(PicturePrimitive),
-    Line(LinePrimitive),
     Brush(BrushPrimitive),
 }
 
 pub struct PrimitiveStore {
     /// CPU side information only.
     pub cpu_brushes: Vec<BrushPrimitive>,
     pub cpu_text_runs: Vec<TextRunPrimitiveCpu>,
     pub cpu_pictures: Vec<PicturePrimitive>,
     pub cpu_images: Vec<ImagePrimitiveCpu>,
     pub cpu_yuv_images: Vec<YuvImagePrimitiveCpu>,
     pub cpu_gradients: Vec<GradientPrimitiveCpu>,
     pub cpu_radial_gradients: Vec<RadialGradientPrimitiveCpu>,
     pub cpu_metadata: Vec<PrimitiveMetadata>,
     pub cpu_borders: Vec<BorderPrimitiveCpu>,
-    pub cpu_lines: Vec<LinePrimitive>,
 }
 
 impl PrimitiveStore {
     pub fn new() -> PrimitiveStore {
         PrimitiveStore {
             cpu_metadata: Vec::new(),
             cpu_brushes: Vec::new(),
             cpu_text_runs: Vec::new(),
             cpu_pictures: Vec::new(),
             cpu_images: Vec::new(),
             cpu_yuv_images: Vec::new(),
             cpu_gradients: Vec::new(),
             cpu_radial_gradients: Vec::new(),
             cpu_borders: Vec::new(),
-            cpu_lines: Vec::new(),
         }
     }
 
     pub fn recycle(self) -> Self {
         PrimitiveStore {
             cpu_metadata: recycle_vec(self.cpu_metadata),
             cpu_brushes: recycle_vec(self.cpu_brushes),
             cpu_text_runs: recycle_vec(self.cpu_text_runs),
             cpu_pictures: recycle_vec(self.cpu_pictures),
             cpu_images: recycle_vec(self.cpu_images),
             cpu_yuv_images: recycle_vec(self.cpu_yuv_images),
             cpu_gradients: recycle_vec(self.cpu_gradients),
             cpu_radial_gradients: recycle_vec(self.cpu_radial_gradients),
             cpu_borders: recycle_vec(self.cpu_borders),
-            cpu_lines: recycle_vec(self.cpu_lines),
         }
     }
 
     pub fn add_primitive(
         &mut self,
         local_rect: &LayerRect,
         local_clip_rect: &LayerRect,
         is_backface_visible: bool,
@@ -1020,40 +1014,30 @@ impl PrimitiveStore {
         };
 
         let metadata = match container {
             PrimitiveContainer::Brush(brush) => {
                 let opacity = match brush.kind {
                     BrushKind::Clear => PrimitiveOpacity::translucent(),
                     BrushKind::Solid { ref color } => PrimitiveOpacity::from_alpha(color.a),
                     BrushKind::Mask { .. } => PrimitiveOpacity::translucent(),
+                    BrushKind::Line { .. } => PrimitiveOpacity::translucent(),
                 };
 
                 let metadata = PrimitiveMetadata {
                     opacity,
                     prim_kind: PrimitiveKind::Brush,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_brushes.len()),
                     ..base_metadata
                 };
 
                 self.cpu_brushes.push(brush);
 
                 metadata
             }
-            PrimitiveContainer::Line(line) => {
-                let metadata = PrimitiveMetadata {
-                    opacity: PrimitiveOpacity::translucent(),
-                    prim_kind: PrimitiveKind::Line,
-                    cpu_prim_index: SpecificPrimitiveIndex(self.cpu_lines.len()),
-                    ..base_metadata
-                };
-
-                self.cpu_lines.push(line);
-                metadata
-            }
             PrimitiveContainer::TextRun(text_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
                     prim_kind: PrimitiveKind::TextRun,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_runs.len()),
                     ..base_metadata
                 };
 
@@ -1162,27 +1146,29 @@ impl PrimitiveStore {
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         child_tasks: Vec<RenderTaskId>,
         parent_tasks: &mut Vec<RenderTaskId>,
         pic_index: SpecificPrimitiveIndex,
     ) {
         let metadata = &mut self.cpu_metadata[prim_index.0];
         match metadata.prim_kind {
-            PrimitiveKind::Border | PrimitiveKind::Line => {}
+            PrimitiveKind::Border => {}
             PrimitiveKind::Picture => {
                 self.cpu_pictures[metadata.cpu_prim_index.0]
                     .prepare_for_render(
                         prim_index,
                         prim_context,
                         render_tasks,
                         metadata.screen_rect.as_ref().expect("bug: trying to draw an off-screen picture!?"),
                         &metadata.local_rect,
                         child_tasks,
                         parent_tasks,
+                        resource_cache,
+                        gpu_cache,
                     );
             }
             PrimitiveKind::TextRun => {
                 let pic = &self.cpu_pictures[pic_index.0];
                 let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
                 // The transform only makes sense for screen space rasterization
                 let transform = match pic.kind {
                     PictureKind::BoxShadow { .. } => None,
@@ -1243,35 +1229,16 @@ impl PrimitiveStore {
 
         // Mark this GPU resource as required for this frame.
         if let Some(mut request) = gpu_cache.request(&mut metadata.gpu_location) {
             // has to match VECS_PER_BRUSH_PRIM
             request.push(metadata.local_rect);
             request.push(metadata.local_clip_rect);
 
             match metadata.prim_kind {
-                PrimitiveKind::Line => {
-                    let line = &self.cpu_lines[metadata.cpu_prim_index.0];
-                    line.write_gpu_blocks(&mut request);
-
-                    // TODO(gw): This is a bit of a hack. The Line type
-                    //           is drawn by the brush_line shader, so the
-                    //           layout here needs to conform to the same
-                    //           BrushPrimitive layout. We should tidy this
-                    //           up in the future so it's enforced that these
-                    //           types use a shared function to write out the
-                    //           GPU blocks...
-                    request.push(metadata.local_rect);
-                    request.push([
-                        EdgeAaSegmentMask::empty().bits() as f32,
-                        0.0,
-                        0.0,
-                        0.0
-                    ]);
-                }
                 PrimitiveKind::Border => {
                     let border = &self.cpu_borders[metadata.cpu_prim_index.0];
                     border.write_gpu_blocks(request);
                 }
                 PrimitiveKind::Image => {
                     let image = &self.cpu_images[metadata.cpu_prim_index.0];
                     image.write_gpu_blocks(request);
                 }
@@ -1301,48 +1268,30 @@ impl PrimitiveStore {
 
                     // TODO(gw): This is a bit of a hack. The Picture type
                     //           is drawn by the brush_image shader, so the
                     //           layout here needs to conform to the same
                     //           BrushPrimitive layout. We should tidy this
                     //           up in the future so it's enforced that these
                     //           types use a shared function to write out the
                     //           GPU blocks...
-                    request.push(metadata.local_rect);
-                    request.push([
-                        EdgeAaSegmentMask::empty().bits() as f32,
-                        0.0,
-                        0.0,
-                        0.0
-                    ]);
+                    request.write_segment(metadata.local_rect);
                 }
                 PrimitiveKind::Brush => {
                     let brush = &self.cpu_brushes[metadata.cpu_prim_index.0];
                     brush.write_gpu_blocks(&mut request);
                     match brush.segment_desc {
                         Some(ref segment_desc) => {
                             for segment in &segment_desc.segments {
                                 // has to match VECS_PER_SEGMENT
-                                request.push(segment.local_rect);
-                                request.push([
-                                    segment.edge_flags.bits() as f32,
-                                    0.0,
-                                    0.0,
-                                    0.0
-                                ]);
+                                request.write_segment(segment.local_rect);
                             }
                         }
                         None => {
-                            request.push(metadata.local_rect);
-                            request.push([
-                                EdgeAaSegmentMask::empty().bits() as f32,
-                                0.0,
-                                0.0,
-                                0.0
-                            ]);
+                            request.write_segment(metadata.local_rect);
                         }
                     }
                 }
             }
         }
     }
 
     fn write_brush_segment_description(
@@ -1508,17 +1457,16 @@ impl PrimitiveStore {
                 let segment_screen_rect = calculate_screen_bounding_rect(
                     &prim_context.scroll_node.world_content_transform,
                     &segment.local_rect,
                     prim_context.device_pixel_scale,
                 );
 
                 combined_outer_rect.intersection(&segment_screen_rect).map(|bounds| {
                     let clip_task = RenderTask::new_mask(
-                        None,
                         bounds,
                         clips.clone(),
                         prim_context.scroll_node.coordinate_system_id,
                     );
 
                     let clip_task_id = render_tasks.add(clip_task);
                     tasks.push(clip_task_id);
 
@@ -1550,22 +1498,23 @@ impl PrimitiveStore {
         let prim_screen_rect = match prim_screen_rect.intersection(screen_rect) {
             Some(rect) => rect,
             None => {
                 self.cpu_metadata[prim_index.0].screen_rect = None;
                 return false;
             }
         };
 
-        let clip_chain = prim_context.clip_node.clip_chain_node.clone();
-        let mut combined_outer_rect = match clip_chain {
-            Some(ref node) => prim_screen_rect.intersection(&node.combined_outer_screen_rect),
+        let mut combined_outer_rect = match prim_context.clip_chain {
+            Some(ref chain) => prim_screen_rect.intersection(&chain.combined_outer_screen_rect),
             None => Some(prim_screen_rect),
         };
 
+        let clip_chain = prim_context.clip_chain.map_or(None, |x| x.nodes.clone());
+
         let prim_coordinate_system_id = prim_context.scroll_node.coordinate_system_id;
         let transform = &prim_context.scroll_node.world_content_transform;
         let extra_clip =  {
             let metadata = &self.cpu_metadata[prim_index.0];
             let prim_clips = clip_store.get_mut(&metadata.clip_sources);
             if prim_clips.has_clips() {
                 prim_clips.update(gpu_cache, resource_cache);
                 let (screen_inner_rect, screen_outer_rect) =
@@ -1583,19 +1532,16 @@ impl PrimitiveStore {
                     },
                     // The local_clip_rect a property of ClipChain nodes that are ClipScrollNodes.
                     // It's used to calculate a local clipping rectangle before we reach this
                     // point, so we can set it to zero here. It should be unused from this point
                     // on.
                     local_clip_rect: LayerRect::zero(),
                     screen_inner_rect,
                     screen_outer_rect: screen_outer_rect.unwrap_or(prim_screen_rect),
-                    combined_outer_screen_rect:
-                        combined_outer_rect.unwrap_or_else(DeviceIntRect::zero),
-                    combined_inner_screen_rect: DeviceIntRect::zero(),
                     prev: None,
                 }))
             } else {
                 None
             }
         };
 
         // If everything is clipped out, then we don't need to render this primitive.
@@ -1648,17 +1594,16 @@ impl PrimitiveStore {
             node_data,
             &clips,
             &combined_outer_rect,
         ) {
             return true;
         }
 
         let clip_task = RenderTask::new_mask(
-            None,
             combined_outer_rect,
             clips,
             prim_coordinate_system_id,
         );
 
         let clip_task_id = render_tasks.add(clip_task);
         self.cpu_metadata[prim_index.0].clip_task_id = Some(clip_task_id);
         tasks.push(clip_task_id);
@@ -1783,17 +1728,17 @@ impl PrimitiveStore {
             };
 
             let screen_bounding_rect = calculate_screen_bounding_rect(
                 &prim_context.scroll_node.world_content_transform,
                 &local_rect,
                 prim_context.device_pixel_scale,
             );
 
-            let clip_bounds = match prim_context.clip_node.clip_chain_node {
+            let clip_bounds = match prim_context.clip_chain {
                 Some(ref node) => node.combined_outer_screen_rect,
                 None => *screen_rect,
             };
             metadata.screen_rect = screen_bounding_rect.intersection(&clip_bounds);
 
             if metadata.screen_rect.is_none() && perform_culling {
                 return None;
             }
@@ -1866,23 +1811,34 @@ impl PrimitiveStore {
             local_rect_in_original_parent_space: LayerRect::zero(),
         };
 
         for run in runs {
             // TODO(gw): Perhaps we can restructure this to not need to create
             //           a new primitive context for every run (if the hash
             //           lookups ever show up in a profile).
             let scroll_node = &clip_scroll_tree.nodes[&run.clip_and_scroll.scroll_node_id];
-            let clip_node = &clip_scroll_tree.nodes[&run.clip_and_scroll.clip_node_id()];
+            let clip_chain = clip_scroll_tree.get_clip_chain(&run.clip_and_scroll.clip_node_id());
+
+            if perform_culling {
+                if !scroll_node.invertible {
+                    debug!("{:?} {:?}: position not invertible", run.base_prim_index, pipeline_id);
+                    continue;
+                }
 
-            if perform_culling && !clip_node.is_visible() {
-                debug!("{:?} of clipped out {:?}", run.base_prim_index, pipeline_id);
-                continue;
+                match clip_chain {
+                     Some(ref chain) if chain.combined_outer_screen_rect.is_empty() => {
+                        debug!("{:?} {:?}: clipped out", run.base_prim_index, pipeline_id);
+                        continue;
+                    }
+                    _ => {},
+                }
             }
 
+
             let parent_relative_transform = parent_prim_context
                 .scroll_node
                 .world_content_transform
                 .inverse()
                 .map(|inv_parent| {
                     inv_parent.pre_mul(&scroll_node.world_content_transform)
                 });
 
@@ -1900,23 +1856,23 @@ impl PrimitiveStore {
             let display_list = &pipelines
                 .get(&pipeline_id)
                 .expect("No display list?")
                 .display_list;
 
             let child_prim_context = PrimitiveContext::new(
                 parent_prim_context.device_pixel_scale,
                 display_list,
-                clip_node,
+                clip_chain,
                 scroll_node,
             );
 
 
             let clip_chain_rect = match perform_culling {
-                true => get_local_clip_rect_for_nodes(scroll_node, clip_node),
+                true => get_local_clip_rect_for_nodes(scroll_node, clip_chain),
                 false => None,
             };
 
             let clip_chain_rect_index = match clip_chain_rect {
                 Some(rect) if rect.is_empty() => continue,
                 Some(rect) => {
                     local_rects.push(rect);
                     ClipChainRectIndex(local_rects.len() - 1)
@@ -1989,27 +1945,24 @@ impl InsideTest<ComplexClipRegion> for C
             clip.radii.bottom_left.width >= self.radii.bottom_left.width - delta_left &&
             clip.radii.bottom_left.height >= self.radii.bottom_left.height - delta_bottom &&
             clip.radii.bottom_right.width >= self.radii.bottom_right.width - delta_right &&
             clip.radii.bottom_right.height >= self.radii.bottom_right.height - delta_bottom
     }
 }
 
 fn convert_clip_chain_to_clip_vector(
-    clip_chain: ClipChain,
-    extra_clip: ClipChain,
+    clip_chain_nodes: ClipChainNodeRef,
+    extra_clip: ClipChainNodeRef,
     combined_outer_rect: &DeviceIntRect,
     combined_inner_rect: &mut DeviceIntRect,
 ) -> Vec<ClipWorkItem> {
     // Filter out all the clip instances that don't contribute to the result.
     ClipChainNodeIter { current: extra_clip }
-        .chain(ClipChainNodeIter { current: clip_chain })
-        .take_while(|node| {
-            !node.combined_inner_screen_rect.contains_rect(&combined_outer_rect)
-        })
+        .chain(ClipChainNodeIter { current: clip_chain_nodes })
         .filter_map(|node| {
             *combined_inner_rect = if !node.screen_inner_rect.is_empty() {
                 // If this clip's inner area contains the area of the primitive clipped
                 // by previous clips, then it's not going to affect rendering in any way.
                 if node.screen_inner_rect.contains_rect(&combined_outer_rect) {
                     return None;
                 }
                 combined_inner_rect.intersection(&node.screen_inner_rect)
@@ -2020,19 +1973,24 @@ fn convert_clip_chain_to_clip_vector(
 
             Some(node.work_item.clone())
         })
         .collect()
 }
 
 fn get_local_clip_rect_for_nodes(
     scroll_node: &ClipScrollNode,
-    clip_node: &ClipScrollNode,
+    clip_chain: Option<&ClipChain>,
 ) -> Option<LayerRect> {
-    let local_rect = ClipChainNodeIter { current: clip_node.clip_chain_node.clone() }.fold(
+    let clip_chain_nodes = match clip_chain {
+        Some(ref clip_chain) => clip_chain.nodes.clone(),
+        None => return None,
+    };
+
+    let local_rect = ClipChainNodeIter { current: clip_chain_nodes }.fold(
         None,
         |combined_local_clip_rect: Option<LayerRect>, node| {
             if node.work_item.coordinate_system_id != scroll_node.coordinate_system_id {
                 return combined_local_clip_rect;
             }
 
             Some(match combined_local_clip_rect {
                 Some(combined_rect) =>
@@ -2043,8 +2001,28 @@ fn get_local_clip_rect_for_nodes(
     );
 
     match local_rect {
         Some(local_rect) =>
             Some(scroll_node.coordinate_system_relative_transform.unapply(&local_rect)),
         None => None,
     }
 }
+
+impl<'a> GpuDataRequest<'a> {
+    // Write the GPU cache data for an individual segment.
+    // TODO(gw): The second block is currently unused. In
+    //           the future, it will be used to store a
+    //           UV rect, allowing segments to reference
+    //           part of an image.
+    fn write_segment(
+        &mut self,
+        local_rect: LayerRect,
+    ) {
+        self.push(local_rect);
+        self.push([
+            0.0,
+            0.0,
+            0.0,
+            0.0
+        ]);
+    }
+}
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -939,16 +939,17 @@ impl Profiler {
         debug_renderer: &mut DebugRenderer,
     ) {
         Profiler::draw_counters(
             &[
                 &renderer_profile.frame_time as &ProfileCounter,
                 &renderer_profile.color_targets,
                 &renderer_profile.alpha_targets,
                 &renderer_profile.draw_calls,
+                &renderer_profile.vertices,
                 &self.backend_time,
                 &self.compositor_time,
                 &self.gpu_time,
             ],
             debug_renderer,
             true,
             &mut self.draw_state,
         );
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -796,41 +796,43 @@ impl ToDebugString for SpecificDisplayIt
             SpecificDisplayItem::Line(..) => String::from("line"),
             SpecificDisplayItem::Gradient(..) => String::from("gradient"),
             SpecificDisplayItem::RadialGradient(..) => String::from("radial_gradient"),
             SpecificDisplayItem::BoxShadow(..) => String::from("box_shadow"),
             SpecificDisplayItem::Border(..) => String::from("border"),
             SpecificDisplayItem::PushStackingContext(..) => String::from("push_stacking_context"),
             SpecificDisplayItem::Iframe(..) => String::from("iframe"),
             SpecificDisplayItem::Clip(..) => String::from("clip"),
+            SpecificDisplayItem::ClipChain(..) => String::from("clip_chain"),
             SpecificDisplayItem::ScrollFrame(..) => String::from("scroll_frame"),
             SpecificDisplayItem::StickyFrame(..) => String::from("sticky_frame"),
             SpecificDisplayItem::SetGradientStops => String::from("set_gradient_stops"),
             SpecificDisplayItem::PopStackingContext => String::from("pop_stacking_context"),
             SpecificDisplayItem::PushShadow(..) => String::from("push_shadow"),
             SpecificDisplayItem::PopAllShadows => String::from("pop_all_shadows"),
         }
     }
 }
 
 
 #[cfg(feature = "capture")]
 impl RenderBackend {
     // Note: the mutable `self` is only needed here for resolving blob images
     fn save_capture(&mut self, root: &PathBuf) -> Vec<ExternalCaptureImage> {
-        use ron::ser::pretty;
+        use ron::ser::{to_string_pretty, PrettyConfig};
         use std::fs;
         use std::io::Write;
 
         info!("capture: saving {}", root.to_string_lossy());
+        let ron_config = PrettyConfig::default();
         let (resources, deferred) = self.resource_cache.save_capture(root);
 
         for (&id, doc) in &self.documents {
             info!("\tdocument {:?}", id);
-            let ron = pretty::to_string(&doc.scene).unwrap();
+            let ron = to_string_pretty(&doc.scene, ron_config.clone()).unwrap();
             let file_name = format!("scene-{}-{}.ron", (id.0).0, id.1);
             let ron_path = root.clone().join(file_name);
             let mut file = fs::File::create(ron_path).unwrap();
             write!(file, "{}\n", ron).unwrap();
         }
 
         info!("\tbackend");
         let serial = PlainRenderBackend {
@@ -839,17 +841,17 @@ impl RenderBackend {
             frame_config: self.frame_config.clone(),
             documents: self.documents
                 .iter()
                 .map(|(id, doc)| (*id, doc.view.clone()))
                 .collect(),
             resources,
         };
 
-        let ron = pretty::to_string(&serial).unwrap();
+        let ron = to_string_pretty(&serial, ron_config).unwrap();
         let ron_path = root.clone().join("backend.ron");
         let mut file = fs::File::create(ron_path).unwrap();
         write!(file, "{}\n", ron).unwrap();
 
         deferred
     }
 
     fn load_capture(
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,25 +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::{ClipId, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
-use api::{LayerRect, PremultipliedColorF};
+use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize};
+use api::{ImageDescriptor, ImageFormat, LayerRect, PremultipliedColorF};
 use box_shadow::BoxShadowCacheKey;
 use clip::{ClipSourcesWeakHandle};
 use clip_scroll_tree::CoordinateSystemId;
+use device::TextureFilter;
+use gpu_cache::GpuCache;
 use gpu_types::{ClipScrollNodeIndex, PictureType};
-use internal_types::RenderPassIndex;
+use internal_types::{FastHashMap, RenderPassIndex, SourceTexture};
 use picture::ContentOrigin;
 use prim_store::{PrimitiveIndex};
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
+use resource_cache::CacheItem;
 use std::{cmp, ops, usize, f32, i32};
 use std::rc::Rc;
+use texture_cache::{TextureCache, TextureCacheHandle};
 use tiling::{RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind};
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
 pub const MAX_BLUR_STD_DEVIATION: f32 = 4.0;
 pub const MIN_DOWNSCALING_RT_SIZE: i32 = 128;
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
@@ -30,82 +34,92 @@ pub struct RenderTaskId(pub u32); // TOD
 pub struct RenderTaskAddress(pub u32);
 
 #[derive(Debug)]
 pub struct RenderTaskTree {
     pub tasks: Vec<RenderTask>,
     pub task_data: Vec<RenderTaskData>,
 }
 
-pub type ClipChain = Option<Rc<ClipChainNode>>;
+pub type ClipChainNodeRef = Option<Rc<ClipChainNode>>;
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct ClipChainNode {
     pub work_item: ClipWorkItem,
     pub local_clip_rect: LayerRect,
     pub screen_outer_rect: DeviceIntRect,
     pub screen_inner_rect: DeviceIntRect,
+    pub prev: ClipChainNodeRef,
+}
+
+#[derive(Debug, Clone)]
+pub struct ClipChain {
     pub combined_outer_screen_rect: DeviceIntRect,
     pub combined_inner_screen_rect: DeviceIntRect,
-    pub prev: ClipChain,
+    pub nodes: ClipChainNodeRef,
 }
 
-impl ClipChainNode {
-    pub fn new(
+impl ClipChain {
+    pub fn empty(screen_rect: &DeviceIntRect) -> ClipChain {
+        ClipChain {
+            combined_inner_screen_rect: *screen_rect,
+            combined_outer_screen_rect: *screen_rect,
+            nodes: None,
+        }
+    }
+
+    pub fn new_with_added_node(
+        &self,
         work_item: ClipWorkItem,
         local_clip_rect: LayerRect,
         screen_outer_rect: DeviceIntRect,
         screen_inner_rect: DeviceIntRect,
-        parent_chain: ClipChain,
-    ) -> ClipChainNode {
-        let mut node = ClipChainNode {
+    ) -> ClipChain {
+        let new_node = ClipChainNode {
             work_item,
             local_clip_rect,
             screen_outer_rect,
             screen_inner_rect,
-            combined_outer_screen_rect: screen_outer_rect,
-            combined_inner_screen_rect: screen_inner_rect,
             prev: None,
         };
-        node.set_parent(parent_chain);
-        node
+
+        let mut new_chain = self.clone();
+        new_chain.add_node(new_node);
+        new_chain
     }
 
-    fn set_parent(&mut self, new_parent: ClipChain) {
-        self.prev = new_parent.clone();
-
-        let parent_node = match new_parent {
-            Some(ref parent_node) => parent_node,
-            None => return,
-        };
+    pub fn add_node(&mut self, mut new_node: ClipChainNode) {
+        new_node.prev = self.nodes.clone();
 
         // If this clip's outer rectangle is completely enclosed by the clip
         // chain's inner rectangle, then the only clip that matters from this point
         // on is this clip. We can disconnect this clip from the parent clip chain.
-        if parent_node.combined_inner_screen_rect.contains_rect(&self.screen_outer_rect) {
-            self.prev = None;
+        if self.combined_inner_screen_rect.contains_rect(&new_node.screen_outer_rect) {
+            new_node.prev = None;
         }
 
         self.combined_outer_screen_rect =
-            parent_node.combined_outer_screen_rect.intersection(&self.screen_outer_rect)
+            self.combined_outer_screen_rect.intersection(&new_node.screen_outer_rect)
             .unwrap_or_else(DeviceIntRect::zero);
         self.combined_inner_screen_rect =
-            parent_node.combined_inner_screen_rect.intersection(&self.screen_inner_rect)
+            self.combined_inner_screen_rect.intersection(&new_node.screen_inner_rect)
             .unwrap_or_else(DeviceIntRect::zero);
+
+        self.nodes = Some(Rc::new(new_node));
     }
 }
 
 pub struct ClipChainNodeIter {
-    pub current: ClipChain,
+    pub current: ClipChainNodeRef,
 }
 
 impl Iterator for ClipChainNodeIter {
     type Item = Rc<ClipChainNode>;
 
-    fn next(&mut self) -> ClipChain {
+    fn next(&mut self) -> ClipChainNodeRef {
         let previous = self.current.clone();
         self.current = match self.current {
             Some(ref item) => item.prev.clone(),
             None => return None,
         };
         previous
     }
 }
@@ -145,17 +159,18 @@ impl RenderTaskTree {
             self.assign_to_passes(*child, pass_index - 1, passes);
         }
 
         // Sanity check - can be relaxed if needed
         match task.location {
             RenderTaskLocation::Fixed => {
                 debug_assert!(pass_index == passes.len() - 1);
             }
-            RenderTaskLocation::Dynamic(..) => {
+            RenderTaskLocation::Dynamic(..) |
+            RenderTaskLocation::TextureCache(..) => {
                 debug_assert!(pass_index < passes.len() - 1);
             }
         }
 
         // If this task can be shared between multiple
         // passes, render it in the first pass so that
         // it is available to all subsequent passes.
         let pass_index = if task.is_shared() {
@@ -165,20 +180,17 @@ impl RenderTaskTree {
             pass_index
         };
 
         let pass = &mut passes[pass_index];
         pass.add_render_task(id, task.get_dynamic_size(), task.target_kind());
     }
 
     pub fn get_task_address(&self, id: RenderTaskId) -> RenderTaskAddress {
-        match self[id].kind {
-            RenderTaskKind::Alias(alias_id) => RenderTaskAddress(alias_id.0),
-            _ => RenderTaskAddress(id.0),
-        }
+        RenderTaskAddress(id.0)
     }
 
     pub fn build(&mut self) {
         for task in &mut self.tasks {
             self.task_data.push(task.write_task_data());
         }
     }
 }
@@ -191,29 +203,21 @@ impl ops::Index<RenderTaskId> for Render
 }
 
 impl ops::IndexMut<RenderTaskId> for RenderTaskTree {
     fn index_mut(&mut self, id: RenderTaskId) -> &mut RenderTask {
         &mut self.tasks[id.0 as usize]
     }
 }
 
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum RenderTaskKey {
-    /// Draw the alpha mask for a shared clip.
-    CacheMask(ClipId),
-    CacheScaling(BoxShadowCacheKey, DeviceIntSize),
-    CacheBlur(BoxShadowCacheKey, i32),
-    CachePicture(BoxShadowCacheKey),
-}
-
 #[derive(Debug)]
 pub enum RenderTaskLocation {
     Fixed,
     Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
+    TextureCache(SourceTexture, i32, DeviceIntRect),
 }
 
 #[derive(Debug, Clone)]
 pub struct ClipWorkItem {
     pub scroll_node_data_index: ClipScrollNodeIndex,
     pub clip_sources: ClipSourcesWeakHandle,
     pub coordinate_system_id: CoordinateSystemId,
 }
@@ -233,124 +237,110 @@ pub struct PictureTask {
     pub color: PremultipliedColorF,
     pub pic_type: PictureType,
 }
 
 #[derive(Debug)]
 pub struct BlurTask {
     pub blur_std_deviation: f32,
     pub target_kind: RenderTargetKind,
-    pub regions: Vec<LayerRect>,
     pub color: PremultipliedColorF,
     pub scale_factor: f32,
 }
 
 impl BlurTask {
     #[cfg(feature = "debugger")]
     fn print_with<T: PrintTreePrinter>(&self, pt: &mut T) {
         pt.add_item(format!("std deviation: {}", self.blur_std_deviation));
         pt.add_item(format!("target: {:?}", self.target_kind));
         pt.add_item(format!("scale: {}", self.scale_factor));
-        for region in &self.regions {
-            pt.add_item(format!("region {:?}", region));
-        }
     }
 }
 
 #[derive(Debug)]
 pub struct RenderTaskData {
     pub data: [f32; FLOATS_PER_RENDER_TASK_INFO],
 }
 
 #[derive(Debug)]
 pub enum RenderTaskKind {
     Picture(PictureTask),
     CacheMask(CacheMaskTask),
     VerticalBlur(BlurTask),
     HorizontalBlur(BlurTask),
     Readback(DeviceIntRect),
-    Alias(RenderTaskId),
     Scaling(RenderTargetKind),
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum ClearMode {
     // Applicable to color and alpha targets.
     Zero,
     One,
 
     // Applicable to color targets only.
     Transparent,
 }
 
 #[derive(Debug)]
 pub struct RenderTask {
-    pub cache_key: Option<RenderTaskKey>,
     pub location: RenderTaskLocation,
     pub children: Vec<RenderTaskId>,
     pub kind: RenderTaskKind,
     pub clear_mode: ClearMode,
     pub pass_index: Option<RenderPassIndex>,
 }
 
 impl RenderTask {
     pub fn new_picture(
         size: Option<DeviceIntSize>,
         prim_index: PrimitiveIndex,
         target_kind: RenderTargetKind,
         content_origin: ContentOrigin,
         color: PremultipliedColorF,
         clear_mode: ClearMode,
         children: Vec<RenderTaskId>,
-        box_shadow_cache_key: Option<BoxShadowCacheKey>,
         pic_type: PictureType,
     ) -> Self {
         let location = match size {
             Some(size) => RenderTaskLocation::Dynamic(None, size),
             None => RenderTaskLocation::Fixed,
         };
 
         RenderTask {
-            cache_key: match box_shadow_cache_key {
-                Some(key) => Some(RenderTaskKey::CachePicture(key)),
-                None => None,
-            },
             children,
             location,
             kind: RenderTaskKind::Picture(PictureTask {
                 prim_index,
                 target_kind,
                 content_origin,
                 color,
                 pic_type,
             }),
             clear_mode,
             pass_index: None,
         }
     }
 
     pub fn new_readback(screen_rect: DeviceIntRect) -> Self {
         RenderTask {
-            cache_key: None,
             children: Vec::new(),
             location: RenderTaskLocation::Dynamic(None, screen_rect.size),
             kind: RenderTaskKind::Readback(screen_rect),
             clear_mode: ClearMode::Transparent,
             pass_index: None,
         }
     }
 
     pub fn new_mask(
-        key: Option<ClipId>,
         outer_rect: DeviceIntRect,
         clips: Vec<ClipWorkItem>,
         prim_coordinate_system_id: CoordinateSystemId,
     ) -> RenderTask {
         RenderTask {
-            cache_key: key.map(RenderTaskKey::CacheMask),
             children: Vec::new(),
             location: RenderTaskLocation::Dynamic(None, outer_rect.size),
             kind: RenderTaskKind::CacheMask(CacheMaskTask {
                 actual_rect: outer_rect,
                 clips,
                 coordinate_system_id: prim_coordinate_system_id,
             }),
             clear_mode: ClearMode::One,
@@ -376,21 +366,19 @@ impl RenderTask {
     //           |
     //           +---- This is stored as the input task to the primitive shader.
     //
     pub fn new_blur(
         blur_std_deviation: f32,
         src_task_id: RenderTaskId,
         render_tasks: &mut RenderTaskTree,
         target_kind: RenderTargetKind,
-        regions: &[LayerRect],
         clear_mode: ClearMode,
         color: PremultipliedColorF,
-        box_shadow_cache_key: Option<BoxShadowCacheKey>,
-    ) -> Self {
+    ) -> (Self, f32) {
         // Adjust large std deviation value.
         let mut adjusted_blur_std_deviation = blur_std_deviation;
         let blur_target_size = render_tasks[src_task_id].get_dynamic_size();
         let mut adjusted_blur_target_size = blur_target_size;
         let mut downscaling_src_task_id = src_task_id;
         let mut scale_factor = 1.0;
         while adjusted_blur_std_deviation > MAX_BLUR_STD_DEVIATION {
             if adjusted_blur_target_size.width < MIN_DOWNSCALING_RT_SIZE ||
@@ -399,74 +387,58 @@ impl RenderTask {
             }
             adjusted_blur_std_deviation *= 0.5;
             scale_factor *= 2.0;
             adjusted_blur_target_size = (blur_target_size.to_f32() / scale_factor).to_i32();
             let downscaling_task = RenderTask::new_scaling(
                 target_kind,
                 downscaling_src_task_id,
                 adjusted_blur_target_size,
-                box_shadow_cache_key,
             );
             downscaling_src_task_id = render_tasks.add(downscaling_task);
         }
         scale_factor = blur_target_size.width as f32 / adjusted_blur_target_size.width as f32;
 
         let blur_task_v = RenderTask {
-            cache_key: match box_shadow_cache_key {
-                Some(key) => Some(RenderTaskKey::CacheBlur(key, 0)),
-                None => None,
-            },
             children: vec![downscaling_src_task_id],
             location: RenderTaskLocation::Dynamic(None, adjusted_blur_target_size),
             kind: RenderTaskKind::VerticalBlur(BlurTask {
                 blur_std_deviation: adjusted_blur_std_deviation,
                 target_kind,
-                regions: regions.to_vec(),
                 color,
                 scale_factor,
             }),
             clear_mode,
             pass_index: None,
         };
 
         let blur_task_v_id = render_tasks.add(blur_task_v);
 
         let blur_task_h = RenderTask {
-            cache_key: match box_shadow_cache_key {
-                Some(key) => Some(RenderTaskKey::CacheBlur(key, 1)),
-                None => None,
-            },
             children: vec![blur_task_v_id],
             location: RenderTaskLocation::Dynamic(None, adjusted_blur_target_size),
             kind: RenderTaskKind::HorizontalBlur(BlurTask {
                 blur_std_deviation: adjusted_blur_std_deviation,
                 target_kind,
-                regions: regions.to_vec(),
                 color,
                 scale_factor,
             }),
             clear_mode,
             pass_index: None,
         };
 
-        blur_task_h
+        (blur_task_h, scale_factor)
     }
 
     pub fn new_scaling(
         target_kind: RenderTargetKind,
         src_task_id: RenderTaskId,
         target_size: DeviceIntSize,
-        box_shadow_cache_key: Option<BoxShadowCacheKey>,
     ) -> Self {
         RenderTask {
-            cache_key: match box_shadow_cache_key {
-                Some(key) => Some(RenderTaskKey::CacheScaling(key, target_size)),
-                None => None,
-            },
             children: vec![src_task_id],
             location: RenderTaskLocation::Dynamic(None, target_size),
             kind: RenderTaskKind::Scaling(target_kind),
             clear_mode: match target_kind {
                 RenderTargetKind::Color => ClearMode::Transparent,
                 RenderTargetKind::Alpha => ClearMode::One,
             },
             pass_index: None,
@@ -522,18 +494,17 @@ impl RenderTask {
                         task.blur_std_deviation,
                         task.scale_factor,
                         0.0,
                     ],
                     task.color.to_array()
                 )
             }
             RenderTaskKind::Readback(..) |
-            RenderTaskKind::Scaling(..) |
-            RenderTaskKind::Alias(..) => {
+            RenderTaskKind::Scaling(..) => {
                 (
                     [0.0; 3],
                     [0.0; 4],
                 )
             }
         };
 
         let (target_rect, target_index) = self.get_target_rect();
@@ -555,16 +526,17 @@ impl RenderTask {
             ]
         }
     }
 
     pub fn get_dynamic_size(&self) -> DeviceIntSize {
         match self.location {
             RenderTaskLocation::Fixed => DeviceIntSize::zero(),
             RenderTaskLocation::Dynamic(_, size) => size,
+            RenderTaskLocation::TextureCache(_, _, rect) => rect.size,
         }
     }
 
     pub fn get_target_rect(&self) -> (DeviceIntRect, RenderTargetIndex) {
         match self.location {
             RenderTaskLocation::Fixed => {
                 (DeviceIntRect::zero(), RenderTargetIndex(0))
             }
@@ -583,16 +555,19 @@ impl RenderTask {
             //           to mark a task as unused explicitly. This
             //           would allow us to restore this debug check.
             RenderTaskLocation::Dynamic(Some((origin, target_index)), size) => {
                 (DeviceIntRect::new(origin, size), target_index)
             }
             RenderTaskLocation::Dynamic(None, _) => {
                 (DeviceIntRect::zero(), RenderTargetIndex(0))
             }
+            RenderTaskLocation::TextureCache(_, layer, rect) => {
+                (rect, RenderTargetIndex(layer as usize))
+            }
         }
     }
 
     pub fn target_kind(&self) -> RenderTargetKind {
         match self.kind {
             RenderTaskKind::Readback(..) => RenderTargetKind::Color,
 
             RenderTaskKind::CacheMask(..) => {
@@ -606,20 +581,16 @@ impl RenderTask {
 
             RenderTaskKind::Scaling(target_kind) => {
                 target_kind
             }
 
             RenderTaskKind::Picture(ref task_info) => {
                 task_info.target_kind
             }
-
-            RenderTaskKind::Alias(..) => {
-                panic!("BUG: target_kind() called on invalidated task");
-            }
         }
     }
 
     // Check if this task wants to be made available as an input
     // to all passes (except the first) in the render task tree.
     // To qualify for this, the task needs to have no children / dependencies.
     // Currently, this is only supported for A8 targets, but it can be
     // trivially extended to also support RGBA8 targets in the future
@@ -628,20 +599,16 @@ impl RenderTask {
         match self.kind {
             RenderTaskKind::Picture(..) |
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::HorizontalBlur(..) |
             RenderTaskKind::Scaling(..) => false,
 
             RenderTaskKind::CacheMask(..) => true,
-
-            RenderTaskKind::Alias(..) => {
-                panic!("BUG: is_shared() called on aliased task");
-            }
         }
     }
 
     #[cfg(feature = "debugger")]
     pub fn print_with<T: PrintTreePrinter>(&self, pt: &mut T, tree: &RenderTaskTree) -> bool {
         match self.kind {
             RenderTaskKind::Picture(ref task) => {
                 pt.new_level(format!("Picture of {:?}", task.prim_index));
@@ -662,26 +629,150 @@ 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::Alias(ref alias_id) => {
-                pt.add_item(format!("Alias of {:?}", alias_id));
-                return false
-            }
         }
 
         pt.add_item(format!("clear to: {:?}", self.clear_mode));
 
         for &child_id in &self.children {
             if tree[child_id].print_with(pt, tree) {
                 pt.add_item(format!("self: {:?}", child_id))
             }
         }
 
         pt.end_level();
         true
     }
 }
+
+#[derive(Debug, Hash, PartialEq, Eq)]
+pub enum RenderTaskCacheKeyKind {
+    BoxShadow(BoxShadowCacheKey),
+}
+
+#[derive(Debug, Hash, PartialEq, Eq)]
+pub struct RenderTaskCacheKey {
+    pub size: DeviceIntSize,
+    pub kind: RenderTaskCacheKeyKind,
+}
+
+struct RenderTaskCacheEntry {
+    handle: TextureCacheHandle,
+}
+
+// A cache of render tasks that are stored in the texture
+// cache for usage across frames.
+pub struct RenderTaskCache {
+    entries: FastHashMap<RenderTaskCacheKey, RenderTaskCacheEntry>,
+}
+
+impl RenderTaskCache {
+    pub fn new() -> RenderTaskCache {
+        RenderTaskCache {
+            entries: FastHashMap::default(),
+        }
+    }
+
+    pub fn clear(&mut self) {
+        self.entries.clear();
+    }
+
+    pub fn begin_frame(
+        &mut self,
+        texture_cache: &mut TextureCache,
+    ) {
+        // Drop any items from the cache that have been
+        // evicted from the texture cache.
+        //
+        // This isn't actually necessary for the texture
+        // cache to be able to evict old render tasks.
+        // It will evict render tasks as required, since
+        // the access time in the texture cache entry will
+        // be stale if this task hasn't been requested
+        // for a while.
+        //
+        // Nonetheless, we should remove stale entries
+        // from here so that this hash map doesn't
+        // grow indefinitely!
+        self.entries.retain(|_, value| {
+            texture_cache.is_allocated(&value.handle)
+        });
+    }
+
+    pub fn request_render_task<F>(
+        &mut self,
+        key: RenderTaskCacheKey,
+        texture_cache: &mut TextureCache,
+        gpu_cache: &mut GpuCache,
+        render_tasks: &mut RenderTaskTree,
+        mut f: F,
+    ) -> CacheItem where F: FnMut(&mut RenderTaskTree) -> (RenderTaskId, [f32; 3]) {
+        // Get the texture cache handle for this cache key,
+        // or create one.
+        let cache_entry = self.entries
+                              .entry(key)
+                              .or_insert(RenderTaskCacheEntry {
+                                  handle: TextureCacheHandle::new(),
+                              });
+
+        // Check if this texture cache handle is valie.
+        if texture_cache.request(&mut cache_entry.handle, gpu_cache) {
+            // Invoke user closure to get render task chain
+            // to draw this into the texture cache.
+            let (render_task_id, user_data) = f(render_tasks);
+            let render_task = &mut render_tasks[render_task_id];
+
+            // Find out what size to alloc in the texture cache.
+            let size = match render_task.location {
+                RenderTaskLocation::Fixed |
+                RenderTaskLocation::TextureCache(..) => {
+                    panic!("BUG: dynamic task was expected");
+                }
+                RenderTaskLocation::Dynamic(_, size) => size,
+            };
+
+            // TODO(gw): Support color tasks in the texture cache,
+            //           and perhaps consider if we can determine
+            //           if some tasks are opaque as an optimization.
+            let descriptor = ImageDescriptor::new(
+                size.width as u32,
+                size.height as u32,
+                ImageFormat::R8,
+                false,
+            );
+
+            // Allocate space in the texture cache, but don't supply
+            // and CPU-side data to be uploaded.
+            texture_cache.update(
+                &mut cache_entry.handle,
+                descriptor,
+                TextureFilter::Linear,
+                None,
+                user_data,
+                None,
+                gpu_cache,
+            );
+
+            // Get the allocation details in the texture cache, and store
+            // this in the render task. The renderer will draw this
+            // task into the appropriate layer and rect of the texture
+            // cache on this frame.
+            let (texture_id, texture_layer, uv_rect) =
+                texture_cache.get_cache_location(&cache_entry.handle);
+
+            render_task.location = RenderTaskLocation::TextureCache(
+                texture_id,
+                texture_layer,
+                uv_rect.to_i32()
+            );
+        }
+
+        // Finally, return the texture cache handle that we know
+        // is now up to date.
+        texture_cache.get(&cache_entry.handle)
+    }
+}
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -61,17 +61,17 @@ use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::Arc;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
 use texture_cache::TextureCache;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use tiling::{AlphaRenderTarget, ColorRenderTarget};
 use tiling::{RenderPass, RenderPassKind, RenderTargetList};
-use tiling::{Frame, RenderTarget, ScalingInfo};
+use tiling::{Frame, RenderTarget, ScalingInfo, TextureCacheRenderTarget};
 use time::precise_time_ns;
 use util::TransformedRectKind;
 
 pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024;
 /// Enabling this toggle would force the GPU cache scattered texture to
 /// be resized every frame, which enables GPU debuggers to see if this
 /// is performed correctly.
 const GPU_CACHE_RESIZE_TEST: bool = false;
@@ -98,20 +98,16 @@ const GPU_TAG_BRUSH_LINE: GpuProfileTag 
 const GPU_TAG_CACHE_CLIP: GpuProfileTag = GpuProfileTag {
     label: "C_Clip",
     color: debug_colors::PURPLE,
 };
 const GPU_TAG_CACHE_TEXT_RUN: GpuProfileTag = GpuProfileTag {
     label: "C_TextRun",
     color: debug_colors::MISTYROSE,
 };
-const GPU_TAG_CACHE_LINE: GpuProfileTag = GpuProfileTag {
-    label: "C_Line",
-    color: debug_colors::BROWN,
-};
 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,
 };
@@ -408,21 +404,16 @@ const DESC_BLUR: VertexDescriptor = Vert
             count: 1,
             kind: VertexAttributeKind::I32,
         },
         VertexAttribute {
             name: "aBlurDirection",
             count: 1,
             kind: VertexAttributeKind::I32,
         },
-        VertexAttribute {
-            name: "aBlurRegion",
-            count: 4,
-            kind: VertexAttributeKind::F32
-        },
     ],
 };
 
 const DESC_CLIP: VertexDescriptor = VertexDescriptor {
     vertex_attributes: &[
         VertexAttribute {
             name: "aPosition",
             count: 2,
@@ -764,16 +755,17 @@ impl SourceTextureResolver {
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[allow(dead_code)] // SubpixelVariableTextColor is not used at the moment.
 pub enum BlendMode {
     None,
+    Alpha,
     PremultipliedAlpha,
     PremultipliedDestOut,
     SubpixelDualSource,
     SubpixelConstantTextColor(ColorF),
     SubpixelWithBgColor,
     SubpixelVariableTextColor,
 }
 
@@ -1294,16 +1286,17 @@ impl BrushShader {
         projection: &Transform3D<f32>,
         mode: M,
         renderer_errors: &mut Vec<RendererError>,
     ) where M: Into<ShaderMode> {
         match blend_mode {
             BlendMode::None => {
                 self.opaque.bind(device, projection, mode, renderer_errors)
             }
+            BlendMode::Alpha |
             BlendMode::PremultipliedAlpha |
             BlendMode::PremultipliedDestOut |
             BlendMode::SubpixelDualSource |
             BlendMode::SubpixelConstantTextColor(..) |
             BlendMode::SubpixelVariableTextColor |
             BlendMode::SubpixelWithBgColor => {
                 self.alpha.bind(device, projection, mode, renderer_errors)
             }
@@ -2094,17 +2087,17 @@ impl Renderer {
                 21,
             ];
 
             let mut texture = device.create_texture(TextureTarget::Default);
             device.init_texture(
                 &mut texture,
                 8,
                 8,
-                ImageFormat::A8,
+                ImageFormat::R8,
                 TextureFilter::Nearest,
                 None,
                 1,
                 Some(&dither_matrix),
             );
 
             Some(texture)
         } else {
@@ -2548,21 +2541,16 @@ impl Renderer {
         );
         for (_, batch) in &target.alpha_batcher.text_run_cache_prims {
             debug_target.add(
                 debug_server::BatchKind::Cache,
                 "Text Shadow",
                 batch.len(),
             );
         }
-        debug_target.add(
-            debug_server::BatchKind::Cache,
-            "Lines",
-            target.alpha_batcher.line_cache_prims.len(),
-        );
 
         for batch in target
             .alpha_batcher
             .batch_list
             .opaque_batch_list
             .batches
             .iter()
             .rev()
@@ -2581,29 +2569,43 @@ impl Renderer {
                 batch.instances.len(),
             );
         }
 
         debug_target
     }
 
     #[cfg(feature = "debugger")]
+    fn debug_texture_cache_target(target: &TextureCacheRenderTarget) -> debug_server::Target {
+        let mut debug_target = debug_server::Target::new("Texture Cache");
+
+        debug_target.add(
+            debug_server::BatchKind::Cache,
+            "Horizontal Blur",
+            target.horizontal_blurs.len(),
+        );
+
+        debug_target
+    }
+
+    #[cfg(feature = "debugger")]
     fn get_passes_for_debugger(&self) -> String {
         let mut debug_passes = debug_server::PassList::new();
 
         for &(_, ref render_doc) in &self.active_documents {
             for pass in &render_doc.frame.passes {
                 let mut debug_targets = Vec::new();
                 match pass.kind {
                     RenderPassKind::MainFramebuffer(ref target) => {
                         debug_targets.push(Self::debug_color_target(target));
                     }
-                    RenderPassKind::OffScreen { ref alpha, ref color } => {
+                    RenderPassKind::OffScreen { ref alpha, ref color, ref texture_cache } => {
                         debug_targets.extend(alpha.targets.iter().map(Self::debug_alpha_target));
                         debug_targets.extend(color.targets.iter().map(Self::debug_color_target));
+                        debug_targets.extend(texture_cache.iter().map(|(_, target)| Self::debug_texture_cache_target(target)))
                     }
                 }
 
                 debug_passes.add(debug_server::Pass { targets: debug_targets });
             }
         }
 
         serde_json::to_string(&debug_passes).unwrap()
@@ -3439,37 +3441,16 @@ impl Renderer {
                 self.draw_instanced_batch(
                     instances,
                     VertexArrayKind::Primitive,
                     &BatchTextures::color(*texture_id),
                     stats,
                 );
             }
         }
-        if !target.alpha_batcher.line_cache_prims.is_empty() {
-            // TODO(gw): Technically, we don't need blend for solid
-            //           lines. We could check that here?
-            self.device.set_blend(true);
-            self.device.set_blend_mode_premultiplied_alpha();
-
-            let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_LINE);
-            self.brush_line.bind(
-                &mut self.device,
-                BlendMode::PremultipliedAlpha,
-                projection,
-                0,
-                &mut self.renderer_errors,
-            );
-            self.draw_instanced_batch(
-                &target.alpha_batcher.line_cache_prims,
-                VertexArrayKind::Primitive,
-                &BatchTextures::no_texture(),
-                stats,
-            );
-        }
 
         //TODO: record the pixel count for cached primitives
 
         if !target.alpha_batcher.is_empty() {
             let _gl = self.gpu_profile.start_marker("alpha batches");
             self.device.set_blend(false);
             let mut prev_blend_mode = BlendMode::None;
 
@@ -3507,16 +3488,17 @@ impl Renderer {
             }
 
             let transparent_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_TRANSPARENT);
 
             for batch in &target.alpha_batcher.batch_list.alpha_batch_list.batches {
                 if self.debug_flags.contains(DebugFlags::ALPHA_PRIM_DBG) {
                     let color = match batch.key.blend_mode {
                         BlendMode::None => debug_colors::BLACK,
+                        BlendMode::Alpha |
                         BlendMode::PremultipliedAlpha => debug_colors::GREY,
                         BlendMode::PremultipliedDestOut => debug_colors::SALMON,
                         BlendMode::SubpixelConstantTextColor(..) => debug_colors::GREEN,
                         BlendMode::SubpixelVariableTextColor => debug_colors::RED,
                         BlendMode::SubpixelWithBgColor => debug_colors::BLUE,
                         BlendMode::SubpixelDualSource => debug_colors::YELLOW,
                     }.into();
                     for item_rect in &batch.item_rects {
@@ -3533,16 +3515,17 @@ impl Renderer {
                         // 1) Use dual source blending where available (almost all recent hardware).
                         // 2) Use frame buffer fetch where available (most modern hardware).
                         // 3) Consider the old constant color blend method where no clip is applied.
                         let _timer = self.gpu_profile.start_timer(GPU_TAG_PRIM_TEXT_RUN);
 
                         self.device.set_blend(true);
 
                         match batch.key.blend_mode {
+                            BlendMode::Alpha => panic!("Attempt to composite non-premultiplied text primitives."),
                             BlendMode::PremultipliedAlpha => {
                                 self.device.set_blend_mode_premultiplied_alpha();
 
                                 self.ps_text_run.bind(
                                     &mut self.device,
                                     glyph_format,
                                     transform_kind,
                                     projection,
@@ -3701,16 +3684,20 @@ impl Renderer {
                         self.device.set_blend(false);
                     }
                     _ => {
                         if batch.key.blend_mode != prev_blend_mode {
                             match batch.key.blend_mode {
                                 BlendMode::None => {
                                     self.device.set_blend(false);
                                 }
+                                BlendMode::Alpha => {
+                                    self.device.set_blend(true);
+                                    self.device.set_blend_mode_alpha();
+                                }
                                 BlendMode::PremultipliedAlpha => {
                                     self.device.set_blend(true);
                                     self.device.set_blend_mode_premultiplied_alpha();
                                 }
                                 BlendMode::PremultipliedDestOut => {
                                     self.device.set_blend(true);
                                     self.device.set_blend_mode_premultiplied_dest_out();
                                 }
@@ -3958,16 +3945,61 @@ impl Renderer {
                     stats,
                 );
             }
         }
 
         self.gpu_profile.finish_sampler(alpha_sampler);
     }
 
+    fn draw_texture_cache_target(
+        &mut self,
+        texture: &SourceTexture,
+        layer: i32,
+        target: &TextureCacheRenderTarget,
+        stats: &mut RendererStats,
+    ) {
+        let projection = {
+            let texture = self.texture_resolver
+                .resolve(texture)
+                .expect("BUG: invalid target texture");
+            let target_size = texture.get_dimensions();
+
+            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);
+
+            Transform3D::ortho(
+                0.0,
+                target_size.width as f32,
+                0.0,
+                target_size.height as f32,
+                ORTHO_NEAR_PLANE,
+                ORTHO_FAR_PLANE,
+            )
+        };
+
+        // Draw any blurs for this target.
+        if !target.horizontal_blurs.is_empty() {
+            let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
+
+            self.cs_blur_a8
+                .bind(&mut self.device, &projection, 0, &mut self.renderer_errors);
+
+            self.draw_instanced_batch(
+                &target.horizontal_blurs,
+                VertexArrayKind::Blur,
+                &BatchTextures::no_texture(),
+                stats,
+            );
+        }
+    }
+
     fn update_deferred_resolves(&mut self, frame: &Frame) -> Option<GpuCacheUpdateList> {
         // The first thing we do is run through any pending deferred
         // resolves, and use a callback to get the UV rect for this
         // custom item. Then we patch the resource_rects structure
         // here before it's uploaded to the GPU.
         if frame.deferred_resolves.is_empty() {
             return None;
         }
@@ -4077,16 +4109,17 @@ impl Renderer {
                     }
                 });
             match index {
                 Some(pos) => self.texture_resolver.render_target_pool.swap_remove(pos),
                 None => return,
             }
         } else {
             if list.texture.is_some() {
+                list.check_ready();
                 return
             }
             match self.texture_resolver.render_target_pool.pop() {
                 Some(texture) => texture,
                 None => self.device.create_texture(TextureTarget::Array),
             }
         };
 
@@ -4098,37 +4131,38 @@ impl Renderer {
             TextureFilter::Linear,
             Some(RenderTargetInfo {
                 has_depth: list.needs_depth(),
             }),
             list.targets.len() as _,
             None,
         );
         list.texture = Some(texture);
+        list.check_ready();
     }
 
     fn prepare_tile_frame(&mut self, frame: &mut Frame) {
         // Init textures and render targets to match this scene.
         // First pass grabs all the perfectly matching targets from the pool.
         for pass in &mut frame.passes {
-            if let RenderPassKind::OffScreen { ref mut alpha, ref mut color } = pass.kind {
+            if let RenderPassKind::OffScreen { ref mut alpha, ref mut color, .. } = pass.kind {
                 self.prepare_target_list(alpha, true);
                 self.prepare_target_list(color, true);
             }
         }
     }
 
     fn bind_frame_data(&mut self, frame: &mut Frame) {
         let _timer = self.gpu_profile.start_timer(GPU_TAG_SETUP_DATA);
         self.device.set_device_pixel_ratio(frame.device_pixel_ratio);
 
         // Some of the textures are already assigned by `prepare_frame`.
         // Now re-allocate the space for the rest of the target textures.
         for pass in &mut frame.passes {
-            if let RenderPassKind::OffScreen { ref mut alpha, ref mut color } = pass.kind {
+            if let RenderPassKind::OffScreen { ref mut alpha, ref mut color, .. } = pass.kind {
                 self.prepare_target_list(alpha, false);
                 self.prepare_target_list(color, false);
             }
         }
 
         self.node_data_texture.update(&mut self.device, &mut frame.node_data);
         self.device.bind_texture(TextureSampler::ClipScrollNodes, &self.node_data_texture.texture);
 
@@ -4211,19 +4245,27 @@ impl Renderer {
                         &frame.render_tasks,
                         &projection,
                         frame_id,
                         stats,
                     );
 
                     (None, None)
                 }
-                RenderPassKind::OffScreen { ref mut alpha, ref mut color } => {
-                    assert!(alpha.targets.is_empty() || alpha.texture.is_some());
-                    assert!(color.targets.is_empty() || color.texture.is_some());
+                RenderPassKind::OffScreen { ref mut alpha, ref mut color, ref mut texture_cache } => {
+                    alpha.check_ready();
+                    color.check_ready();
+                    for (&(texture_id, target_index), target) in texture_cache {
+                        self.draw_texture_cache_target(
+                            &texture_id,
+                            target_index,
+                            target,
+                            stats,
+                        );
+                    }
 
                     for (target_index, target) in alpha.targets.iter().enumerate() {
                         stats.alpha_target_count += 1;
 
                         let projection = Transform3D::ortho(
                             0.0,
                             alpha.max_size.width as f32,
                             0.0,
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_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/. */
 
 use api::{AddFont, BlobImageData, BlobImageResources, ResourceUpdate, ResourceUpdates};
 use api::{BlobImageDescriptor, BlobImageError, BlobImageRenderer, BlobImageRequest};
-use api::ColorF;
-use api::{DevicePoint, DeviceUintRect, DeviceUintSize};
+use api::{ColorF, DevicePoint, DeviceUintRect, DeviceUintSize};
 use api::{Epoch, FontInstanceKey, FontKey, FontTemplate};
 use api::{ExternalImageData, ExternalImageType};
 use api::{FontInstanceOptions, FontInstancePlatformOptions, FontVariation};
 use api::{GlyphDimensions, GlyphKey, IdNamespace};
 use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering};
 use api::{TileOffset, TileSize};
 #[cfg(feature = "capture")]
 use api::{NativeFontHandle};
@@ -20,16 +19,17 @@ use frame::FrameId;
 use glyph_cache::GlyphCache;
 use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphRasterizer, GlyphRequest};
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use internal_types::{FastHashMap, FastHashSet, SourceTexture, TextureUpdateList};
 #[cfg(feature = "capture")]
 use internal_types::ExternalCaptureImage;
 use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
 use rayon::ThreadPool;
+use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId, RenderTaskTree};
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::cmp;
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::mem;
 #[cfg(feature = "capture")]
 use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
@@ -47,16 +47,17 @@ pub struct GlyphFetchResult {
 // They are converted to normalized ST
 // values in the vertex shader. The reason
 // for this is that the texture may change
 // dimensions (e.g. the pages in a texture
 // atlas can grow). When this happens, by
 // storing the coordinates as texel values
 // we don't need to go through and update
 // various CPU-side structures.
+#[derive(Debug)]
 pub struct CacheItem {
     pub texture_id: SourceTexture,
     pub uv_rect_handle: GpuCacheHandle,
 }
 
 #[derive(Debug)]
 pub struct ImageProperties {
     pub descriptor: ImageDescriptor,
@@ -214,16 +215,17 @@ impl BlobImageResources for Resources {
             .get(key)
             .map(|resource| (&resource.data, &resource.descriptor))
     }
 }
 
 pub struct ResourceCache {
     cached_glyphs: GlyphCache,
     cached_images: ImageCache,
+    cached_render_tasks: RenderTaskCache,
 
     resources: Resources,
     state: State,
     current_frame_id: FrameId,
 
     texture_cache: TextureCache,
 
     // TODO(gw): We should expire (parts of) this cache semi-regularly!
@@ -242,16 +244,17 @@ impl ResourceCache {
     pub fn new(
         texture_cache: TextureCache,
         workers: Arc<ThreadPool>,
         blob_image_renderer: Option<Box<BlobImageRenderer>>,
     ) -> Self {
         ResourceCache {
             cached_glyphs: GlyphCache::new(),
             cached_images: ResourceClassCache::new(),
+            cached_render_tasks: RenderTaskCache::new(),
             resources: Resources {
                 font_templates: FastHashMap::default(),
                 font_instances: Arc::new(RwLock::new(FastHashMap::default())),
                 image_templates: ImageTemplates::new(),
             },
             cached_glyph_dimensions: FastHashMap::default(),
             texture_cache,
             state: State::Idle,
@@ -273,16 +276,37 @@ impl ResourceCache {
             ImageData::External(info) => {
                 // External handles already represent existing textures so it does
                 // not make sense to tile them into smaller ones.
                 info.image_type == ExternalImageType::ExternalBuffer && size_check
             }
         }
     }
 
+    // Request the texture cache item for a cacheable render
+    // task. If the item is already cached, the texture cache
+    // handle will be returned. Otherwise, the user supplied
+    // closure will be invoked to generate the render task
+    // chain that is required to draw this task.
+    pub fn request_render_task<F>(
+        &mut self,
+        key: RenderTaskCacheKey,
+        gpu_cache: &mut GpuCache,
+        render_tasks: &mut RenderTaskTree,
+        f: F,
+    ) -> CacheItem where F: FnMut(&mut RenderTaskTree) -> (RenderTaskId, [f32; 3]) {
+        self.cached_render_tasks.request_render_task(
+            key,
+            &mut self.texture_cache,
+            gpu_cache,
+            render_tasks,
+            f
+        )
+    }
+
     pub fn update_resources(
         &mut self,
         updates: ResourceUpdates,
         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).
@@ -757,16 +781,17 @@ impl ResourceCache {
             })
             .collect()
     }
 
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         debug_assert_eq!(self.state, State::Idle);
         self.state = State::AddResources;
         self.texture_cache.begin_frame(frame_id);
+        self.cached_render_tasks.begin_frame(&mut self.texture_cache);
         self.current_frame_id = frame_id;
     }
 
     pub fn block_until_all_resources_added(
         &mut self,
         gpu_cache: &mut GpuCache,
         texture_cache_profile: &mut TextureCacheProfileCounters,
     ) {
@@ -866,17 +891,17 @@ impl ResourceCache {
                 image_template.descriptor.clone()
             };
 
             let entry = self.cached_images.get_mut(&request).as_mut().unwrap();
             self.texture_cache.update(
                 &mut entry.texture_cache_handle,
                 descriptor,
                 filter,
-                image_data,
+                Some(image_data),
                 [0.0; 3],
                 image_template.dirty_rect,
                 gpu_cache,
             );
             image_template.dirty_rect = None;
         }
     }
 
@@ -891,16 +916,17 @@ impl ResourceCache {
         // We may want to look into something less extreme, but on the other hand this
         // should only be used in situations where are running low enough on memory
         // that we risk crashing if we don't do something about it.
         // The advantage of clearing the cache completely is that it gets rid of any
         // remaining fragmentation that could have persisted if we kept around the most
         // recently used resources.
         self.cached_images.clear();
         self.cached_glyphs.clear();
+        self.cached_render_tasks.clear();
     }
 
     pub fn clear_namespace(&mut self, namespace: IdNamespace) {
         self.resources
             .image_templates
             .images
             .retain(|key, _| key.0 != namespace);
 
@@ -1122,16 +1148,17 @@ impl ResourceCache {
 
     pub fn load_capture(&mut self, resources: PlainResources, root: &PathBuf) {
         use std::fs::File;
         use std::io::Read;
 
         info!("loading resource cache");
         self.cached_glyphs.clear();
         self.cached_images.clear();
+        self.cached_render_tasks.clear();
 
         self.state = State::Idle;
         self.current_frame_id = FrameId(0);
 
         let max_texture_size = self.texture_cache.max_texture_size();
         self.texture_cache = TextureCache::new(max_texture_size);
 
         self.cached_glyph_dimensions.clear();
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -201,17 +201,17 @@ pub struct TextureCache {
     shared_entry_handles: Vec<FreeListHandle<CacheEntry>>,
 }
 
 impl TextureCache {
     pub fn new(max_texture_size: u32) -> Self {
         TextureCache {
             max_texture_size,
             array_a8_linear: TextureArray::new(
-                ImageFormat::A8,
+                ImageFormat::R8,
                 TextureFilter::Linear,
                 TEXTURE_ARRAY_LAYERS_LINEAR,
             ),
             array_rgba8_linear: TextureArray::new(
                 ImageFormat::BGRA8,
                 TextureFilter::Linear,
                 TEXTURE_ARRAY_LAYERS_LINEAR,
             ),
@@ -279,17 +279,17 @@ impl TextureCache {
     }
 
     // Update the data stored by a given texture cache handle.
     pub fn update(
         &mut self,
         handle: &mut TextureCacheHandle,
         descriptor: ImageDescriptor,
         filter: TextureFilter,
-        data: ImageData,
+        data: Option<ImageData>,
         user_data: [f32; 3],
         mut dirty_rect: Option<DeviceUintRect>,
         gpu_cache: &mut GpuCache,
     ) {
         // Determine if we need to allocate texture cache memory
         // for this item. We need to reallocate if any of the following
         // is true:
         // - Never been in the cache
@@ -332,56 +332,66 @@ impl TextureCache {
         gpu_cache.invalidate(&entry.uv_rect_handle);
 
         // Upload the resource rect and texture array layer.
         entry.update_gpu_cache(gpu_cache);
 
         // Create an update command, which the render thread processes
         // to upload the new image data into the correct location
         // in GPU memory.
-        let (layer_index, origin) = match entry.kind {
-            EntryKind::Standalone { .. } => (0, DeviceUintPoint::zero()),
-            EntryKind::Cache {
-                layer_index,
-                origin,
-                ..
-            } => (layer_index, origin),
-        };
+        if let Some(data) = data {
+            let (layer_index, origin) = match entry.kind {
+                EntryKind::Standalone { .. } => (0, DeviceUintPoint::zero()),
+                EntryKind::Cache {
+                    layer_index,
+                    origin,
+                    ..
+                } => (layer_index, origin),
+            };
 
-        let op = TextureUpdate::new_update(
-            data,
-            &descriptor,
-            origin,
-            entry.size,
-            entry.texture_id,
-            layer_index as i32,
-            dirty_rect,
-        );
-        self.pending_updates.push(op);
+            let op = TextureUpdate::new_update(
+                data,
+                &descriptor,
+                origin,
+                entry.size,
+                entry.texture_id,
+                layer_index as i32,
+                dirty_rect,
+            );
+            self.pending_updates.push(op);
+        }
     }
 
     // Get a specific region by index from a shared texture array.
     fn get_region_mut(&mut self,
         format: ImageFormat,
         filter: TextureFilter,
         region_index: u16
     ) -> &mut TextureRegion {
         let texture_array = match (format, filter) {
-            (ImageFormat::A8, TextureFilter::Linear) => &mut self.array_a8_linear,
+            (ImageFormat::R8, TextureFilter::Linear) => &mut self.array_a8_linear,
             (ImageFormat::BGRA8, TextureFilter::Linear) => &mut self.array_rgba8_linear,
             (ImageFormat::BGRA8, TextureFilter::Nearest) => &mut self.array_rgba8_nearest,
             (ImageFormat::Invalid, _) |
             (ImageFormat::RGBAF32, _) |
             (ImageFormat::RG8, _) |
-            (ImageFormat::A8, TextureFilter::Nearest) => unreachable!(),
+            (ImageFormat::R8, TextureFilter::Nearest) => unreachable!(),
         };
 
         &mut texture_array.regions[region_index as usize]
     }
 
+    // Check if a given texture handle has a valid allocation
+    // in the texture cache.
+    pub fn is_allocated(&self, handle: &TextureCacheHandle) -> bool {
+        handle.entry.as_ref().map_or(false, |handle| {
+            self.entries.get_opt(handle).is_some()
+        })
+    }
+
     // Retrieve the details of an item in the cache. This is used
     // during batch creation to provide the resource rect address
     // to the shaders and texture ID to the batching logic.
     // This function will asssert in debug modes if the caller
     // tries to get a handle that was not requested this frame.
     pub fn get(&self, handle: &TextureCacheHandle) -> CacheItem {
         match handle.entry {
             Some(ref handle) => {
@@ -393,16 +403,46 @@ impl TextureCache {
                     uv_rect_handle: entry.uv_rect_handle,
                     texture_id: SourceTexture::TextureCache(entry.texture_id),
                 }
             }
             None => panic!("BUG: handle not requested earlier in frame"),
         }
     }
 
+    // A more detailed version of get(). This allows access to the actual
+    // device rect of the cache allocation.
+    pub fn get_cache_location(
+        &self,
+        handle: &TextureCacheHandle,
+    ) -> (SourceTexture, i32, DeviceUintRect) {
+        let handle = handle
+            .entry
+            .as_ref()
+            .expect("BUG: handle not requested earlier in frame");
+
+        let entry = self.entries
+            .get_opt(handle)
+            .expect("BUG: was dropped from cache or not updated!");
+        debug_assert_eq!(entry.last_access, self.frame_id);
+        let (layer_index, origin) = match entry.kind {
+            EntryKind::Standalone { .. } => {
+                (0, DeviceUintPoint::zero())
+            }
+            EntryKind::Cache {
+                layer_index,
+                origin,
+                ..
+            } => (layer_index, origin),
+        };
+        (SourceTexture::TextureCache(entry.texture_id),
+         layer_index as i32,
+         DeviceUintRect::new(origin, entry.size))
+    }
+
     // Expire old standalone textures.
     fn expire_old_standalone_entries(&mut self) {
         let mut eviction_candidates = Vec::new();
         let mut retained_entries = Vec::new();
 
         // Build a list of eviction candidates (which are
         // anything not used this frame).
         for handle in self.standalone_entry_handles.drain(..) {
@@ -465,17 +505,17 @@ impl TextureCache {
         });
 
         // Doing an eviction is quite expensive, so we don't want to
         // do it all the time. To avoid this, try and evict a
         // significant number of items each cycle. However, we don't
         // want to evict everything we can, since that will result in
         // more items being uploaded than necessary.
         // Instead, we say we will keep evicting until both of these
-        // consitions are met:
+        // conditions are met:
         // - We have evicted some arbitrary number of items (512 currently).
         //   AND
         // - We have freed an item that will definitely allow us to
         //   fit the currently requested allocation.
         let needed_slab_size =
             SlabSize::new(required_alloc.width, required_alloc.height).get_size();
         let mut found_matching_slab = false;
         let mut freed_complete_page = false;
@@ -532,22 +572,22 @@ impl TextureCache {
     fn allocate_from_shared_cache(
         &mut self,
         descriptor: &ImageDescriptor,
         filter: TextureFilter,
         user_data: [f32; 3],
     ) -> Option<CacheEntry> {
         // Work out which cache it goes in, based on format.
         let texture_array = match (descriptor.format, filter) {
-            (ImageFormat::A8, TextureFilter::Linear) => &mut self.array_a8_linear,
+            (ImageFormat::R8, TextureFilter::Linear) => &mut self.array_a8_linear,
             (ImageFormat::BGRA8, TextureFilter::Linear) => &mut self.array_rgba8_linear,
             (ImageFormat::BGRA8, TextureFilter::Nearest) => &mut self.array_rgba8_nearest,
             (ImageFormat::Invalid, _) |
             (ImageFormat::RGBAF32, _) |
-            (ImageFormat::A8, TextureFilter::Nearest) |
+            (ImageFormat::R8, TextureFilter::Nearest) |
             (ImageFormat::RG8, _) => unreachable!(),
         };
 
         // Lazy initialize this texture array if required.
         if texture_array.texture_id.is_none() {
             let texture_id = self.cache_textures.allocate();
 
             let update_op = TextureUpdate {
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -9,46 +9,39 @@ use api::{LayerRect, MixBlendMode, Pipel
 use batch::{AlphaBatcher, ClipBatcher};
 use clip::{ClipStore};
 use clip_scroll_tree::{ClipScrollTree};
 use device::Texture;
 use gpu_cache::{GpuCache, GpuCacheUpdateList};
 use gpu_types::{BlurDirection, BlurInstance, BrushInstance, ClipChainRectIndex};
 use gpu_types::{ClipScrollNodeData, ClipScrollNodeIndex};
 use gpu_types::{PrimitiveInstance};
-use internal_types::{FastHashMap, RenderPassIndex};
+use internal_types::{FastHashMap, RenderPassIndex, SourceTexture};
 use picture::{PictureKind};
 use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveStore};
-use prim_store::{BrushMaskKind, BrushKind, DeferredResolve};
+use prim_store::{BrushMaskKind, BrushKind, DeferredResolve, EdgeAaSegmentMask};
 use profiler::FrameProfileCounters;
-use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKey, RenderTaskKind};
+use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use std::{cmp, usize, f32, i32};
-use std::collections::hash_map::Entry;
 use texture_allocator::GuillotineAllocator;
 
 const MIN_TARGET_SIZE: u32 = 2048;
 
 #[derive(Debug)]
 pub struct ScrollbarPrimitive {
     pub clip_id: ClipId,
     pub prim_index: PrimitiveIndex,
     pub frame_rect: LayerRect,
 }
 
 #[derive(Debug, Copy, Clone)]
 pub struct RenderTargetIndex(pub usize);
 
-#[derive(Debug)]
-struct DynamicTaskInfo {
-    task_id: RenderTaskId,
-    rect: DeviceIntRect,
-}
-
 pub struct RenderTargetContext<'a> {
     pub device_pixel_scale: DevicePixelScale,
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'a ResourceCache,
     pub node_data: &'a [ClipScrollNodeData],
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub use_dual_source_blending: bool,
 }
@@ -196,16 +189,32 @@ impl<T: RenderTarget> RenderTargetList<T
         };
 
         (origin, RenderTargetIndex(self.targets.len() - 1))
     }
 
     pub fn needs_depth(&self) -> bool {
         self.targets.iter().any(|target| target.needs_depth())
     }
+
+    pub fn check_ready(&self) {
+        match self.texture {
+            Some(ref t) => {
+                assert_eq!(t.get_dimensions(), self.max_size);
+                assert_eq!(t.get_format(), self.format);
+                assert_eq!(t.get_render_target_layer_count(), self.targets.len());
+                assert_eq!(t.get_layer_count() as usize, self.targets.len());
+                assert_eq!(t.has_depth(), t.get_rt_info().unwrap().has_depth);
+                assert_eq!(t.has_depth(), self.needs_depth());
+            }
+            None => {
+                assert!(self.targets.is_empty())
+            }
+        }
+    }
 }
 
 /// Frame output information for a given pipeline ID.
 /// Storing the task ID allows the renderer to find
 /// the target rect within the render target that this
 /// pipeline exists at.
 pub struct FrameOutput {
     pub task_id: RenderTaskId,
@@ -277,19 +286,16 @@ impl RenderTarget for ColorRenderTarget 
         ctx: &RenderTargetContext,
         _: &GpuCache,
         render_tasks: &RenderTaskTree,
         _: &ClipStore,
     ) {
         let task = &render_tasks[task_id];
 
         match task.kind {
-            RenderTaskKind::Alias(..) => {
-                panic!("BUG: add_task() called on invalidated task");
-            }
             RenderTaskKind::VerticalBlur(ref info) => {
                 info.add_instances(
                     &mut self.vertical_blurs,
                     task_id,
                     task.children[0],
                     BlurDirection::Vertical,
                     render_tasks,
                 );
@@ -404,19 +410,16 @@ impl RenderTarget for AlphaRenderTarget 
             }
             ClearMode::One => {}
             ClearMode::Transparent => {
                 panic!("bug: invalid clear mode for alpha task");
             }
         }
 
         match task.kind {
-            RenderTaskKind::Alias(..) => {
-                panic!("BUG: add_task() called on invalidated task");
-            }
             RenderTaskKind::Readback(..) => {
                 panic!("Should not be added to alpha target!");
             }
             RenderTaskKind::VerticalBlur(ref info) => {
                 info.add_instances(
                     &mut self.vertical_blurs,
                     task_id,
                     task.children[0],
@@ -460,23 +463,25 @@ impl RenderTarget for AlphaRenderTarget 
                                             //           tasks support clip masks and
                                             //           transform primitives, these
                                             //           will need to be filled out!
                                             clip_chain_rect_index: ClipChainRectIndex(0),
                                             scroll_id: ClipScrollNodeIndex(0),
                                             clip_task_address: RenderTaskAddress(0),
                                             z: 0,
                                             segment_index: 0,
+                                            edge_flags: EdgeAaSegmentMask::empty(),
                                             user_data0: 0,
                                             user_data1: 0,
                                         };
                                         let brush = &ctx.prim_store.cpu_brushes[sub_metadata.cpu_prim_index.0];
                                         let batch = match brush.kind {
                                             BrushKind::Solid { .. } |
-                                            BrushKind::Clear => {
+                                            BrushKind::Clear |
+                                            BrushKind::Line { .. } => {
                                                 unreachable!("bug: unexpected brush here");
                                             }
                                             BrushKind::Mask { ref kind, .. } => {
                                                 match *kind {
                                                     BrushMaskKind::Corner(..) => &mut self.brush_mask_corners,
                                                     BrushMaskKind::RoundedRect(..) => &mut self.brush_mask_rounded_rects,
                                                 }
                                             }
@@ -520,64 +525,104 @@ impl RenderTarget for AlphaRenderTarget 
         self.allocator.used_rect
     }
 
     fn needs_depth(&self) -> bool {
         false
     }
 }
 
+pub struct TextureCacheRenderTarget {
+    pub horizontal_blurs: Vec<BlurInstance>,
+}
+
+impl TextureCacheRenderTarget {
+    fn new(
+        _size: Option<DeviceUintSize>,
+        _screen_size: DeviceIntSize,
+    ) -> Self {
+        TextureCacheRenderTarget {
+            horizontal_blurs: Vec::new(),
+        }
+    }
+
+    fn add_task(
+        &mut self,
+        task_id: RenderTaskId,
+        render_tasks: &RenderTaskTree,
+    ) {
+        let task = &render_tasks[task_id];
+
+        match task.kind {
+            RenderTaskKind::HorizontalBlur(ref info) => {
+                info.add_instances(
+                    &mut self.horizontal_blurs,
+                    task_id,
+                    task.children[0],
+                    BlurDirection::Horizontal,
+                    render_tasks,
+                );
+            }
+            RenderTaskKind::VerticalBlur(..) |
+            RenderTaskKind::Picture(..) |
+            RenderTaskKind::CacheMask(..) |
+            RenderTaskKind::Readback(..) |
+            RenderTaskKind::Scaling(..) => {
+                panic!("BUG: unexpected task kind for texture cache target");
+            }
+        }
+    }
+}
 
 pub enum RenderPassKind {
     MainFramebuffer(ColorRenderTarget),
     OffScreen {
         alpha: RenderTargetList<AlphaRenderTarget>,
         color: RenderTargetList<ColorRenderTarget>,
+        texture_cache: FastHashMap<(SourceTexture, i32), TextureCacheRenderTarget>,
     },
 }
 
 /// A render pass represents a set of rendering operations that don't depend on one
 /// another.
 ///
 /// A render pass can have several render targets if there wasn't enough space in one
 /// target to do all of the rendering for that pass.
 pub struct RenderPass {
     pub kind: RenderPassKind,
     tasks: Vec<RenderTaskId>,
-    dynamic_tasks: FastHashMap<RenderTaskKey, DynamicTaskInfo>,
 }
 
 impl RenderPass {
     pub fn new_main_framebuffer(screen_size: DeviceIntSize) -> Self {
         let target = ColorRenderTarget::new(None, screen_size);
         RenderPass {
             kind: RenderPassKind::MainFramebuffer(target),
             tasks: vec![],
-            dynamic_tasks: FastHashMap::default(),
         }
     }
 
     pub fn new_off_screen(screen_size: DeviceIntSize) -> Self {
         RenderPass {
             kind: RenderPassKind::OffScreen {
                 color: RenderTargetList::new(screen_size, ImageFormat::BGRA8),
-                alpha: RenderTargetList::new(screen_size, ImageFormat::A8),
+                alpha: RenderTargetList::new(screen_size, ImageFormat::R8),
+                texture_cache: FastHashMap::default(),
             },
             tasks: vec![],
-            dynamic_tasks: FastHashMap::default(),
         }
     }
 
     pub fn add_render_task(
         &mut self,
         task_id: RenderTaskId,
         size: DeviceIntSize,
         target_kind: RenderTargetKind,
     ) {
-        if let RenderPassKind::OffScreen { ref mut color, ref mut alpha } = self.kind {
+        if let RenderPassKind::OffScreen { ref mut color, ref mut alpha, .. } = self.kind {
             let max_size = match target_kind {
                 RenderTargetKind::Color => &mut color.max_size,
                 RenderTargetKind::Alpha => &mut alpha.max_size,
             };
             max_size.width = cmp::max(max_size.width, size.width as u32);
             max_size.height = cmp::max(max_size.height, size.height as u32);
         }
 
@@ -599,67 +644,64 @@ impl RenderPass {
             RenderPassKind::MainFramebuffer(ref mut target) => {
                 for &task_id in &self.tasks {
                     assert_eq!(render_tasks[task_id].target_kind(), RenderTargetKind::Color);
                     render_tasks[task_id].pass_index = Some(pass_index);
                     target.add_task(task_id, ctx, gpu_cache, render_tasks, clip_store);
                 }
                 target.build(ctx, gpu_cache, render_tasks, deferred_resolves);
             }
-            RenderPassKind::OffScreen { ref mut color, ref mut alpha } => {
+            RenderPassKind::OffScreen { ref mut color, ref mut alpha, ref mut texture_cache } => {
                 // Step through each task, adding to batches as appropriate.
                 for &task_id in &self.tasks {
-                    let target_kind = {
+                    let (target_kind, texture_target) = {
                         let task = &mut render_tasks[task_id];
                         task.pass_index = Some(pass_index);
                         let target_kind = task.target_kind();
 
                         // Find a target to assign this task to, or create a new
                         // one if required.
                         match task.location {
-                            RenderTaskLocation::Fixed => {}
+                            RenderTaskLocation::TextureCache(texture_id, layer, _) => {
+                                // TODO(gw): When we support caching color items, we will
+                                //           need to calculate that here to get the
+                                //           correct target kind.
+                                (RenderTargetKind::Alpha, Some((texture_id, layer)))
+                            }
+                            RenderTaskLocation::Fixed => {
+                                (RenderTargetKind::Color, None)
+                            }
                             RenderTaskLocation::Dynamic(ref mut origin, size) => {
-                                let dynamic_entry = match task.cache_key {
-                                    // See if this task is a duplicate.
-                                    // If so, just skip adding it!
-                                    Some(cache_key) => match self.dynamic_tasks.entry(cache_key) {
-                                        Entry::Occupied(entry) => {
-                                            debug_assert_eq!(entry.get().rect.size, size);
-                                            task.kind = RenderTaskKind::Alias(entry.get().task_id);
-                                            continue;
-                                        },
-                                        Entry::Vacant(entry) => Some(entry),
-                                    },
-                                    None => None,
-                                };
-
                                 let alloc_size = DeviceUintSize::new(size.width as u32, size.height as u32);
                                 let (alloc_origin, target_index) =  match target_kind {
                                     RenderTargetKind::Color => color.allocate(alloc_size),
                                     RenderTargetKind::Alpha => alpha.allocate(alloc_size),
                                 };
                                 *origin = Some((alloc_origin.to_i32(), target_index));
 
-                                // If this task is cacheable / sharable, store it in the task hash
-                                // for this pass.
-                                if let Some(entry) = dynamic_entry {
-                                    entry.insert(DynamicTaskInfo {
-                                        task_id,
-                                        rect: DeviceIntRect::new(alloc_origin.to_i32(), size),
-                                    });
-                                }
+                                (target_kind, None)
                             }
                         }
-
-                        target_kind
                     };
 
-                    match target_kind {
-                        RenderTargetKind::Color => color.add_task(task_id, ctx, gpu_cache, render_tasks, clip_store),
-                        RenderTargetKind::Alpha => alpha.add_task(task_id, ctx, gpu_cache, render_tasks, clip_store),
+                    match texture_target {
+                        Some(texture_target) => {
+                            let texture = texture_cache
+                                .entry(texture_target)
+                                .or_insert(
+                                    TextureCacheRenderTarget::new(None, DeviceIntSize::zero())
+                                );
+                            texture.add_task(task_id, render_tasks);
+                        }
+                        None => {
+                            match target_kind {
+                                RenderTargetKind::Color => color.add_task(task_id, ctx, gpu_cache, render_tasks, clip_store),
+                                RenderTargetKind::Alpha => alpha.add_task(task_id, ctx, gpu_cache, render_tasks, clip_store),
+                            }
+                        }
                     }
                 }
 
                 color.build(ctx, gpu_cache, render_tasks, deferred_resolves);
                 alpha.build(ctx, gpu_cache, render_tasks, deferred_resolves);
             }
         }
     }
@@ -721,23 +763,13 @@ impl BlurTask {
         source_task_id: RenderTaskId,
         blur_direction: BlurDirection,
         render_tasks: &RenderTaskTree,
     ) {
         let instance = BlurInstance {
             task_address: render_tasks.get_task_address(task_id),
             src_task_address: render_tasks.get_task_address(source_task_id),
             blur_direction,
-            region: LayerRect::zero(),
         };
 
-        if self.regions.is_empty() {
-            instances.push(instance);
-        } else {
-            for region in &self.regions {
-                instances.push(BlurInstance {
-                    region: *region,
-                    ..instance
-                });
-            }
-        }
+        instances.push(instance);
     }
 }
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -106,31 +106,33 @@ pub enum SpecificDisplayItem {
     Line(LineDisplayItem),
     Text(TextDisplayItem),
     Image(ImageDisplayItem),
     YuvImage(YuvImageDisplayItem),
     Border(BorderDisplayItem),
     BoxShadow(BoxShadowDisplayItem),
     Gradient(GradientDisplayItem),
     RadialGradient(RadialGradientDisplayItem),
+    ClipChain(ClipChainItem),
     Iframe(IframeDisplayItem),
     PushStackingContext(PushStackingContextDisplayItem),
     PopStackingContext,
     SetGradientStops,
     PushShadow(Shadow),
     PopAllShadows,
 }
 
 /// This is a "complete" version of the DI specifics,
 /// containing the auxiliary data within the corresponding
 /// enumeration variants, to be used for debug serialization.
 #[cfg(feature = "debug-serialization")]
 #[derive(Deserialize, Serialize)]
 pub enum CompletelySpecificDisplayItem {
     Clip(ClipDisplayItem, Vec<ComplexClipRegion>),
+    ClipChain(ClipChainItem, Vec<ClipId>),
     ScrollFrame(ScrollFrameDisplayItem, Vec<ComplexClipRegion>),
     StickyFrame(StickyFrameDisplayItem),
     Rectangle(RectangleDisplayItem),
     ClearRectangle,
     Line(LineDisplayItem),
     Text(TextDisplayItem, Vec<GlyphInstance>),
     Image(ImageDisplayItem),
     YuvImage(YuvImageDisplayItem),
@@ -422,16 +424,22 @@ pub struct RadialGradient {
     pub start_radius: f32,
     pub end_center: LayoutPoint,
     pub end_radius: f32,
     pub ratio_xy: f32,
     pub extend_mode: ExtendMode,
 } // IMPLICIT stops: Vec<GradientStop>
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct ClipChainItem {
+    pub id: ClipChainId,
+    pub parent: Option<ClipChainId>,
+} // IMPLICIT stops: Vec<ClipId>
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct RadialGradientDisplayItem {
     pub gradient: RadialGradient,
     pub tile_size: LayoutSize,
     pub tile_spacing: LayoutSize,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct PushStackingContextDisplayItem {
@@ -502,26 +510,33 @@ pub struct IframeDisplayItem {
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageDisplayItem {
     pub image_key: ImageKey,
     pub stretch_size: LayoutSize,
     pub tile_spacing: LayoutSize,
     pub image_rendering: ImageRendering,
+    pub alpha_type: AlphaType,
 }
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum ImageRendering {
     Auto = 0,
     CrispEdges = 1,
     Pixelated = 2,
 }
 
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub enum AlphaType {
+    Alpha = 0,
+    PremultipliedAlpha = 1,
+}
+
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct YuvImageDisplayItem {
     pub yuv_data: YuvData,
     pub color_space: YuvColorSpace,
     pub image_rendering: ImageRendering,
 }
 
 #[repr(u32)]
@@ -722,19 +737,24 @@ impl ComplexClipRegion {
         rect: LayoutRect,
         radii: BorderRadius,
         mode: ClipMode,
     ) -> Self {
         ComplexClipRegion { rect, radii, mode }
     }
 }
 
+
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub struct ClipChainId(pub u64, pub PipelineId);
+
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum ClipId {
     Clip(u64, PipelineId),
+    ClipChain(ClipChainId),
     ClipExternalId(u64, PipelineId),
     DynamicallyAddedNode(u64, PipelineId),
 }
 
 impl ClipId {
     pub fn root_scroll_node(pipeline_id: PipelineId) -> ClipId {
         ClipId::Clip(0, pipeline_id)
     }
@@ -751,16 +771,17 @@ impl ClipId {
         }
 
         ClipId::ClipExternalId(id, pipeline_id)
     }
 
     pub fn pipeline_id(&self) -> PipelineId {
         match *self {
             ClipId::Clip(_, pipeline_id) |
+            ClipId::ClipChain(ClipChainId(_, pipeline_id)) |
             ClipId::ClipExternalId(_, pipeline_id) |
             ClipId::DynamicallyAddedNode(_, pipeline_id) => pipeline_id,
         }
     }
 
     pub fn external_id(&self) -> Option<u64> {
         match *self {
             ClipId::ClipExternalId(id, _) => Some(id),
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -1,23 +1,23 @@
 /* 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 {BorderDetails, BorderDisplayItem, BorderRadius, BorderWidths, BoxShadowClipMode};
-use {BoxShadowDisplayItem, ClipAndScrollInfo, ClipDisplayItem, ClipId, ColorF, ComplexClipRegion};
-use {DisplayItem, ExtendMode, FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, Gradient};
-use {GradientDisplayItem, GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask};
-use {ImageRendering, LayerPrimitiveInfo, LayoutPoint, LayoutPrimitiveInfo, LayoutRect, LayoutSize};
-use {LayoutTransform, LayoutVector2D, LineDisplayItem, LineOrientation, LineStyle, LocalClip};
-use {MixBlendMode, PipelineId, PropertyBinding, PushStackingContextDisplayItem, RadialGradient};
-use {RadialGradientDisplayItem, RectangleDisplayItem, ScrollFrameDisplayItem, ScrollPolicy};
-use {ScrollSensitivity, Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem};
-use {StickyOffsetBounds, TextDisplayItem, TransformStyle, YuvColorSpace, YuvData};
-use YuvImageDisplayItem;
+use {AlphaType, BorderDetails, BorderDisplayItem, BorderRadius, BorderWidths, BoxShadowClipMode};
+use {BoxShadowDisplayItem, ClipAndScrollInfo, ClipChainId, ClipChainItem, ClipDisplayItem, ClipId};
+use {ColorF, ComplexClipRegion, DisplayItem, ExtendMode, FilterOp, FontInstanceKey, GlyphInstance};
+use {GlyphOptions, Gradient, GradientDisplayItem, GradientStop, IframeDisplayItem};
+use {ImageDisplayItem, ImageKey, ImageMask, ImageRendering, LayerPrimitiveInfo, LayoutPoint};
+use {LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
+use {LineDisplayItem, LineOrientation, LineStyle, LocalClip, MixBlendMode, PipelineId};
+use {PropertyBinding, PushStackingContextDisplayItem, RadialGradient, RadialGradientDisplayItem};
+use {RectangleDisplayItem, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity, Shadow};
+use {SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, StickyOffsetBounds};
+use {TextDisplayItem, TransformStyle, YuvColorSpace, YuvData, YuvImageDisplayItem};
 use bincode;
 use euclid::SideOffsets2D;
 use serde::{Deserialize, Serialize};
 use std::io::{Read, Write};
 use std::{io, mem, ptr};
 use std::marker::PhantomData;
 use std::slice;
 use time::precise_time_ns;
@@ -81,16 +81,17 @@ pub struct BuiltDisplayListDescriptor {
 
 pub struct BuiltDisplayListIter<'a> {
     list: &'a BuiltDisplayList,
     data: &'a [u8],
     cur_item: DisplayItem,
     cur_stops: ItemRange<GradientStop>,
     cur_glyphs: ItemRange<GlyphInstance>,
     cur_filters: ItemRange<FilterOp>,
+    cur_clip_chain_items: ItemRange<ClipId>,
     cur_complex_clip: (ItemRange<ComplexClipRegion>, usize),
     peeking: Peek,
 }
 
 pub struct DisplayItemRef<'a: 'b, 'b> {
     iter: &'b BuiltDisplayListIter<'a>,
 }
 
@@ -192,16 +193,17 @@ impl<'a> BuiltDisplayListIter<'a> {
                 // Dummy data, will be overwritten by `next`
                 item: SpecificDisplayItem::PopStackingContext,
                 clip_and_scroll: ClipAndScrollInfo::simple(ClipId::new(0, PipelineId::dummy())),
                 info: LayoutPrimitiveInfo::new(LayoutRect::zero()),
             },
             cur_stops: ItemRange::default(),
             cur_glyphs: ItemRange::default(),
             cur_filters: ItemRange::default(),
+            cur_clip_chain_items: ItemRange::default(),
             cur_complex_clip: (ItemRange::default(), 0),
             peeking: Peek::NotPeeking,
         }
     }
 
     pub fn display_list(&self) -> &'a BuiltDisplayList {
         self.list
     }
@@ -218,16 +220,17 @@ impl<'a> BuiltDisplayListIter<'a> {
                 self.peeking = Peek::IsPeeking;
             }
             Peek::NotPeeking => { /* do nothing */ }
         }
 
         // Don't let these bleed into another item
         self.cur_stops = ItemRange::default();
         self.cur_complex_clip = (ItemRange::default(), 0);
+        self.cur_clip_chain_items = ItemRange::default();
 
         loop {
             if self.data.len() == 0 {
                 return None;
             }
 
             {
                 let reader = bincode::read_types::IoReader::new(UnsafeReader::new(&mut self.data));
@@ -238,16 +241,19 @@ impl<'a> BuiltDisplayListIter<'a> {
 
             match self.cur_item.item {
                 SetGradientStops => {
                     self.cur_stops = skip_slice::<GradientStop>(self.list, &mut self.data).0;
 
                     // This is a dummy item, skip over it
                     continue;
                 }
+                ClipChain(_) => {
+                    self.cur_clip_chain_items = skip_slice::<ClipId>(self.list, &mut self.data).0;
+                }
                 Clip(_) | ScrollFrame(_) => {
                     self.cur_complex_clip = self.skip_slice::<ComplexClipRegion>()
                 }
                 Text(_) => self.cur_glyphs = self.skip_slice::<GlyphInstance>().0,
                 PushStackingContext(_) => self.cur_filters = self.skip_slice::<FilterOp>().0,
                 _ => { /* do nothing */ }
             }
 
@@ -351,16 +357,20 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> {
     pub fn glyphs(&self) -> ItemRange<GlyphInstance> {
         self.iter.cur_glyphs
     }
 
     pub fn filters(&self) -> ItemRange<FilterOp> {
         self.iter.cur_filters
     }
 
+    pub fn clip_chain_items(&self) -> ItemRange<ClipId> {
+        self.iter.cur_clip_chain_items
+    }
+
     pub fn display_list(&self) -> &BuiltDisplayList {
         self.iter.display_list()
     }
 
     pub fn is_backface_visible(&self) -> bool {
         self.iter.cur_item.info.is_backface_visible
     }
 
@@ -413,51 +423,59 @@ impl<'a, T: for<'de> Deserialize<'de>> :
 impl Serialize for BuiltDisplayList {
     fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
         use display_item::CompletelySpecificDisplayItem::*;
         use display_item::GenericDisplayItem;
 
         let mut seq = serializer.serialize_seq(None)?;
         let mut traversal = self.iter();
         while let Some(item) = traversal.next() {
-            let di = item.display_item();
+            let display_item = item.display_item();
             let serial_di = GenericDisplayItem {
-                item: match di.item {
-                    SpecificDisplayItem::Clip(v) => Clip(v,
+                item: match display_item.item {
+                    SpecificDisplayItem::Clip(v) => Clip(
+                        v,
                         item.iter.list.get(item.iter.cur_complex_clip.0).collect()
                     ),
-                    SpecificDisplayItem::ScrollFrame(v) => ScrollFrame(v,
+                    SpecificDisplayItem::ClipChain(v) => ClipChain(
+                        v,
+                        item.iter.list.get(item.iter.cur_clip_chain_items).collect(),
+                    ),
+                    SpecificDisplayItem::ScrollFrame(v) => ScrollFrame(
+                        v,
                         item.iter.list.get(item.iter.cur_complex_clip.0).collect()
                     ),
                     SpecificDisplayItem::StickyFrame(v) => StickyFrame(v),
                     SpecificDisplayItem::Rectangle(v) => Rectangle(v),
                     SpecificDisplayItem::ClearRectangle => ClearRectangle,
                     SpecificDisplayItem::Line(v) => Line(v),
-                    SpecificDisplayItem::Text(v) => Text(v,
+                    SpecificDisplayItem::Text(v) => Text(
+                        v,
                         item.iter.list.get(item.iter.cur_glyphs).collect()
                     ),
                     SpecificDisplayItem::Image(v) => Image(v),
                     SpecificDisplayItem::YuvImage(v) => YuvImage(v),
                     SpecificDisplayItem::Border(v) => Border(v),
                     SpecificDisplayItem::BoxShadow(v) => BoxShadow(v),
                     SpecificDisplayItem::Gradient(v) => Gradient(v),
                     SpecificDisplayItem::RadialGradient(v) => RadialGradient(v),
                     SpecificDisplayItem::Iframe(v) => Iframe(v),
-                    SpecificDisplayItem::PushStackingContext(v) => PushStackingContext(v,
+                    SpecificDisplayItem::PushStackingContext(v) => PushStackingContext(
+                        v,
                         item.iter.list.get(item.iter.cur_filters).collect()
                     ),
                     SpecificDisplayItem::PopStackingContext => PopStackingContext,
                     SpecificDisplayItem::SetGradientStops => SetGradientStops(
                         item.iter.list.get(item.iter.cur_stops).collect()
                     ),
                     SpecificDisplayItem::PushShadow(v) => PushShadow(v),
                     SpecificDisplayItem::PopAllShadows => PopAllShadows,
                 },
-                clip_and_scroll: di.clip_and_scroll,
-                info: di.info,
+                clip_and_scroll: display_item.clip_and_scroll,
+                info: display_item.info,
             };
             seq.serialize_element(&serial_di)?
         }
         seq.end()
     }
 }
 
 // The purpose of this implementation is to deserialize
@@ -487,49 +505,54 @@ impl<'de> Deserialize<'de> for BuiltDisp
         let list = Vec::<GenericDisplayItem<CompletelySpecificDisplayItem>>
             ::deserialize(deserializer)?;
 
         let mut data = Vec::new();
         let mut temp = Vec::new();
         for complete in list {
             let item = DisplayItem {
                 item: match complete.item {
-                    Clip(v, complex_clips) => {
+                    Clip(specific_item, complex_clips) => {
                         push_vec(&mut temp, complex_clips);
-                        SpecificDisplayItem::Clip(v)
+                        SpecificDisplayItem::Clip(specific_item)
                     },
-                    ScrollFrame(v, complex_clips) => {
+                    ClipChain(specific_item, clip_chain_ids) => {
+                        push_vec(&mut temp, clip_chain_ids);
+                        SpecificDisplayItem::ClipChain(specific_item)
+                    }
+                    ScrollFrame(specific_item, complex_clips) => {
                         push_vec(&mut temp, complex_clips);
-                        SpecificDisplayItem::ScrollFrame(v)
+                        SpecificDisplayItem::ScrollFrame(specific_item)
                     },
-                    StickyFrame(v) => SpecificDisplayItem::StickyFrame(v),
-                    Rectangle(v) => SpecificDisplayItem::Rectangle(v),
+                    StickyFrame(specific_item) => SpecificDisplayItem::StickyFrame(specific_item),
+                    Rectangle(specific_item) => SpecificDisplayItem::Rectangle(specific_item),
                     ClearRectangle => SpecificDisplayItem::ClearRectangle,
-                    Line(v) => SpecificDisplayItem::Line(v),
-                    Text(v, glyphs) => {
+                    Line(specific_item) => SpecificDisplayItem::Line(specific_item),
+                    Text(specific_item, glyphs) => {
                         push_vec(&mut temp, glyphs);
-                        SpecificDisplayItem::Text(v)
+                        SpecificDisplayItem::Text(specific_item)
                     },
-                    Image(v) => SpecificDisplayItem::Image(v),
-                    YuvImage(v) => SpecificDisplayItem::YuvImage(v),
-                    Border(v) => SpecificDisplayItem::Border(v),
-                    BoxShadow(v) => SpecificDisplayItem::BoxShadow(v),
-                    Gradient(v) => SpecificDisplayItem::Gradient(v),
-                    RadialGradient(v) => SpecificDisplayItem::RadialGradient(v),
-                    Iframe(v) => SpecificDisplayItem::Iframe(v),
-                    PushStackingContext(v, filters) => {
+                    Image(specific_item) => SpecificDisplayItem::Image(specific_item),
+                    YuvImage(specific_item) => SpecificDisplayItem::YuvImage(specific_item),
+                    Border(specific_item) => SpecificDisplayItem::Border(specific_item),
+                    BoxShadow(specific_item) => SpecificDisplayItem::BoxShadow(specific_item),
+                    Gradient(specific_item) => SpecificDisplayItem::Gradient(specific_item),
+                    RadialGradient(specific_item) =>
+                        SpecificDisplayItem::RadialGradient(specific_item),
+                    Iframe(specific_item) => SpecificDisplayItem::Iframe(specific_item),
+                    PushStackingContext(specific_item, filters) => {
                         push_vec(&mut temp, filters);
-                        SpecificDisplayItem::PushStackingContext(v)
+                        SpecificDisplayItem::PushStackingContext(specific_item)
                     },
                     PopStackingContext => SpecificDisplayItem::PopStackingContext,
                     SetGradientStops(stops) => {
                         push_vec(&mut temp, stops);
                         SpecificDisplayItem::SetGradientStops
                     },
-                    PushShadow(v) => SpecificDisplayItem::PushShadow(v),
+                    PushShadow(specific_item) => SpecificDisplayItem::PushShadow(specific_item),
                     PopAllShadows => SpecificDisplayItem::PopAllShadows,
                 },
                 clip_and_scroll: complete.clip_and_scroll,
                 info: complete.info,
             };
             serialize_fast(&mut data, &item);
             // the aux data is serialized after the item, hence the temporary
             data.extend(temp.drain(..));
@@ -761,24 +784,26 @@ impl<'a, 'b> Read for UnsafeReader<'a, '
     }
 }
 
 #[derive(Clone, Debug)]
 pub struct SaveState {
     dl_len: usize,
     clip_stack_len: usize,
     next_clip_id: u64,
+    next_clip_chain_id: u64,
 }
 
 #[derive(Clone)]
 pub struct DisplayListBuilder {
     pub data: Vec<u8>,
     pub pipeline_id: PipelineId,
     clip_stack: Vec<ClipAndScrollInfo>,
     next_clip_id: u64,
+    next_clip_chain_id: u64,
     builder_start_time: u64,
 
     /// The size of the content of this display list. This is used to allow scrolling
     /// outside the bounds of the display list items themselves.
     content_size: LayoutSize,
     save_state: Option<SaveState>,
 }
 
@@ -799,16 +824,17 @@ impl DisplayListBuilder {
 
         DisplayListBuilder {
             data: Vec::with_capacity(capacity),
             pipeline_id,
             clip_stack: vec![
                 ClipAndScrollInfo::simple(ClipId::root_scroll_node(pipeline_id)),
             ],
             next_clip_id: FIRST_CLIP_ID,
+            next_clip_chain_id: 0,
             builder_start_time: start_time,
             content_size,
             save_state: None,
         }
     }
 
     /// Return the content size for this display list
     pub fn content_size(&self) -> LayoutSize {
@@ -824,26 +850,28 @@ impl DisplayListBuilder {
     /// * Must call `clear_save()` if the restore becomes unnecessary.
     pub fn save(&mut self) {
         assert!(self.save_state.is_none(), "DisplayListBuilder doesn't support nested saves");
 
         self.save_state = Some(SaveState {
             clip_stack_len: self.clip_stack.len(),
             dl_len: self.data.len(),
             next_clip_id: self.next_clip_id,
+            next_clip_chain_id: self.next_clip_chain_id,
         });
     }
 
     /// Restores the state of the builder to when `save()` was last called.
     pub fn restore(&mut self) {
         let state = self.save_state.take().expect("No save to restore DisplayListBuilder from");
 
         self.clip_stack.truncate(state.clip_stack_len);
         self.data.truncate(state.dl_len);
         self.next_clip_id = state.next_clip_id;
+        self.next_clip_chain_id = state.next_clip_chain_id;
     }
 
     /// Discards the builder's save (indicating the attempted operation was sucessful).
     pub fn clear_save(&mut self) {
         self.save_state.take().expect("No save to clear in DisplayListBuilder");
     }
 
     pub fn print_display_list(&mut self) {
@@ -959,23 +987,25 @@ impl DisplayListBuilder {
     }
 
     pub fn push_image(
         &mut self,
         info: &LayoutPrimitiveInfo,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         image_rendering: ImageRendering,
+        alpha_type: AlphaType,
         key: ImageKey,
     ) {
         let item = SpecificDisplayItem::Image(ImageDisplayItem {
             image_key: key,
             stretch_size,
             tile_spacing,
             image_rendering,
+            alpha_type,
         });
 
         self.push_item(item, info);
     }
 
     /// Push a yuv image. All planar data in yuv image should use the same buffer type.
     pub fn push_yuv_image(
         &mut self,
@@ -1310,16 +1340,21 @@ impl DisplayListBuilder {
 
     fn generate_clip_id(&mut self, id: Option<ClipId>) -> ClipId {
         id.unwrap_or_else(|| {
             self.next_clip_id += 1;
             ClipId::Clip(self.next_clip_id - 1, self.pipeline_id)
         })
     }
 
+    fn generate_clip_chain_id(&mut self) -> ClipChainId {
+        self.next_clip_chain_id += 1;
+        ClipChainId(self.next_clip_chain_id - 1, self.pipeline_id)
+    }
+
     pub fn define_scroll_frame<I>(
         &mut self,
         id: Option<ClipId>,
         content_rect: LayoutRect,
         clip_rect: LayoutRect,
         complex_clips: I,
         image_mask: Option<ImageMask>,
         scroll_sensitivity: ScrollSensitivity,
@@ -1350,28 +1385,43 @@ impl DisplayListBuilder {
         scroll_sensitivity: ScrollSensitivity,
     ) -> ClipId
     where
         I: IntoIterator<Item = ComplexClipRegion>,
         I::IntoIter: ExactSizeIterator + Clone,
     {
         let id = self.generate_clip_id(id);
         let item = SpecificDisplayItem::ScrollFrame(ScrollFrameDisplayItem {
-            id: id,
-            image_mask: image_mask,
+            id,
+            image_mask,
             scroll_sensitivity,
         });
         let info = LayoutPrimitiveInfo::with_clip_rect(content_rect, clip_rect);
 
         let scrollinfo = ClipAndScrollInfo::simple(parent);
         self.push_item_with_clip_scroll_info(item, &info, scrollinfo);
         self.push_iter(complex_clips);
         id
     }
 
+    pub fn define_clip_chain<I>(
+        &mut self,
+        parent: Option<ClipChainId>,
+        clips: I,
+    ) -> ClipChainId
+    where
+        I: IntoIterator<Item = ClipId>,
+        I::IntoIter: ExactSizeIterator + Clone,
+    {
+        let id = self.generate_clip_chain_id();
+        self.push_new_empty_item(SpecificDisplayItem::ClipChain(ClipChainItem { id, parent}));
+        self.push_iter(clips);
+        id
+    }
+
     pub fn define_clip<I>(
         &mut self,
         id: Option<ClipId>,
         clip_rect: LayoutRect,
         complex_clips: I,
         image_mask: Option<ImageMask>,
     ) -> ClipId
     where
--- a/gfx/webrender_api/src/font.rs
+++ b/gfx/webrender_api/src/font.rs
@@ -194,27 +194,40 @@ impl Hash for FontVariation {
         self.value.to_bits().hash(state);
     }
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)]
 pub struct GlyphOptions {
     pub render_mode: FontRenderMode,
+    pub flags: FontInstanceFlags,
+}
+
+impl Default for GlyphOptions {
+    fn default() -> GlyphOptions {
+        GlyphOptions {
+            render_mode: FontRenderMode::Subpixel,
+            flags: FontInstanceFlags::empty(),
+        }
+    }
 }
 
 bitflags! {
     #[repr(C)]
     #[derive(Deserialize, Serialize)]
     pub struct FontInstanceFlags: u32 {
         // 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;
 
         // Windows flags
         const FORCE_GDI         = 1 << 16;
 
         // Mac flags
         const FONT_SMOOTHING    = 1 << 16;
 
         // FreeType flags
--- a/gfx/webrender_api/src/image.rs
+++ b/gfx/webrender_api/src/image.rs
@@ -46,26 +46,26 @@ pub struct ExternalImageData {
     pub channel_index: u8,
     pub image_type: ExternalImageType,
 }
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum ImageFormat {
     Invalid = 0,
-    A8 = 1,
+    R8 = 1,
     BGRA8 = 3,
     RGBAF32 = 4,
     RG8 = 5,
 }
 
 impl ImageFormat {
     pub fn bytes_per_pixel(self) -> u32 {
         match self {
-            ImageFormat::A8 => 1,
+            ImageFormat::R8 => 1,
             ImageFormat::BGRA8 => 4,
             ImageFormat::RGBAF32 => 16,
             ImageFormat::RG8 => 2,
             ImageFormat::Invalid => 0,
         }
     }
 }