Bug 1465058 - Update webrender to commit 8e697f8cb1f1aab2e5f6b9b903eb7191340b10c5. r?Gankro draft
authorKartikaya Gupta <kgupta@mozilla.com>
Mon, 04 Jun 2018 10:53:49 -0400
changeset 803559 5bacb36ae257d8cee8d48e7afc0433b2d0f47e11
parent 803479 fa5724780fe76d6ccbbd08d978342a1db6a43d49
child 803560 ac8ccbe185b2196d13f8d87d519e6c8a376b9d6b
push id112146
push userkgupta@mozilla.com
push dateMon, 04 Jun 2018 14:57:10 +0000
reviewersGankro
bugs1465058
milestone62.0a1
Bug 1465058 - Update webrender to commit 8e697f8cb1f1aab2e5f6b9b903eb7191340b10c5. r?Gankro MozReview-Commit-ID: BakJj8upl1A
gfx/webrender/examples/alpha_perf.rs
gfx/webrender/examples/animation.rs
gfx/webrender/examples/basic.rs
gfx/webrender/examples/blob.rs
gfx/webrender/examples/document.rs
gfx/webrender/examples/frame_output.rs
gfx/webrender/examples/iframe.rs
gfx/webrender/examples/image_resize.rs
gfx/webrender/examples/multiwindow.rs
gfx/webrender/examples/scrolling.rs
gfx/webrender/examples/texture_cache_stress.rs
gfx/webrender/examples/yuv.rs
gfx/webrender/res/clip_shared.glsl
gfx/webrender/res/cs_border_segment.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/shared.glsl
gfx/webrender/res/shared_border.glsl
gfx/webrender/res/transform.glsl
gfx/webrender/src/border.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/glyph_rasterizer/pathfinder.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/scene.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/src/rawtest.rs
gfx/wrench/src/yaml_frame_reader.rs
gfx/wrench/src/yaml_frame_writer.rs
--- a/gfx/webrender/examples/alpha_perf.rs
+++ b/gfx/webrender/examples/alpha_perf.rs
@@ -30,19 +30,17 @@ impl Example for App {
         _document_id: DocumentId,
     ) {
         let bounds = (0, 0).to(1920, 1080);
         let info = LayoutPrimitiveInfo::new(bounds);
 
         builder.push_stacking_context(
             &info,
             None,
-            None,
             TransformStyle::Flat,
-            None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
         for _ in 0 .. self.rect_count {
             builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 0.05));
         }
--- a/gfx/webrender/examples/animation.rs
+++ b/gfx/webrender/examples/animation.rs
@@ -43,22 +43,29 @@ impl Example for App {
         // Create a 200x200 stacking context with an animated transform property.
         let bounds = (0, 0).to(200, 200);
 
         let filters = vec![
             FilterOp::Opacity(PropertyBinding::Binding(self.opacity_key, self.opacity), self.opacity),
         ];
 
         let info = LayoutPrimitiveInfo::new(bounds);
+        let reference_frame_id = builder.push_reference_frame(
+            &info,
+            Some(PropertyBinding::Binding(self.property_key, LayoutTransform::identity())),
+            None,
+        );
+
+        builder.push_clip_id(reference_frame_id);
+
+        let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
             None,
-            Some(PropertyBinding::Binding(self.property_key, LayoutTransform::identity())),
             TransformStyle::Flat,
-            None,
             MixBlendMode::Normal,
             filters,
             GlyphRasterSpace::Screen,
         );
 
         let complex_clip = ComplexClipRegion {
             rect: bounds,
             radii: BorderRadius::uniform(50.0),
@@ -68,16 +75,19 @@ impl Example for App {
         builder.push_clip_id(clip_id);
 
         // Fill it with a white rect
         builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
 
         builder.pop_clip_id();
 
         builder.pop_stacking_context();
+
+        builder.pop_clip_id();
+        builder.pop_reference_frame();
     }
 
     fn on_event(&mut self, win_event: winit::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool {
         match win_event {
             winit::WindowEvent::KeyboardInput {
                 input: winit::KeyboardInput {
                     state: winit::ElementState::Pressed,
                     virtual_keycode: Some(key),
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -188,19 +188,17 @@ impl Example for App {
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
             None,
-            None,
             TransformStyle::Flat,
-            None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
         let image_mask_key = api.generate_image_key();
         txn.add_image(
             image_mask_key,
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -246,19 +246,17 @@ impl Example for App {
             None,
         );
 
         let bounds = api::LayoutRect::new(api::LayoutPoint::zero(), builder.content_size());
         let info = api::LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
             None,
-            None,
             api::TransformStyle::Flat,
-            None,
             api::MixBlendMode::Normal,
             Vec::new(),
             api::GlyphRasterSpace::Screen,
         );
 
         let info = api::LayoutPrimitiveInfo::new((30, 30).by(500, 500));
         builder.push_image(
             &info,
--- a/gfx/webrender/examples/document.rs
+++ b/gfx/webrender/examples/document.rs
@@ -64,17 +64,17 @@ impl App {
         ];
 
         for (pipeline_id, layer, color, offset) in init_data {
             let size = DeviceUintSize::new(250, 250);
             let bounds = DeviceUintRect::new(offset, size);
 
             let document_id = api.add_document(size, layer);
             let mut txn = Transaction::new();
-            txn.set_window_parameters(framebuffer_size, bounds, 1.0);
+            txn.set_window_parameters(framebuffer_size, bounds, device_pixel_ratio);
             txn.set_root_pipeline(pipeline_id);
             api.send_transaction(document_id, txn);
 
             self.documents.push(Document {
                 id: document_id,
                 pipeline_id,
                 content_rect: bounds.to_f32() / TypedScale::new(device_pixel_ratio),
                 color,
@@ -109,19 +109,17 @@ impl Example for App {
             let local_rect = LayoutRect::new(
                 LayoutPoint::zero(),
                 doc.content_rect.size,
             );
 
             builder.push_stacking_context(
                 &LayoutPrimitiveInfo::new(doc.content_rect),
                 None,
-                None,
                 TransformStyle::Flat,
-                None,
                 MixBlendMode::Normal,
                 Vec::new(),
                 GlyphRasterSpace::Screen,
             );
             builder.push_rect(
                 &LayoutPrimitiveInfo::new(local_rect),
                 doc.color,
             );
--- a/gfx/webrender/examples/frame_output.rs
+++ b/gfx/webrender/examples/frame_output.rs
@@ -97,19 +97,17 @@ impl App {
         let mut builder = DisplayListBuilder::new(
             document.pipeline_id,
             document.content_rect.size,
         );
 
         builder.push_stacking_context(
             &info,
             None,
-            None,
             TransformStyle::Flat,
-            None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
         builder.push_rect(&info, ColorF::new(1.0, 1.0, 0.0, 1.0));
         builder.pop_stacking_context();
 
@@ -143,19 +141,17 @@ impl Example for App {
                 builder.content_size().width;
             self.init_output_document(api, DeviceUintSize::new(200, 200), device_pixel_ratio);
         }
 
         let info = LayoutPrimitiveInfo::new((100, 100).to(200, 200));
         builder.push_stacking_context(
             &info,
             None,
-            None,
             TransformStyle::Flat,
-            None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
         builder.push_image(
             &info,
             info.rect.size,
--- a/gfx/webrender/examples/iframe.rs
+++ b/gfx/webrender/examples/iframe.rs
@@ -35,19 +35,17 @@ impl Example for App {
 
         let sub_pipeline_id = PipelineId(pipeline_id.0, 42);
         let mut sub_builder = DisplayListBuilder::new(sub_pipeline_id, sub_bounds.size);
 
         let info = LayoutPrimitiveInfo::new(sub_bounds);
         sub_builder.push_stacking_context(
             &info,
             None,
-            None,
             TransformStyle::Flat,
-            None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
         // green rect visible == success
         sub_builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
         sub_builder.pop_stacking_context();
@@ -57,30 +55,40 @@ impl Example for App {
             Epoch(0),
             None,
             sub_bounds.size,
             sub_builder.finalize(),
             true,
         );
         api.send_transaction(document_id, txn);
 
+        let info = LayoutPrimitiveInfo::new(sub_bounds);
+        let reference_frame_id = builder.push_reference_frame(
+            &info,
+            Some(PropertyBinding::Binding(PropertyBindingKey::new(42), LayoutTransform::identity())),
+            None,
+        );
+        builder.push_clip_id(reference_frame_id);
+
+
         // And this is for the root pipeline
         builder.push_stacking_context(
             &info,
             None,
-            Some(PropertyBinding::Binding(PropertyBindingKey::new(42), LayoutTransform::identity())),
             TransformStyle::Flat,
-            None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
         // red rect under the iframe: if this is visible, things have gone wrong
         builder.push_rect(&info, ColorF::new(1.0, 0.0, 0.0, 1.0));
         builder.push_iframe(&info, sub_pipeline_id, false);
         builder.pop_stacking_context();
+
+        builder.pop_clip_id();
+        builder.pop_reference_frame();
     }
 }
 
 fn main() {
     let mut app = App {};
     boilerplate::main_wrapper(&mut app, None);
 }
--- a/gfx/webrender/examples/image_resize.rs
+++ b/gfx/webrender/examples/image_resize.rs
@@ -37,19 +37,17 @@ impl Example for App {
             None,
         );
 
         let bounds = (0, 0).to(512, 512);
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
             None,
-            None,
             TransformStyle::Flat,
-            None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
         let image_size = LayoutSize::new(100.0, 100.0);
 
         let info = LayoutPrimitiveInfo::with_clip_rect(
--- a/gfx/webrender/examples/multiwindow.rs
+++ b/gfx/webrender/examples/multiwindow.rs
@@ -175,19 +175,17 @@ impl Window {
         let mut txn = Transaction::new();
         let mut builder = DisplayListBuilder::new(self.pipeline_id, layout_size);
 
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
             None,
-            None,
             TransformStyle::Flat,
-            None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
         let info = LayoutPrimitiveInfo::new(LayoutRect::new(
             LayoutPoint::new(100.0, 100.0),
             LayoutSize::new(100.0, 200.0)
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -30,34 +30,30 @@ impl Example for App {
         _document_id: DocumentId,
     ) {
         let info = LayoutPrimitiveInfo::new(
             LayoutRect::new(LayoutPoint::zero(), builder.content_size())
         );
         builder.push_stacking_context(
             &info,
             None,
-            None,
             TransformStyle::Flat,
-            None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
         if true {
             // scrolling and clips stuff
             // let's make a scrollbox
             let scrollbox = (0, 0).to(300, 400);
             builder.push_stacking_context(
                 &LayoutPrimitiveInfo::new((10, 10).by(0, 0)),
                 None,
-                None,
                 TransformStyle::Flat,
-                None,
                 MixBlendMode::Normal,
                 Vec::new(),
                 GlyphRasterSpace::Screen,
             );
             // set the scrolling clip
             let clip_id = builder.define_scroll_frame(
                 None,
                 (0, 0).by(1000, 1000),
--- a/gfx/webrender/examples/texture_cache_stress.rs
+++ b/gfx/webrender/examples/texture_cache_stress.rs
@@ -89,19 +89,17 @@ impl Example for App {
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = (0, 0).to(512, 512);
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
             None,
-            None,
             TransformStyle::Flat,
-            None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
         let x0 = 50.0;
         let y0 = 50.0;
         let image_size = LayoutSize::new(4.0, 4.0);
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -84,19 +84,17 @@ impl Example for App {
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
             None,
-            None,
             TransformStyle::Flat,
-            None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
         let yuv_chanel1 = api.generate_image_key();
         let yuv_chanel2 = api.generate_image_key();
         let yuv_chanel2_1 = api.generate_image_key();
--- a/gfx/webrender/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -93,18 +93,8 @@ ClipVertexInfo write_clip_tile_vertex(Re
 
     init_transform_vs(vec4(local_clip_rect.p0, local_clip_rect.p0 + local_clip_rect.size));
 
     ClipVertexInfo vi = ClipVertexInfo(node_pos.xyw, actual_pos, local_clip_rect);
     return vi;
 }
 
 #endif //WR_VERTEX_SHADER
-
-#ifdef WR_FRAGMENT_SHADER
-
-//Note: identical to prim_shared
-float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) {
-    vec2 dir_to_p0 = p0 - p;
-    return dot(normalize(perp_dir), dir_to_p0);
-}
-
-#endif //WR_FRAGMENT_SHADER
--- a/gfx/webrender/res/cs_border_segment.glsl
+++ b/gfx/webrender/res/cs_border_segment.glsl
@@ -1,46 +1,75 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include shared,prim_shared,ellipse,shared_border
+#include shared,ellipse
 
-flat varying vec4 vColor0;
-flat varying vec4 vColor1;
+// For edges, the colors are the same. For corners, these
+// are the colors of each edge making up the corner.
+flat varying vec4 vColor0[2];
+flat varying vec4 vColor1[2];
+
+// A point + tangent defining the line where the edge
+// transition occurs. Used for corners only.
 flat varying vec4 vColorLine;
-flat varying int vFeatures;
-flat varying vec2 vClipCenter;
+
+// x = segment, y = styles, z = edge axes
+flat varying ivec3 vConfig;
+
+// xy = Local space position of the clip center.
+// zw = Scale the rect origin by this to get the outer
+// corner from the segment rectangle.
+flat varying vec4 vClipCenter_Sign;
+
+// An outer and inner elliptical radii for border
+// corner clipping.
 flat varying vec4 vClipRadii;
-flat varying vec2 vClipSign;
+
+// Reference point for determine edge clip lines.
+flat varying vec4 vEdgeReference;
 
+// Stores widths/2 and widths/3 to save doing this in FS.
+flat varying vec4 vPartialWidths;
+
+// Local space position
 varying vec2 vPos;
 
-#define CLIP_RADII      1
-#define MIX_COLOR       2
+#define SEGMENT_TOP_LEFT        0
+#define SEGMENT_TOP_RIGHT       1
+#define SEGMENT_BOTTOM_RIGHT    2
+#define SEGMENT_BOTTOM_LEFT     3
+#define SEGMENT_LEFT            4
+#define SEGMENT_TOP             5
+#define SEGMENT_RIGHT           6
+#define SEGMENT_BOTTOM          7
+
+// Border styles as defined in webrender_api/types.rs
+#define BORDER_STYLE_NONE         0
+#define BORDER_STYLE_SOLID        1
+#define BORDER_STYLE_DOUBLE       2
+#define BORDER_STYLE_DOTTED       3
+#define BORDER_STYLE_DASHED       4
+#define BORDER_STYLE_HIDDEN       5
+#define BORDER_STYLE_GROOVE       6
+#define BORDER_STYLE_RIDGE        7
+#define BORDER_STYLE_INSET        8
+#define BORDER_STYLE_OUTSET       9
 
 #ifdef WR_VERTEX_SHADER
 
 in vec2 aTaskOrigin;
 in vec4 aRect;
 in vec4 aColor0;
 in vec4 aColor1;
 in int aFlags;
 in vec2 aWidths;
 in vec2 aRadii;
 
-#define SEGMENT_TOP_LEFT        0
-#define SEGMENT_TOP_RIGHT       1
-#define SEGMENT_BOTTOM_RIGHT    2
-#define SEGMENT_BOTTOM_LEFT     3
-#define SEGMENT_LEFT            4
-#define SEGMENT_TOP             5
-#define SEGMENT_RIGHT           6
-#define SEGMENT_BOTTOM          7
-
 vec2 get_outer_corner_scale(int segment) {
     vec2 p;
 
     switch (segment) {
         case SEGMENT_TOP_LEFT:
             p = vec2(0.0, 0.0);
             break;
         case SEGMENT_TOP_RIGHT:
@@ -56,72 +85,257 @@ vec2 get_outer_corner_scale(int segment)
             // Should never get hit
             p = vec2(0.0);
             break;
     }
 
     return p;
 }
 
-void main(void) {
-    vec2 pos = aRect.xy + aRect.zw * aPosition.xy;
+vec4 mod_color(vec4 color, float f) {
+    return vec4(clamp(color.rgb * f, vec3(0.0), vec3(color.a)), color.a);
+}
+
+vec4[2] get_colors_for_side(vec4 color, int style) {
+    vec4 result[2];
+    const vec2 f = vec2(1.3, 0.7);
 
+    switch (style) {
+        case BORDER_STYLE_GROOVE:
+            result[0] = mod_color(color, f.x);
+            result[1] = mod_color(color, f.y);
+            break;
+        case BORDER_STYLE_RIDGE:
+            result[0] = mod_color(color, f.y);
+            result[1] = mod_color(color, f.x);
+            break;
+        default:
+            result[0] = color;
+            result[1] = color;
+            break;
+    }
+
+    return result;
+}
+
+void main(void) {
     int segment = aFlags & 0xff;
-    int style = (aFlags >> 8) & 0xff;
+    int style0 = (aFlags >> 8) & 0xff;
+    int style1 = (aFlags >> 16) & 0xff;
 
     vec2 outer_scale = get_outer_corner_scale(segment);
-    vec2 outer = aRect.xy + outer_scale * aRect.zw;
+    vec2 outer = outer_scale * aRect.zw;
     vec2 clip_sign = 1.0 - 2.0 * outer_scale;
 
-    vColor0 = aColor0;
-    vColor1 = aColor1;
-    vPos = pos;
-
-    vFeatures = 0;
-    vClipSign = clip_sign;
-    vClipCenter = outer + clip_sign * aRadii;
-    vClipRadii = vec4(aRadii, aRadii - aWidths);
-    vColorLine = vec4(0.0);
-
+    // Set some flags used by the FS to determine the
+    // orientation of the two edges in this corner.
+    ivec2 edge_axis;
+    // Derive the positions for the edge clips, which must be handled
+    // differently between corners and edges.
+    vec2 edge_reference;
     switch (segment) {
         case SEGMENT_TOP_LEFT:
+            edge_axis = ivec2(0, 1);
+            edge_reference = outer;
+            break;
         case SEGMENT_TOP_RIGHT:
+            edge_axis = ivec2(1, 0);
+            edge_reference = vec2(outer.x - aWidths.x, outer.y);
+            break;
         case SEGMENT_BOTTOM_RIGHT:
+            edge_axis = ivec2(0, 1);
+            edge_reference = outer - aWidths;
+            break;
         case SEGMENT_BOTTOM_LEFT:
-            vFeatures |= (CLIP_RADII | MIX_COLOR);
-            vColorLine = vec4(outer, aWidths.y * -clip_sign.y, aWidths.x * clip_sign.x);
+            edge_axis = ivec2(1, 0);
+            edge_reference = vec2(outer.x, outer.y - aWidths.y);
+            break;
+        case SEGMENT_TOP:
+        case SEGMENT_BOTTOM:
+            edge_axis = ivec2(1, 1);
+            edge_reference = vec2(0.0);
+            break;
+        case SEGMENT_LEFT:
+        case SEGMENT_RIGHT:
+        default:
+            edge_axis = ivec2(0, 0);
+            edge_reference = vec2(0.0);
             break;
+    }
+
+    vConfig = ivec3(
+        segment,
+        style0 | (style1 << 16),
+        edge_axis.x | (edge_axis.y << 16)
+    );
+    vPartialWidths = vec4(aWidths / 3.0, aWidths / 2.0);
+    vPos = aRect.zw * aPosition.xy;
+
+    vColor0 = get_colors_for_side(aColor0, style0);
+    vColor1 = get_colors_for_side(aColor1, style1);
+    vClipCenter_Sign = vec4(outer + clip_sign * aRadii, clip_sign);
+    vClipRadii = vec4(aRadii, max(aRadii - aWidths, 0.0));
+    vColorLine = vec4(outer, aWidths.y * -clip_sign.y, aWidths.x * clip_sign.x);
+    vEdgeReference = vec4(edge_reference, edge_reference + aWidths);
+
+    gl_Position = uTransform * vec4(aTaskOrigin + aRect.xy + vPos, 0.0, 1.0);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+vec4 evaluate_color_for_style_in_corner(
+    vec2 clip_relative_pos,
+    int style,
+    vec4 color[2],
+    vec4 clip_radii,
+    float mix_factor,
+    int segment,
+    float aa_range
+) {
+    switch (style) {
+        case BORDER_STYLE_DOUBLE: {
+            // Get the distances from 0.33 of the radii, and
+            // also 0.67 of the radii. Use these to form a
+            // SDF subtraction which will clip out the inside
+            // third of the rounded edge.
+            float d_radii_a = distance_to_ellipse(
+                clip_relative_pos,
+                clip_radii.xy - vPartialWidths.xy,
+                aa_range
+            );
+            float d_radii_b = distance_to_ellipse(
+                clip_relative_pos,
+                clip_radii.xy - 2.0 * vPartialWidths.xy,
+                aa_range
+            );
+            float d = min(-d_radii_a, d_radii_b);
+            float alpha = distance_aa(aa_range, d);
+            return alpha * color[0];
+        }
+        case BORDER_STYLE_GROOVE:
+        case BORDER_STYLE_RIDGE: {
+            float d = distance_to_ellipse(
+                clip_relative_pos,
+                clip_radii.xy - vPartialWidths.zw,
+                aa_range
+            );
+            float alpha = distance_aa(aa_range, d);
+            float swizzled_factor;
+            switch (segment) {
+                case SEGMENT_TOP_LEFT: swizzled_factor = 0.0; break;
+                case SEGMENT_TOP_RIGHT: swizzled_factor = mix_factor; break;
+                case SEGMENT_BOTTOM_RIGHT: swizzled_factor = 1.0; break;
+                case SEGMENT_BOTTOM_LEFT: swizzled_factor = 1.0 - mix_factor; break;
+                default: swizzled_factor = 0.0; break;
+            };
+            vec4 c0 = mix(color[1], color[0], swizzled_factor);
+            vec4 c1 = mix(color[0], color[1], swizzled_factor);
+            return mix(c0, c1, alpha);
+        }
         default:
             break;
     }
 
-    gl_Position = uTransform * vec4(aTaskOrigin + pos, 0.0, 1.0);
+    return color[0];
 }
-#endif
 
-#ifdef WR_FRAGMENT_SHADER
+vec4 evaluate_color_for_style_in_edge(
+    vec2 pos,
+    int style,
+    vec4 color[2],
+    float aa_range,
+    int edge_axis
+) {
+    switch (style) {
+        case BORDER_STYLE_DOUBLE: {
+            float d0 = -1.0;
+            float d1 = -1.0;
+            if (vPartialWidths[edge_axis] > 1.0) {
+                vec2 ref = vec2(
+                    vEdgeReference[edge_axis] + vPartialWidths[edge_axis],
+                    vEdgeReference[edge_axis+2] - vPartialWidths[edge_axis]
+                );
+                d0 = pos[edge_axis] - ref.x;
+                d1 = ref.y - pos[edge_axis];
+            }
+            float d = min(d0, d1);
+            float alpha = distance_aa(aa_range, d);
+            return alpha * color[0];
+        }
+        case BORDER_STYLE_GROOVE:
+        case BORDER_STYLE_RIDGE: {
+            float ref = vEdgeReference[edge_axis] + vPartialWidths[edge_axis+2];
+            float d = pos[edge_axis] - ref;
+            float alpha = distance_aa(aa_range, d);
+            return mix(color[0], color[1], alpha);
+        }
+        default:
+            break;
+    }
+
+    return color[0];
+}
+
 void main(void) {
     float aa_range = compute_aa_range(vPos);
     float d = -1.0;
+    vec4 color0, color1;
 
-    // Apply color mix
+    int segment = vConfig.x;
+    ivec2 style = ivec2(vConfig.y & 0xffff, vConfig.y >> 16);
+    ivec2 edge_axis = ivec2(vConfig.z & 0xffff, vConfig.z >> 16);
+
     float mix_factor = 0.0;
-    if ((vFeatures & MIX_COLOR) != 0) {
+    if (edge_axis.x != edge_axis.y) {
         float d_line = distance_to_line(vColorLine.xy, vColorLine.zw, vPos);
         mix_factor = distance_aa(aa_range, -d_line);
     }
 
-    // Apply clip radii
-    if ((vFeatures & CLIP_RADII) != 0) {
-        vec2 p = vPos - vClipCenter;
-        if (vClipSign.x * p.x < 0.0 && vClipSign.y * p.y < 0.0) {
-            float d_radii_a = distance_to_ellipse(p, vClipRadii.xy, aa_range);
-            float d_radii_b = distance_to_ellipse(p, vClipRadii.zw, aa_range);
-            float d_radii = max(d_radii_a, -d_radii_b);
-            d = max(d, d_radii);
-        }
+    // Check if inside corner clip-region
+    vec2 clip_relative_pos = vPos - vClipCenter_Sign.xy;
+    bool in_clip_region = all(lessThan(vClipCenter_Sign.zw * clip_relative_pos, vec2(0.0)));
+
+    if (in_clip_region) {
+        float d_radii_a = distance_to_ellipse(clip_relative_pos, vClipRadii.xy, aa_range);
+        float d_radii_b = distance_to_ellipse(clip_relative_pos, vClipRadii.zw, aa_range);
+        float d_radii = max(d_radii_a, -d_radii_b);
+        d = max(d, d_radii);
+
+        color0 = evaluate_color_for_style_in_corner(
+            clip_relative_pos,
+            style.x,
+            vColor0,
+            vClipRadii,
+            mix_factor,
+            segment,
+            aa_range
+        );
+        color1 = evaluate_color_for_style_in_corner(
+            clip_relative_pos,
+            style.y,
+            vColor1,
+            vClipRadii,
+            mix_factor,
+            segment,
+            aa_range
+        );
+    } else {
+        color0 = evaluate_color_for_style_in_edge(
+            vPos,
+            style.x,
+            vColor0,
+            aa_range,
+            edge_axis.x
+        );
+        color1 = evaluate_color_for_style_in_edge(
+            vPos,
+            style.y,
+            vColor1,
+            aa_range,
+            edge_axis.y
+        );
     }
 
     float alpha = distance_aa(aa_range, d);
-    vec4 color = mix(vColor0, vColor1, mix_factor);
+    vec4 color = mix(color0, color1, mix_factor);
     oFragColor = color * alpha;
 }
 #endif
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -20,21 +20,16 @@ uniform sampler2DArray sCacheRGBA8;
 
 // An A8 target for standalone tasks that is available to all passes.
 uniform sampler2DArray sSharedCacheA8;
 
 vec2 clamp_rect(vec2 pt, RectWithSize rect) {
     return clamp(pt, rect.p0, rect.p0 + rect.size);
 }
 
-float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) {
-    vec2 dir_to_p0 = p0 - p;
-    return dot(normalize(perp_dir), dir_to_p0);
-}
-
 // TODO: convert back to RectWithEndPoint if driver issues are resolved, if ever.
 flat varying vec4 vClipMaskUvBounds;
 varying vec3 vClipMaskUv;
 
 
 #ifdef WR_VERTEX_SHADER
 
 #define VECS_PER_LOCAL_CLIP_RECT    1
--- a/gfx/webrender/res/shared.glsl
+++ b/gfx/webrender/res/shared.glsl
@@ -51,16 +51,71 @@
 
     // Fragment shader outputs
     #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
         layout(location = 0, index = 0) out vec4 oFragColor;
         layout(location = 0, index = 1) out vec4 oFragBlend;
     #else
         out vec4 oFragColor;
     #endif
+
+    #define EPSILON     0.0001
+
+    float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) {
+        vec2 dir_to_p0 = p0 - p;
+        return dot(normalize(perp_dir), dir_to_p0);
+    }
+
+    /// Find the appropriate half range to apply the AA approximation over.
+    /// This range represents a coefficient to go from one CSS pixel to half a device pixel.
+    float compute_aa_range(vec2 position) {
+        // The constant factor is chosen to compensate for the fact that length(fw) is equal
+        // to sqrt(2) times the device pixel ratio in the typical case. 0.5/sqrt(2) = 0.35355.
+        //
+        // This coefficient is chosen to ensure that any sample 0.5 pixels or more inside of
+        // the shape has no anti-aliasing applied to it (since pixels are sampled at their center,
+        // such a pixel (axis aligned) is fully inside the border). We need this so that antialiased
+        // curves properly connect with non-antialiased vertical or horizontal lines, among other things.
+        //
+        // Lines over a half-pixel away from the pixel center *can* intersect with the pixel square;
+        // indeed, unless they are horizontal or vertical, they are guaranteed to. However, choosing
+        // a nonzero area for such pixels causes noticeable artifacts at the junction between an anti-
+        // aliased corner and a straight edge.
+        //
+        // We may want to adjust this constant in specific scenarios (for example keep the principled
+        // value for straight edges where we want pixel-perfect equivalence with non antialiased lines
+        // when axis aligned, while selecting a larger and smoother aa range on curves).
+        return 0.35355 * length(fwidth(position));
+    }
+
+    /// Return the blending coefficient for distance antialiasing.
+    ///
+    /// 0.0 means inside the shape, 1.0 means outside.
+    ///
+    /// This cubic polynomial approximates the area of a 1x1 pixel square under a
+    /// line, given the signed Euclidean distance from the center of the square to
+    /// that line. Calculating the *exact* area would require taking into account
+    /// not only this distance but also the angle of the line. However, in
+    /// practice, this complexity is not required, as the area is roughly the same
+    /// regardless of the angle.
+    ///
+    /// The coefficients of this polynomial were determined through least-squares
+    /// regression and are accurate to within 2.16% of the total area of the pixel
+    /// square 95% of the time, with a maximum error of 3.53%.
+    ///
+    /// See the comments in `compute_aa_range()` for more information on the
+    /// cutoff values of -0.5 and 0.5.
+    float distance_aa(float aa_range, float signed_distance) {
+        float dist = 0.5 * signed_distance / aa_range;
+        if (dist <= -0.5 + EPSILON)
+            return 1.0;
+        if (dist >= 0.5 - EPSILON)
+            return 0.0;
+        return 0.5 + dist * (0.8431027 * dist * dist - 1.14453603);
+    }
 #endif
 
 //======================================================================================
 // Shared shader uniforms
 //======================================================================================
 #ifdef WR_FEATURE_TEXTURE_2D
 uniform sampler2D sColor0;
 uniform sampler2D sColor1;
--- a/gfx/webrender/res/shared_border.glsl
+++ b/gfx/webrender/res/shared_border.glsl
@@ -1,26 +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/. */
 
-#ifdef WR_VERTEX_SHADER
-
 // Border styles as defined in webrender_api/types.rs
 #define BORDER_STYLE_NONE         0
 #define BORDER_STYLE_SOLID        1
 #define BORDER_STYLE_DOUBLE       2
 #define BORDER_STYLE_DOTTED       3
 #define BORDER_STYLE_DASHED       4
 #define BORDER_STYLE_HIDDEN       5
 #define BORDER_STYLE_GROOVE       6
 #define BORDER_STYLE_RIDGE        7
 #define BORDER_STYLE_INSET        8
 #define BORDER_STYLE_OUTSET       9
 
+#ifdef WR_VERTEX_SHADER
+
 struct Border {
     vec4 style;
     vec4 widths;
     vec4 colors[4];
     vec4 radii[2];
 };
 
 struct BorderCorners {
--- a/gfx/webrender/res/transform.glsl
+++ b/gfx/webrender/res/transform.glsl
@@ -1,74 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#define EPSILON     0.0001
-
 flat varying vec4 vTransformBounds;
 
 #ifdef WR_VERTEX_SHADER
 
 void init_transform_vs(vec4 local_bounds) {
     vTransformBounds = local_bounds;
 }
 
 #endif //WR_VERTEX_SHADER
 
 #ifdef WR_FRAGMENT_SHADER
 
-/// Find the appropriate half range to apply the AA approximation over.
-/// This range represents a coefficient to go from one CSS pixel to half a device pixel.
-float compute_aa_range(vec2 position) {
-    // The constant factor is chosen to compensate for the fact that length(fw) is equal
-    // to sqrt(2) times the device pixel ratio in the typical case. 0.5/sqrt(2) = 0.35355.
-    //
-    // This coefficient is chosen to ensure that any sample 0.5 pixels or more inside of
-    // the shape has no anti-aliasing applied to it (since pixels are sampled at their center,
-    // such a pixel (axis aligned) is fully inside the border). We need this so that antialiased
-    // curves properly connect with non-antialiased vertical or horizontal lines, among other things.
-    //
-    // Lines over a half-pixel away from the pixel center *can* intersect with the pixel square;
-    // indeed, unless they are horizontal or vertical, they are guaranteed to. However, choosing
-    // a nonzero area for such pixels causes noticeable artifacts at the junction between an anti-
-    // aliased corner and a straight edge.
-    //
-    // We may want to adjust this constant in specific scenarios (for example keep the principled
-    // value for straight edges where we want pixel-perfect equivalence with non antialiased lines
-    // when axis aligned, while selecting a larger and smoother aa range on curves).
-    return 0.35355 * length(fwidth(position));
-}
-
-/// Return the blending coefficient for distance antialiasing.
-///
-/// 0.0 means inside the shape, 1.0 means outside.
-///
-/// This cubic polynomial approximates the area of a 1x1 pixel square under a
-/// line, given the signed Euclidean distance from the center of the square to
-/// that line. Calculating the *exact* area would require taking into account
-/// not only this distance but also the angle of the line. However, in
-/// practice, this complexity is not required, as the area is roughly the same
-/// regardless of the angle.
-///
-/// The coefficients of this polynomial were determined through least-squares
-/// regression and are accurate to within 2.16% of the total area of the pixel
-/// square 95% of the time, with a maximum error of 3.53%.
-///
-/// See the comments in `compute_aa_range()` for more information on the
-/// cutoff values of -0.5 and 0.5.
-float distance_aa(float aa_range, float signed_distance) {
-    float dist = 0.5 * signed_distance / aa_range;
-    if (dist <= -0.5 + EPSILON)
-        return 1.0;
-    if (dist >= 0.5 - EPSILON)
-        return 0.0;
-    return 0.5 + dist * (0.8431027 * dist * dist - 1.14453603);
-}
-
 float signed_distance_rect(vec2 pos, vec2 p0, vec2 p1) {
     vec2 d = max(p0 - pos, pos - p1);
     return length(max(vec2(0.0), d)) + min(0.0, max(d.x, d.y));
 }
 
 float init_transform_fs(vec2 local_pos) {
     // Get signed distance from local rect bounds.
     float d = signed_distance_rect(
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -466,26 +466,26 @@ impl<'a> DisplayListFlattener<'a> {
         let top = &border.top;
         let bottom = &border.bottom;
 
         let brush_border_supported = [left, top, right, bottom].iter().all(|edge| {
             match edge.style {
                 BorderStyle::Solid |
                 BorderStyle::Hidden |
                 BorderStyle::None |
+                BorderStyle::Double |
                 BorderStyle::Inset |
+                BorderStyle::Groove |
+                BorderStyle::Ridge |
                 BorderStyle::Outset => {
                     true
                 }
 
-                BorderStyle::Double |
                 BorderStyle::Dotted |
-                BorderStyle::Dashed |
-                BorderStyle::Groove |
-                BorderStyle::Ridge => {
+                BorderStyle::Dashed => {
                     false
                 }
             }
         });
 
         if brush_border_supported {
             let prim = BrushPrimitive::new(
                 BrushKind::Border {
@@ -1011,31 +1011,38 @@ struct DotInfo {
 
 impl DotInfo {
     fn new(arc_pos: f32, diameter: f32) -> DotInfo {
         DotInfo { arc_pos, diameter }
     }
 }
 
 #[derive(Debug)]
+pub struct BorderSegmentInfo {
+    task_rect: DeviceRect,
+    segment: BorderSegment,
+    radius: DeviceSize,
+    widths: DeviceSize,
+}
+
+#[derive(Debug)]
 pub struct BorderRenderTaskInfo {
-    pub instances: Vec<BorderInstance>,
-    pub segments: Vec<BrushSegment>,
+    pub border_segments: Vec<BorderSegmentInfo>,
     pub size: DeviceIntSize,
 }
 
 impl BorderRenderTaskInfo {
     pub fn new(
         rect: &LayoutRect,
         border: &NormalBorder,
         widths: &BorderWidths,
         scale: LayoutToDeviceScale,
+        brush_segments: &mut Vec<BrushSegment>,
     ) -> Self {
-        let mut instances = Vec::new();
-        let mut segments = Vec::new();
+        let mut border_segments = Vec::new();
 
         let dp_width_top = (widths.top * scale.0).ceil();
         let dp_width_bottom = (widths.bottom * scale.0).ceil();
         let dp_width_left = (widths.left * scale.0).ceil();
         let dp_width_right = (widths.right * scale.0).ceil();
 
         let dp_corner_tl = (border.radius.top_left * scale).ceil();
         let dp_corner_tr = (border.radius.top_right * scale).ceil();
@@ -1083,219 +1090,258 @@ impl BorderRenderTaskInfo {
         let width_inner = 16.0;
         let height_inner = 16.0;
 
         let size = DeviceSize::new(
             dp_size_tl.width.max(dp_size_bl.width) + width_inner + dp_size_tr.width.max(dp_size_br.width),
             dp_size_tl.height.max(dp_size_tr.height) + height_inner + dp_size_bl.height.max(dp_size_br.height),
         );
 
-        // These modulate colors are not part of the specification. They
-        // are derived from the Gecko source code and experimentation, and
-        // used to modulate the colors in order to generate colors for
-        // the inset/outset and groove/ridge border styles.
-        let left_color = border.left.border_color(1.0, 2.0 / 3.0, 0.3, 0.7);
-        let top_color = border.top.border_color(1.0, 2.0 / 3.0, 0.3, 0.7);
-        let right_color = border.right.border_color(2.0 / 3.0, 1.0, 0.7, 0.3);
-        let bottom_color = border.bottom.border_color(2.0 / 3.0, 1.0, 0.7, 0.3);
-
         add_edge_segment(
             LayoutRect::from_floats(
                 rect.origin.x,
                 rect.origin.y + local_size_tl.height,
                 rect.origin.x + widths.left,
                 rect.origin.y + rect.size.height - local_size_bl.height,
             ),
             DeviceRect::from_floats(
                 0.0,
                 dp_size_tl.height,
                 dp_width_left,
                 size.height - dp_size_bl.height,
             ),
-            border.left.style,
-            left_color,
+            &border.left,
             BorderSegment::Left,
             EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT,
-            &mut instances,
+            &mut border_segments,
             BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_Y,
-            &mut segments,
+            brush_segments,
         );
 
         add_edge_segment(
             LayoutRect::from_floats(
                 rect.origin.x + local_size_tl.width,
                 rect.origin.y,
                 rect.origin.x + rect.size.width - local_size_tr.width,
                 rect.origin.y + widths.top,
             ),
             DeviceRect::from_floats(
                 dp_size_tl.width,
                 0.0,
                 size.width - dp_size_tr.width,
                 dp_width_top,
             ),
-            border.top.style,
-            top_color,
+            &border.top,
             BorderSegment::Top,
             EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM,
-            &mut instances,
+            &mut border_segments,
             BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_X,
-            &mut segments,
+            brush_segments,
         );
 
         add_edge_segment(
             LayoutRect::from_floats(
                 rect.origin.x + rect.size.width - widths.right,
                 rect.origin.y + local_size_tr.height,
                 rect.origin.x + rect.size.width,
                 rect.origin.y + rect.size.height - local_size_br.height,
             ),
             DeviceRect::from_floats(
                 size.width - dp_width_right,
                 dp_size_tr.height,
                 size.width,
                 size.height - dp_size_br.height,
             ),
-            border.right.style,
-            right_color,
+            &border.right,
             BorderSegment::Right,
             EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT,
-            &mut instances,
+            &mut border_segments,
             BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_Y,
-            &mut segments,
+            brush_segments,
         );
 
         add_edge_segment(
             LayoutRect::from_floats(
                 rect.origin.x + local_size_bl.width,
                 rect.origin.y + rect.size.height - widths.bottom,
                 rect.origin.x + rect.size.width - local_size_br.width,
                 rect.origin.y + rect.size.height,
             ),
             DeviceRect::from_floats(
                 dp_size_bl.width,
                 size.height - dp_width_bottom,
                 size.width - dp_size_br.width,
                 size.height,
             ),
-            border.bottom.style,
-            bottom_color,
+            &border.bottom,
             BorderSegment::Bottom,
             EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP,
-            &mut instances,
+            &mut border_segments,
             BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_X,
-            &mut segments,
+            brush_segments,
         );
 
         add_corner_segment(
             LayoutRect::from_floats(
                 rect.origin.x,
                 rect.origin.y,
                 rect.origin.x + local_size_tl.width,
                 rect.origin.y + local_size_tl.height,
             ),
             DeviceRect::from_floats(
                 0.0,
                 0.0,
                 dp_size_tl.width,
                 dp_size_tl.height,
             ),
-            border.left.style,
-            left_color,
-            border.top.style,
-            top_color,
+            &border.left,
+            &border.top,
             DeviceSize::new(dp_width_left, dp_width_top),
             dp_corner_tl,
             BorderSegment::TopLeft,
             EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT,
-            &mut instances,
-            &mut segments,
+            &mut border_segments,
+            brush_segments,
         );
 
         add_corner_segment(
             LayoutRect::from_floats(
                 rect.origin.x + rect.size.width - local_size_tr.width,
                 rect.origin.y,
                 rect.origin.x + rect.size.width,
                 rect.origin.y + local_size_tr.height,
             ),
             DeviceRect::from_floats(
                 size.width - dp_size_tr.width,
                 0.0,
                 size.width,
                 dp_size_tr.height,
             ),
-            border.top.style,
-            top_color,
-            border.right.style,
-            right_color,
+            &border.top,
+            &border.right,
             DeviceSize::new(dp_width_right, dp_width_top),
             dp_corner_tr,
             BorderSegment::TopRight,
             EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT,
-            &mut instances,
-            &mut segments,
+            &mut border_segments,
+            brush_segments,
         );
 
         add_corner_segment(
             LayoutRect::from_floats(
                 rect.origin.x + rect.size.width - local_size_br.width,
                 rect.origin.y + rect.size.height - local_size_br.height,
                 rect.origin.x + rect.size.width,
                 rect.origin.y + rect.size.height,
             ),
             DeviceRect::from_floats(
                 size.width - dp_size_br.width,
                 size.height - dp_size_br.height,
                 size.width,
                 size.height,
             ),
-            border.right.style,
-            right_color,
-            border.bottom.style,
-            bottom_color,
+            &border.right,
+            &border.bottom,
             DeviceSize::new(dp_width_right, dp_width_bottom),
             dp_corner_br,
             BorderSegment::BottomRight,
             EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT,
-            &mut instances,
-            &mut segments,
+            &mut border_segments,
+            brush_segments,
         );
 
         add_corner_segment(
             LayoutRect::from_floats(
                 rect.origin.x,
                 rect.origin.y + rect.size.height - local_size_bl.height,
                 rect.origin.x + local_size_bl.width,
                 rect.origin.y + rect.size.height,
             ),
             DeviceRect::from_floats(
                 0.0,
                 size.height - dp_size_bl.height,
                 dp_size_bl.width,
                 size.height,
             ),
-            border.bottom.style,
-            bottom_color,
-            border.left.style,
-            left_color,
+            &border.bottom,
+            &border.left,
             DeviceSize::new(dp_width_left, dp_width_bottom),
             dp_corner_bl,
             BorderSegment::BottomLeft,
             EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::LEFT,
-            &mut instances,
-            &mut segments,
+            &mut border_segments,
+            brush_segments,
         );
 
         BorderRenderTaskInfo {
-            segments,
-            instances,
+            border_segments,
             size: size.to_i32(),
         }
     }
+
+    pub fn build_instances(
+        &self,
+        border: &NormalBorder,
+    ) -> Vec<BorderInstance> {
+        let mut instances = Vec::new();
+
+        for info in &self.border_segments {
+            let (side0, side1, flip0, flip1) = match info.segment {
+                BorderSegment::Left => (&border.left, &border.left, false, false),
+                BorderSegment::Top => (&border.top, &border.top, false, false),
+                BorderSegment::Right => (&border.right, &border.right, true, true),
+                BorderSegment::Bottom => (&border.bottom, &border.bottom, true, true),
+                BorderSegment::TopLeft => (&border.left, &border.top, false, false),
+                BorderSegment::TopRight => (&border.top, &border.right, false, true),
+                BorderSegment::BottomRight => (&border.right, &border.bottom, true, true),
+                BorderSegment::BottomLeft => (&border.bottom, &border.left, true, false),
+            };
+
+            let style0 = if side0.style.is_hidden() {
+                side1.style
+            } else {
+                side0.style
+            };
+            let style1 = if side1.style.is_hidden() {
+                side0.style
+            } else {
+                side1.style
+            };
+
+            // These modulate colors are not part of the specification. They
+            // are derived from the Gecko source code and experimentation, and
+            // used to modulate the colors in order to generate colors for
+            // the inset/outset and groove/ridge border styles.
+            let color0 = if flip0 {
+                side0.border_color(2.0 / 3.0, 1.0, 0.7, 0.3)
+            } else {
+                side0.border_color(1.0, 2.0 / 3.0, 0.3, 0.7)
+            };
+
+            let color1 = if flip1 {
+                side1.border_color(2.0 / 3.0, 1.0, 0.7, 0.3)
+            } else {
+                side1.border_color(1.0, 2.0 / 3.0, 0.3, 0.7)
+            };
+
+            add_segment(
+                info.task_rect,
+                style0,
+                style1,
+                color0,
+                color1,
+                info.segment,
+                &mut instances,
+                info.widths,
+                info.radius,
+            );
+        }
+
+        instances
+    }
 }
 
 fn add_brush_segment(
     image_rect: LayoutRect,
     task_rect: DeviceRect,
     brush_flags: BrushFlags,
     edge_flags: EdgeAaSegmentMask,
     brush_segments: &mut Vec<BrushSegment>,
@@ -1342,105 +1388,77 @@ fn add_segment(
     };
 
     instances.push(base_instance);
 }
 
 fn add_corner_segment(
     image_rect: LayoutRect,
     task_rect: DeviceRect,
-    mut style0: BorderStyle,
-    color0: ColorF,
-    mut style1: BorderStyle,
-    color1: ColorF,
+    side0: &BorderSide,
+    side1: &BorderSide,
     widths: DeviceSize,
     radius: DeviceSize,
     segment: BorderSegment,
     edge_flags: EdgeAaSegmentMask,
-    instances: &mut Vec<BorderInstance>,
+    border_segments: &mut Vec<BorderSegmentInfo>,
     brush_segments: &mut Vec<BrushSegment>,
 ) {
-    // TODO(gw): This will need to be a bit more involved when
-    //           we support other border types here. For example,
-    //           groove / ridge borders will always need to
-    //           use two instances.
-
-    if color0.a <= 0.0 && color1.a <= 0.0 {
+    if side0.color.a <= 0.0 && side1.color.a <= 0.0 {
         return;
     }
 
     if widths.width <= 0.0 && widths.height <= 0.0 {
         return;
     }
 
-    let style0_hidden = style0 == BorderStyle::Hidden || style0 == BorderStyle::None;
-    let style1_hidden = style1 == BorderStyle::Hidden || style1 == BorderStyle::None;
-
-    if style0_hidden && style1_hidden {
+    if side0.style.is_hidden() && side1.style.is_hidden() {
         return;
     }
 
-    if style0_hidden {
-        style0 = style1;
-    }
-    if style1_hidden {
-        style1 = style0;
-    }
-
-    add_segment(
+    border_segments.push(BorderSegmentInfo {
         task_rect,
-        style0,
-        style1,
-        color0,
-        color1,
         segment,
-        instances,
+        radius,
         widths,
-        radius,
-    );
+    });
 
     add_brush_segment(
         image_rect,
         task_rect,
         BrushFlags::SEGMENT_RELATIVE,
         edge_flags,
         brush_segments,
     );
 }
 
 fn add_edge_segment(
     image_rect: LayoutRect,
     task_rect: DeviceRect,
-    style: BorderStyle,
-    color: ColorF,
+    side: &BorderSide,
     segment: BorderSegment,
     edge_flags: EdgeAaSegmentMask,
-    instances: &mut Vec<BorderInstance>,
+    border_segments: &mut Vec<BorderSegmentInfo>,
     brush_flags: BrushFlags,
     brush_segments: &mut Vec<BrushSegment>,
 ) {
-    if color.a <= 0.0 {
+    if side.color.a <= 0.0 {
         return;
     }
 
-    if style == BorderStyle::Hidden || style == BorderStyle::None {
+    if side.style.is_hidden() {
         return;
     }
 
-    add_segment(
+    border_segments.push(BorderSegmentInfo {
         task_rect,
-        style,
-        style,
-        color,
-        color,
         segment,
-        instances,
-        DeviceSize::zero(),
-        DeviceSize::zero(),
-    );
+        radius: DeviceSize::zero(),
+        widths: task_rect.size,
+    });
 
     add_brush_segment(
         image_rect,
         task_rect,
         brush_flags,
         edge_flags,
         brush_segments,
     );
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -3,20 +3,20 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, ClipAndScrollInfo};
 use api::{ClipId, ColorF, ComplexClipRegion, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DevicePixelScale, DeviceUintRect, DisplayItemRef, Epoch, ExtendMode, ExternalScrollId};
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, GlyphRasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint};
-use api::{LayoutPrimitiveInfo, LayoutRect, LayoutVector2D, LayoutSize, LayoutTransform};
+use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
 use api::{LineOrientation, LineStyle, LocalClip, NinePatchBorderSource, PipelineId};
-use api::{PropertyBinding, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity, Shadow};
-use api::{SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
+use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity};
+use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
 use api::{TransformStyle, YuvColorSpace, YuvData};
 use app_units::Au;
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
 use clip_scroll_node::{ClipScrollNode, NodeType, StickyFrameInfo};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
 use euclid::{SideOffsets2D, vec2};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
@@ -152,20 +152,16 @@ pub struct DisplayListFlattener<'a> {
 
     /// Used to track the latest flattened epoch for each pipeline.
     pipeline_epochs: Vec<(PipelineId, Epoch)>,
 
     /// A set of pipelines that the caller has requested be made available as
     /// output textures.
     output_pipelines: &'a FastHashSet<PipelineId>,
 
-    /// A list of replacements to make in order to properly handle fixed position
-    /// content as well as stacking contexts that create reference frames.
-    replacements: Vec<(ClipId, ClipId)>,
-
     /// The data structure that converting between ClipId and the various index
     /// types that the ClipScrollTree uses.
     id_to_index_mapper: ClipIdToIndexMapper,
 
     /// A stack of scroll nodes used during display list processing to properly
     /// parent new scroll nodes.
     reference_frame_stack: Vec<(ClipId, ClipScrollNodeIndex)>,
 
@@ -220,17 +216,16 @@ impl<'a> DisplayListFlattener<'a> {
             .and_then(|color| if color.a > 0.0 { Some(color) } else { None });
 
         let mut flattener = DisplayListFlattener {
             scene,
             clip_scroll_tree,
             font_instances,
             config: *frame_builder_config,
             pipeline_epochs: Vec::new(),
-            replacements: Vec::new(),
             output_pipelines,
             id_to_index_mapper: ClipIdToIndexMapper::default(),
             hit_testing_runs: recycle_vec(old_builder.hit_testing_runs),
             cached_gradients: recycle_vec(old_builder.cached_gradients),
             scrollbar_prims: recycle_vec(old_builder.scrollbar_prims),
             reference_frame_stack: Vec::new(),
             picture_stack: Vec::new(),
             shadow_stack: Vec::new(),
@@ -258,61 +253,36 @@ impl<'a> DisplayListFlattener<'a> {
         FrameBuilder::with_display_list_flattener(
             view.inner_rect,
             background_color,
             view.window_size,
             flattener
         )
     }
 
-    /// Since WebRender still handles fixed position and reference frame content internally
-    /// we need to apply this table of id replacements only to the id that affects the
-    /// position of a node. We can eventually remove this when clients start handling
-    /// reference frames themselves. This method applies these replacements.
-    fn apply_scroll_frame_id_replacement(&self, index: ClipId) -> ClipId {
-        match self.replacements.last() {
-            Some(&(to_replace, replacement)) if to_replace == index => replacement,
-            _ => index,
-        }
-    }
-
     fn get_complex_clips(
         &self,
         pipeline_id: PipelineId,
         complex_clips: ItemRange<ComplexClipRegion>,
     ) -> Vec<ComplexClipRegion> {
         if complex_clips.is_empty() {
             return vec![];
         }
-
-        self.scene
-            .pipelines
-            .get(&pipeline_id)
-            .expect("No display list?")
-            .display_list
-            .get(complex_clips)
-            .collect()
+        self.scene.get_display_list_for_pipeline(pipeline_id).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()
+        self.scene.get_display_list_for_pipeline(pipeline_id).get(items).collect()
     }
 
     fn flatten_root(&mut self, pipeline: &'a ScenePipeline, frame_size: &LayoutSize) {
         let pipeline_id = pipeline.pipeline_id;
         let reference_frame_info = self.id_to_index_mapper.simple_scroll_and_clip_chain(
             &ClipId::root_reference_frame(pipeline_id)
         );
 
@@ -374,16 +344,20 @@ impl<'a> DisplayListFlattener<'a> {
     ) {
         loop {
             let subtraversal = {
                 let item = match traversal.next() {
                     Some(item) => item,
                     None => break,
                 };
 
+                if SpecificDisplayItem::PopReferenceFrame == *item.item() {
+                    return;
+                }
+
                 if SpecificDisplayItem::PopStackingContext == *item.item() {
                     return;
                 }
 
                 self.flatten_item(
                     item,
                     pipeline_id,
                     reference_frame_relative_offset,
@@ -457,96 +431,81 @@ impl<'a> DisplayListFlattener<'a> {
             info.external_id,
             pipeline_id,
             &frame_rect,
             &content_rect.size,
             info.scroll_sensitivity,
         );
     }
 
+    fn flatten_reference_frame(
+        &mut self,
+        traversal: &mut BuiltDisplayListIter<'a>,
+        pipeline_id: PipelineId,
+        item: &DisplayItemRef,
+        reference_frame: &ReferenceFrame,
+        scroll_node_id: ClipId,
+        reference_frame_relative_offset: LayoutVector2D,
+    ) {
+        self.push_reference_frame(
+            reference_frame.id,
+            Some(scroll_node_id),
+            pipeline_id,
+            reference_frame.transform,
+            reference_frame.perspective,
+            reference_frame_relative_offset + item.rect().origin.to_vector(),
+        );
+
+        self.flatten_items(traversal, pipeline_id, LayoutVector2D::zero());
+
+        self.pop_reference_frame();
+    }
+
     fn flatten_stacking_context(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
         item: &DisplayItemRef,
         stacking_context: &StackingContext,
-        unreplaced_scroll_id: ClipId,
         scroll_node_id: ClipId,
-        mut reference_frame_relative_offset: LayoutVector2D,
+        reference_frame_relative_offset: LayoutVector2D,
         is_backface_visible: bool,
     ) {
         // Avoid doing unnecessary work for empty stacking contexts.
         if traversal.current_stacking_context_empty() {
             traversal.skip_current_stacking_context();
             return;
         }
 
         let composition_operations = {
             // TODO(optimization?): self.traversal.display_list()
-            let display_list = &self
-                .scene
-                .pipelines
-                .get(&pipeline_id)
-                .expect("No display list?!")
-                .display_list;
+            let display_list = self.scene.get_display_list_for_pipeline(pipeline_id);
             CompositeOps::new(
                 stacking_context.filter_ops_for_compositing(display_list, item.filters()),
                 stacking_context.mix_blend_mode_for_compositing(),
             )
         };
 
-        let bounds = item.rect();
-        reference_frame_relative_offset += bounds.origin.to_vector();
-
-        // If we have a transformation or a perspective, we should have been assigned a new
-        // reference frame id. This means this stacking context establishes a new reference frame.
-        // Descendant fixed position content will be positioned relative to us.
-        if let Some(reference_frame_id) = stacking_context.reference_frame_id {
-            debug_assert!(
-                stacking_context.transform.is_some() ||
-                stacking_context.perspective.is_some()
-            );
-
-            self.push_reference_frame(
-                reference_frame_id,
-                Some(scroll_node_id),
-                pipeline_id,
-                stacking_context.transform,
-                stacking_context.perspective,
-                reference_frame_relative_offset,
-            );
-            self.replacements.push((unreplaced_scroll_id, reference_frame_id));
-            reference_frame_relative_offset = LayoutVector2D::zero();
-        }
-
-        // We apply the replacements one more time in case we need to set it to a replacement
-        // that we just pushed above.
-        let final_scroll_node = self.apply_scroll_frame_id_replacement(unreplaced_scroll_id);
         self.push_stacking_context(
             pipeline_id,
             composition_operations,
             stacking_context.transform_style,
             is_backface_visible,
             false,
-            final_scroll_node,
+            scroll_node_id,
             stacking_context.clip_node_id,
             stacking_context.glyph_raster_space,
         );
 
         self.flatten_items(
             traversal,
             pipeline_id,
-            reference_frame_relative_offset,
+            reference_frame_relative_offset + item.rect().origin.to_vector(),
         );
 
-        if stacking_context.reference_frame_id.is_some() {
-            self.replacements.pop();
-            self.pop_reference_frame();
-        }
-
         self.pop_stacking_context();
     }
 
     fn flatten_iframe(
         &mut self,
         item: &DisplayItemRef,
         info: &IframeDisplayItem,
         clip_and_scroll_ids: &ClipAndScrollInfo,
@@ -603,20 +562,17 @@ impl<'a> DisplayListFlattener<'a> {
     }
 
     fn flatten_item<'b>(
         &'b mut self,
         item: DisplayItemRef<'a, 'b>,
         pipeline_id: PipelineId,
         reference_frame_relative_offset: LayoutVector2D,
     ) -> Option<BuiltDisplayListIter<'a>> {
-        let mut clip_and_scroll_ids = item.clip_and_scroll();
-        let unreplaced_scroll_id = clip_and_scroll_ids.scroll_node_id;
-        clip_and_scroll_ids.scroll_node_id =
-            self.apply_scroll_frame_id_replacement(clip_and_scroll_ids.scroll_node_id);
+        let clip_and_scroll_ids = item.clip_and_scroll();
         let clip_and_scroll = self.id_to_index_mapper.map_clip_and_scroll(&clip_and_scroll_ids);
 
         let prim_info = item.get_layout_primitive_info(&reference_frame_relative_offset);
         match *item.item() {
             SpecificDisplayItem::Image(ref info) => {
                 self.add_image(
                     clip_and_scroll,
                     &prim_info,
@@ -728,23 +684,35 @@ impl<'a> DisplayListFlattener<'a> {
             }
             SpecificDisplayItem::PushStackingContext(ref info) => {
                 let mut subtraversal = item.sub_iter();
                 self.flatten_stacking_context(
                     &mut subtraversal,
                     pipeline_id,
                     &item,
                     &info.stacking_context,
-                    unreplaced_scroll_id,
                     clip_and_scroll_ids.scroll_node_id,
                     reference_frame_relative_offset,
                     prim_info.is_backface_visible,
                 );
                 return Some(subtraversal);
             }
+            SpecificDisplayItem::PushReferenceFrame(ref info) => {
+                let mut subtraversal = item.sub_iter();
+                self.flatten_reference_frame(
+                    &mut subtraversal,
+                    pipeline_id,
+                    &item,
+                    &info.reference_frame,
+                    clip_and_scroll_ids.scroll_node_id,
+                    reference_frame_relative_offset,
+                );
+                return Some(subtraversal);
+
+            }
             SpecificDisplayItem::Iframe(ref info) => {
                 self.flatten_iframe(
                     &item,
                     info,
                     &clip_and_scroll_ids,
                     &reference_frame_relative_offset
                 );
             }
@@ -787,17 +755,17 @@ impl<'a> DisplayListFlattener<'a> {
                     &clip_and_scroll_ids.scroll_node_id,
                     &reference_frame_relative_offset
                 );
             }
 
             // Do nothing; these are dummy items for the display list parser
             SpecificDisplayItem::SetGradientStops => {}
 
-            SpecificDisplayItem::PopStackingContext => {
+            SpecificDisplayItem::PopStackingContext | SpecificDisplayItem::PopReferenceFrame => {
                 unreachable!("Should have returned in parent method.")
             }
             SpecificDisplayItem::PushShadow(shadow) => {
                 let mut prim_info = prim_info.clone();
                 prim_info.rect = LayoutRect::zero();
                 self
                     .push_shadow(shadow, clip_and_scroll, &prim_info);
             }
--- a/gfx/webrender/src/glyph_rasterizer/pathfinder.rs
+++ b/gfx/webrender/src/glyph_rasterizer/pathfinder.rs
@@ -76,17 +76,16 @@ impl Deref for ThreadSafePathfinderFontC
 
 /// PathfinderFontContext can contain a *mut IDWriteFactory.
 /// However, since we know that it is wrapped in a Mutex, it is safe
 /// to assume that this struct is thread-safe
 unsafe impl Send for ThreadSafePathfinderFontContext {}
 unsafe impl Sync for ThreadSafePathfinderFontContext { }
 
 impl GlyphRasterizer {
-
     pub(in super) fn add_font_to_pathfinder(&mut self, font_key: &FontKey, template: &FontTemplate) {
         let font_contexts = Arc::clone(&self.font_contexts);
         debug!("add_font_to_pathfinder({:?})", font_key);
         font_contexts.lock_pathfinder_context().add_font(&font_key, &template);
     }
 
     pub fn get_cache_item_for_glyph(
         &self,
@@ -175,66 +174,65 @@ impl GlyphRasterizer {
                             cached_glyph_info = Some(glyph_info.clone())
                         }
                         GlyphCacheEntry::Blank | GlyphCacheEntry::Pending => {}
                     }
                 }
                 Entry::Vacant(_) => {}
             }
 
-            let cached_glyph_info = match cached_glyph_info {
-                Some(cached_glyph_info) => cached_glyph_info,
-                None => {
-                    let mut pathfinder_font_context = self.font_contexts.lock_pathfinder_context();
+            if cached_glyph_info.is_none() {
+                let mut pathfinder_font_context = self.font_contexts.lock_pathfinder_context();
 
-                    let pathfinder_font_instance = pathfinder_font_renderer::FontInstance {
-                        font_key: font.font_key.clone(),
-                        size: font.size,
-                    };
+                let pathfinder_font_instance = pathfinder_font_renderer::FontInstance {
+                    font_key: font.font_key.clone(),
+                    size: font.size,
+                };
 
-                    // TODO: pathfinder will need to support 2D subpixel offset
-                    let pathfinder_subpixel_offset =
-                        pathfinder_font_renderer::SubpixelOffset(glyph_key.subpixel_offset.0 as u8);
-                    let pathfinder_glyph_key =
-                        pathfinder_font_renderer::GlyphKey::new(glyph_key.index,
-                                                                pathfinder_subpixel_offset);
-                    let glyph_dimensions =
-                        match pathfinder_font_context.glyph_dimensions(&pathfinder_font_instance,
-                                                                       &pathfinder_glyph_key,
-                                                                       false) {
-                            Ok(glyph_dimensions) => glyph_dimensions,
-                            Err(_) => continue,
-                        };
+                // TODO: pathfinder will need to support 2D subpixel offset
+                let pathfinder_subpixel_offset =
+                    pathfinder_font_renderer::SubpixelOffset(glyph_key.subpixel_offset.0 as u8);
+                let pathfinder_glyph_key =
+                    pathfinder_font_renderer::GlyphKey::new(glyph_key.index,
+                                                            pathfinder_subpixel_offset);
 
-                    let cached_glyph_info = CachedGlyphInfo {
-                        render_task_cache_key: RenderTaskCacheKey {
-                            size: TypedSize2D::from_untyped(&glyph_dimensions.size.to_i32()),
-                            kind: RenderTaskCacheKeyKind::Glyph(self.next_gpu_glyph_cache_key),
-                        },
+                if let Ok(glyph_dimensions) =
+                        pathfinder_font_context.glyph_dimensions(&pathfinder_font_instance,
+                                                                 &pathfinder_glyph_key,
+                                                                 false) {
+                    let render_task_cache_key = RenderTaskCacheKey {
+                        size: TypedSize2D::from_untyped(&glyph_dimensions.size.to_i32()),
+                        kind: RenderTaskCacheKeyKind::Glyph(self.next_gpu_glyph_cache_key),
+                    };
+                    cached_glyph_info = Some(CachedGlyphInfo {
+                        render_task_cache_key,
                         format: font.get_glyph_format(),
                         origin: DeviceIntPoint::new(glyph_dimensions.origin.x as i32,
                                                     -glyph_dimensions.origin.y as i32),
-                    };
+                    });
                     self.next_gpu_glyph_cache_key.0 += 1;
-                    cached_glyph_info
                 }
-            };
+            }
 
-            let handle =
-                match self.request_glyph_from_pathfinder_if_necessary(glyph_key,
-                                                                      &font,
-                                                                      cached_glyph_info.clone(),
-                                                                      texture_cache,
-                                                                      gpu_cache,
-                                                                      render_task_cache,
-                                                                      render_task_tree,
-                                                                      render_passes) {
-                    Ok(_) => GlyphCacheEntry::Cached(cached_glyph_info),
-                    Err(_) => GlyphCacheEntry::Blank,
-                };
+            let handle = match cached_glyph_info {
+                Some(glyph_info) => {
+                    match self.request_glyph_from_pathfinder_if_necessary(glyph_key,
+                                                                          &font,
+                                                                          glyph_info.clone(),
+                                                                          texture_cache,
+                                                                          gpu_cache,
+                                                                          render_task_cache,
+                                                                          render_task_tree,
+                                                                          render_passes) {
+                        Ok(_) => GlyphCacheEntry::Cached(glyph_info),
+                        Err(_) => GlyphCacheEntry::Blank,
+                    }
+                }
+                None => GlyphCacheEntry::Blank,
+            };
 
             glyph_key_cache.insert(glyph_key.clone(), handle);
         }
     }
 
     pub fn resolve_glyphs(
         &mut self,
         _: &mut GlyphCache,
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1464,25 +1464,27 @@ impl PrimitiveStore {
             } = *source {
                 // TODO(gw): When drawing in screen raster mode, we should also incorporate a
                 //           scale factor from the world transform to get an appropriately
                 //           sized border task.
                 let world_scale = LayoutToWorldScale::new(1.0);
                 let scale = world_scale * frame_context.device_pixel_scale;
                 let scale_au = Au::from_f32_px(scale.0);
                 let needs_update = scale_au != cache_key.scale;
+                let mut new_segments = Vec::new();
 
                 if needs_update {
                     cache_key.scale = scale_au;
 
                     *task_info = Some(BorderRenderTaskInfo::new(
                         &metadata.local_rect,
                         border,
                         widths,
                         scale,
+                        &mut new_segments,
                     ));
                 }
 
                 let task_info = task_info.as_ref().unwrap();
 
                 *handle = Some(frame_state.resource_cache.request_render_task(
                     RenderTaskCacheKey {
                         size: DeviceIntSize::zero(),
@@ -1490,30 +1492,30 @@ impl PrimitiveStore {
                     },
                     frame_state.gpu_cache,
                     frame_state.render_tasks,
                     None,
                     false,          // todo
                     |render_tasks| {
                         let task = RenderTask::new_border(
                             task_info.size,
-                            task_info.instances.clone(),
+                            task_info.build_instances(border),
                         );
 
                         let task_id = render_tasks.add(task);
 
                         pic_state.tasks.push(task_id);
 
                         task_id
                     }
                 ));
 
                 if needs_update {
                     brush.segment_desc = Some(BrushSegmentDescriptor {
-                        segments: task_info.segments.clone(),
+                        segments: new_segments,
                         clip_mask_kind: BrushClipMaskKind::Unknown,
                     });
                 }
             }
         }
     }
 
     fn prepare_prim_for_render_inner(
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -1185,36 +1185,38 @@ impl RenderBackend {
 trait ToDebugString {
     fn debug_string(&self) -> String;
 }
 
 #[cfg(feature = "debugger")]
 impl ToDebugString for SpecificDisplayItem {
     fn debug_string(&self) -> String {
         match *self {
-            SpecificDisplayItem::Image(..) => String::from("image"),
-            SpecificDisplayItem::YuvImage(..) => String::from("yuv_image"),
-            SpecificDisplayItem::Text(..) => String::from("text"),
-            SpecificDisplayItem::Rectangle(..) => String::from("rectangle"),
+            SpecificDisplayItem::Border(..) => String::from("border"),
+            SpecificDisplayItem::BoxShadow(..) => String::from("box_shadow"),
             SpecificDisplayItem::ClearRectangle => String::from("clear_rectangle"),
-            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::Gradient(..) => String::from("gradient"),
+            SpecificDisplayItem::Iframe(..) => String::from("iframe"),
+            SpecificDisplayItem::Image(..) => String::from("image"),
+            SpecificDisplayItem::Line(..) => String::from("line"),
+            SpecificDisplayItem::PopAllShadows => String::from("pop_all_shadows"),
+            SpecificDisplayItem::PopReferenceFrame => String::from("pop_reference_frame"),
+            SpecificDisplayItem::PopStackingContext => String::from("pop_stacking_context"),
+            SpecificDisplayItem::PushReferenceFrame(..) => String::from("push_reference_frame"),
+            SpecificDisplayItem::PushShadow(..) => String::from("push_shadow"),
+            SpecificDisplayItem::PushStackingContext(..) => String::from("push_stacking_context"),
+            SpecificDisplayItem::RadialGradient(..) => String::from("radial_gradient"),
+            SpecificDisplayItem::Rectangle(..) => String::from("rectangle"),
             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"),
+            SpecificDisplayItem::StickyFrame(..) => String::from("sticky_frame"),
+            SpecificDisplayItem::Text(..) => String::from("text"),
+            SpecificDisplayItem::YuvImage(..) => String::from("yuv_image"),
         }
     }
 }
 
 impl RenderBackend {
     #[cfg(feature = "capture")]
     // Note: the mutable `self` is only needed here for resolving blob images
     fn save_capture(
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -696,17 +696,23 @@ impl RenderTask {
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::Blit(..) => {
                 [0.0; 3]
             }
         };
 
-        let (target_rect, target_index) = self.get_target_rect();
+        let (mut target_rect, target_index) = self.get_target_rect();
+        // The primitives inside a fixed-location render task
+        // are already placed to their corresponding positions,
+        // so the shader doesn't need to shift by the origin.
+        if let RenderTaskLocation::Fixed(_) = self.location {
+            target_rect.origin = DeviceIntPoint::origin();
+        };
 
         RenderTaskData {
             data: [
                 target_rect.origin.x as f32,
                 target_rect.origin.y as f32,
                 target_rect.size.width as f32,
                 target_rect.size.height as f32,
                 target_index.0 as f32,
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -2882,18 +2882,28 @@ impl Renderer {
             self.device.set_blend(false);
             //Note: depth equality is needed for split planes
             self.device.set_depth_func(DepthFunction::LessEqual);
             self.device.enable_depth();
             self.device.enable_depth_write();
 
             for alpha_batch_container in &target.alpha_batch_containers {
                 if let Some(target_rect) = alpha_batch_container.target_rect {
+                    // Note: `framebuffer_target_rect` needs a Y-flip before going to GL
+                    let rect = if render_target.is_none() {
+                        let mut rect = target_rect
+                            .intersection(&framebuffer_target_rect.to_i32())
+                            .unwrap_or(DeviceIntRect::zero());
+                        rect.origin.y = target_size.height as i32 - rect.origin.y - rect.size.height;
+                        rect
+                    } else {
+                        target_rect
+                    };
                     self.device.enable_scissor();
-                    self.device.set_scissor_rect(target_rect);
+                    self.device.set_scissor_rect(rect);
                 }
 
                 // Draw opaque batches front-to-back for maximum
                 // z-buffer efficiency!
                 for batch in alpha_batch_container
                     .opaque_batches
                     .iter()
                     .rev()
@@ -2925,18 +2935,28 @@ impl Renderer {
 
         let _gl = self.gpu_profile.start_marker("alpha batches");
         let transparent_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_TRANSPARENT);
         self.device.set_blend(true);
         let mut prev_blend_mode = BlendMode::None;
 
         for alpha_batch_container in &target.alpha_batch_containers {
             if let Some(target_rect) = alpha_batch_container.target_rect {
+                // Note: `framebuffer_target_rect` needs a Y-flip before going to GL
+                let rect = if render_target.is_none() {
+                    let mut rect = target_rect
+                        .intersection(&framebuffer_target_rect.to_i32())
+                        .unwrap_or(DeviceIntRect::zero());
+                    rect.origin.y = target_size.height as i32 - rect.origin.y - rect.size.height;
+                    rect
+                } else {
+                    target_rect
+                };
                 self.device.enable_scissor();
-                self.device.set_scissor_rect(target_rect);
+                self.device.set_scissor_rect(rect);
             }
 
             for batch in &alpha_batch_container.alpha_batches {
                 self.shaders
                     .get(&batch.key)
                     .bind(
                         &mut self.device, projection,
                         &mut self.renderer_errors,
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -657,16 +657,17 @@ impl ResourceCache {
                         let offset = DevicePoint::new(
                             tile_offset.x as f32 * tile_size as f32,
                             tile_offset.y as f32 * tile_size as f32,
                         );
 
                         if let Some(dirty) = dirty_rect {
                             if intersect_for_tile(dirty, actual_size, tile_size, tile_offset).is_none() {
                                 // don't bother requesting unchanged tiles
+                                self.pending_image_requests.remove(&request);
                                 return
                             }
                         }
 
                         (offset, actual_size)
                     }
                     None => (DevicePoint::zero(), template.descriptor.size),
                 };
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -110,16 +110,23 @@ impl Scene {
             pipeline_epochs: FastHashMap::default(),
         }
     }
 
     pub fn set_root_pipeline_id(&mut self, pipeline_id: PipelineId) {
         self.root_pipeline_id = Some(pipeline_id);
     }
 
+    pub fn get_display_list_for_pipeline(&self, pipeline_id: PipelineId) -> &BuiltDisplayList {
+        &self.pipelines
+            .get(&pipeline_id)
+            .expect("Expected to find display list for pipeline")
+            .display_list
+    }
+
     pub fn set_display_list(
         &mut self,
         pipeline_id: PipelineId,
         epoch: Epoch,
         display_list: BuiltDisplayList,
         background_color: Option<ColorF>,
         viewport_size: LayoutSize,
         content_size: LayoutSize,
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -104,16 +104,18 @@ pub enum SpecificDisplayItem {
     Border(BorderDisplayItem),
     BoxShadow(BoxShadowDisplayItem),
     Gradient(GradientDisplayItem),
     RadialGradient(RadialGradientDisplayItem),
     ClipChain(ClipChainItem),
     Iframe(IframeDisplayItem),
     PushStackingContext(PushStackingContextDisplayItem),
     PopStackingContext,
+    PushReferenceFrame(PushReferenceFrameDisplayListItem),
+    PopReferenceFrame,
     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.
@@ -133,16 +135,18 @@ pub enum CompletelySpecificDisplayItem {
     YuvImage(YuvImageDisplayItem),
     Border(BorderDisplayItem),
     BoxShadow(BoxShadowDisplayItem),
     Gradient(GradientDisplayItem),
     RadialGradient(RadialGradientDisplayItem),
     Iframe(IframeDisplayItem),
     PushStackingContext(PushStackingContextDisplayItem, Vec<FilterOp>),
     PopStackingContext,
+    PushReferenceFrame(PushReferenceFrameDisplayListItem),
+    PopReferenceFrame,
     SetGradientStops(Vec<GradientStop>),
     PushShadow(Shadow),
     PopAllShadows,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ClipDisplayItem {
     pub id: ClipId,
@@ -381,16 +385,22 @@ pub enum BorderStyle {
     Dashed = 4,
     Hidden = 5,
     Groove = 6,
     Ridge = 7,
     Inset = 8,
     Outset = 9,
 }
 
+impl BorderStyle {
+    pub fn is_hidden(&self) -> bool {
+        *self == BorderStyle::Hidden || *self == BorderStyle::None
+    }
+}
+
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum BoxShadowClipMode {
     Outset = 0,
     Inset = 1,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
@@ -458,27 +468,36 @@ pub struct ClipChainItem {
 #[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 PushReferenceFrameDisplayListItem {
+    pub reference_frame: ReferenceFrame,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct ReferenceFrame {
+    pub transform: Option<PropertyBinding<LayoutTransform>>,
+    pub perspective: Option<LayoutTransform>,
+    pub id: ClipId,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct PushStackingContextDisplayItem {
     pub stacking_context: StackingContext,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct StackingContext {
-    pub transform: Option<PropertyBinding<LayoutTransform>>,
     pub transform_style: TransformStyle,
-    pub perspective: Option<LayoutTransform>,
     pub mix_blend_mode: MixBlendMode,
-    pub reference_frame_id: Option<ClipId>,
     pub clip_node_id: Option<ClipId>,
     pub glyph_raster_space: GlyphRasterSpace,
 } // IMPLICIT: filters: Vec<FilterOp>
 
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum TransformStyle {
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -15,20 +15,21 @@ use std::{io, mem, ptr, slice};
 use time::precise_time_ns;
 use {AlphaType, BorderDetails, BorderDisplayItem, BorderRadius, BorderWidths, BoxShadowClipMode};
 use {BoxShadowDisplayItem, ClipAndScrollInfo, ClipChainId, ClipChainItem, ClipDisplayItem, ClipId};
 use {ColorF, ComplexClipRegion, DisplayItem, ExtendMode, ExternalScrollId, FilterOp};
 use {FontInstanceKey, GlyphInstance, GlyphOptions, GlyphRasterSpace, Gradient};
 use {GradientDisplayItem, GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask};
 use {ImageRendering, LayoutPoint, LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform};
 use {LayoutVector2D, LineDisplayItem, LineOrientation, LineStyle, MixBlendMode, PipelineId};
-use {PropertyBinding, PushStackingContextDisplayItem, RadialGradient, RadialGradientDisplayItem};
-use {RectangleDisplayItem, ScrollFrameDisplayItem, ScrollSensitivity, Shadow, SpecificDisplayItem};
-use {StackingContext, StickyFrameDisplayItem, StickyOffsetBounds, TextDisplayItem, TransformStyle};
-use {YuvColorSpace, YuvData, YuvImageDisplayItem};
+use {PropertyBinding, PushReferenceFrameDisplayListItem, PushStackingContextDisplayItem};
+use {RadialGradient, RadialGradientDisplayItem, RectangleDisplayItem, ReferenceFrame};
+use {ScrollFrameDisplayItem, ScrollSensitivity, Shadow, SpecificDisplayItem, StackingContext};
+use {StickyFrameDisplayItem, StickyOffsetBounds, TextDisplayItem, TransformStyle, YuvColorSpace};
+use {YuvData, YuvImageDisplayItem};
 
 // We don't want to push a long text-run. If a text-run is too long, split it into several parts.
 // This needs to be set to (renderer::MAX_VERTEX_TEXTURE_WIDTH - VECS_PER_PRIM_HEADER - VECS_PER_TEXT_RUN) * 2
 pub const MAX_TEXT_RUN_LENGTH: usize = 2038;
 
 // We start at 2, because the root reference is always 0 and the root scroll node is always 1.
 const FIRST_CLIP_ID: usize = 2;
 
@@ -476,16 +477,18 @@ impl Serialize for BuiltDisplayList {
                     SpecificDisplayItem::Gradient(v) => Gradient(v),
                     SpecificDisplayItem::RadialGradient(v) => RadialGradient(v),
                     SpecificDisplayItem::Iframe(v) => Iframe(v),
                     SpecificDisplayItem::PushStackingContext(v) => PushStackingContext(
                         v,
                         item.iter.list.get(item.iter.cur_filters).collect()
                     ),
                     SpecificDisplayItem::PopStackingContext => PopStackingContext,
+                    SpecificDisplayItem::PushReferenceFrame(v) => PushReferenceFrame(v),
+                    SpecificDisplayItem::PopReferenceFrame => PopReferenceFrame,
                     SpecificDisplayItem::SetGradientStops => SetGradientStops(
                         item.iter.list.get(item.iter.cur_stops).collect()
                     ),
                     SpecificDisplayItem::PushShadow(v) => PushShadow(v),
                     SpecificDisplayItem::PopAllShadows => PopAllShadows,
                 },
                 clip_and_scroll: display_item.clip_and_scroll,
                 info: display_item.info,
@@ -550,23 +553,25 @@ impl<'de> Deserialize<'de> for BuiltDisp
                     Gradient(specific_item) => SpecificDisplayItem::Gradient(specific_item),
                     RadialGradient(specific_item) =>
                         SpecificDisplayItem::RadialGradient(specific_item),
                     Iframe(specific_item) => {
                         total_clip_ids += 1;
                         SpecificDisplayItem::Iframe(specific_item)
                     }
                     PushStackingContext(specific_item, filters) => {
-                        if specific_item.stacking_context.reference_frame_id.is_some() {
-                            total_clip_ids += 1;
-                        }
                         DisplayListBuilder::push_iter_impl(&mut temp, filters);
                         SpecificDisplayItem::PushStackingContext(specific_item)
                     },
                     PopStackingContext => SpecificDisplayItem::PopStackingContext,
+                    PushReferenceFrame(specific_item) => {
+                        total_clip_ids += 1;
+                        SpecificDisplayItem::PushReferenceFrame(specific_item)
+                    }
+                    PopReferenceFrame => SpecificDisplayItem::PopReferenceFrame,
                     SetGradientStops(stops) => {
                         DisplayListBuilder::push_iter_impl(&mut temp, stops);
                         SpecificDisplayItem::SetGradientStops
                     },
                     PushShadow(specific_item) => SpecificDisplayItem::PushShadow(specific_item),
                     PopAllShadows => SpecificDisplayItem::PopAllShadows,
                 },
                 clip_and_scroll: complete.clip_and_scroll,
@@ -1272,49 +1277,58 @@ impl DisplayListBuilder {
             gradient,
             tile_size,
             tile_spacing,
         });
 
         self.push_item(item, info);
     }
 
+    pub fn push_reference_frame(
+        &mut self,
+        info: &LayoutPrimitiveInfo,
+        transform: Option<PropertyBinding<LayoutTransform>>,
+        perspective: Option<LayoutTransform>,
+    ) -> ClipId {
+        let id = self.generate_clip_id();
+        let item = SpecificDisplayItem::PushReferenceFrame(PushReferenceFrameDisplayListItem {
+            reference_frame: ReferenceFrame {
+                transform,
+                perspective,
+                id,
+            },
+        });
+        self.push_item(item, info);
+        id
+    }
+
+    pub fn pop_reference_frame(&mut self) {
+        self.push_new_empty_item(SpecificDisplayItem::PopReferenceFrame);
+    }
+
     pub fn push_stacking_context(
         &mut self,
         info: &LayoutPrimitiveInfo,
         clip_node_id: Option<ClipId>,
-        transform: Option<PropertyBinding<LayoutTransform>>,
         transform_style: TransformStyle,
-        perspective: Option<LayoutTransform>,
         mix_blend_mode: MixBlendMode,
         filters: Vec<FilterOp>,
         glyph_raster_space: GlyphRasterSpace,
-    ) -> Option<ClipId> {
-        let reference_frame_id = if transform.is_some() || perspective.is_some() {
-            Some(self.generate_clip_id())
-        } else {
-            None
-        };
-
+    ) {
         let item = SpecificDisplayItem::PushStackingContext(PushStackingContextDisplayItem {
             stacking_context: StackingContext {
-                transform,
                 transform_style,
-                perspective,
                 mix_blend_mode,
-                reference_frame_id,
                 clip_node_id,
                 glyph_raster_space,
             },
         });
 
         self.push_item(item, info);
         self.push_iter(&filters);
-
-        reference_frame_id
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.push_new_empty_item(SpecificDisplayItem::PopStackingContext);
     }
 
     pub fn push_stops(&mut self, stops: &[GradientStop]) {
         if stops.is_empty() {
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-3829687ffbe8d55885d71a3d5e5e79216251548f
+8e697f8cb1f1aab2e5f6b9b903eb7191340b10c5
--- a/gfx/wrench/src/rawtest.rs
+++ b/gfx/wrench/src/rawtest.rs
@@ -149,16 +149,23 @@ impl<'a> RawtestHarness<'a> {
         let blob_img = self.wrench.api.generate_image_key();
         txn.add_image(
             blob_img,
             ImageDescriptor::new(1510, 111256, ImageFormat::BGRA8, false, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
             Some(31),
         );
 
+        let called = Arc::new(AtomicIsize::new(0));
+        let called_inner = Arc::clone(&called);
+
+        self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
+            called_inner.fetch_add(1, Ordering::SeqCst);
+        });
+
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let info = LayoutPrimitiveInfo::new(rect(0., -9600.0, 1510.000031, 111256.));
 
         let image_size = size(1510., 111256.);
 
         let clip_id = builder.define_clip(rect(40., 41., 200., 201.), vec![], None);
 
@@ -173,23 +180,16 @@ impl<'a> RawtestHarness<'a> {
             blob_img,
         );
         builder.pop_clip_id();
 
         let mut epoch = Epoch(0);
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
-        let called = Arc::new(AtomicIsize::new(0));
-        let called_inner = Arc::clone(&called);
-
-        self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
-            called_inner.fetch_add(1, Ordering::SeqCst);
-        });
-
         let pixels = self.render_and_get_pixels(window_rect);
 
         // make sure we didn't request too many blobs
         assert_eq!(called.load(Ordering::SeqCst), 16);
 
         // make sure things are in the right spot
         assert!(
             pixels[(148 +
@@ -220,16 +220,18 @@ impl<'a> RawtestHarness<'a> {
                         window_rect.size.width as usize) * 4 + 3] == 255
         );
 
         // Leaving a tiled blob image in the resource cache
         // confuses the `test_capture`. TODO: remove this
         txn = Transaction::new();
         txn.delete_image(blob_img);
         self.wrench.api.update_resources(txn.resource_updates);
+
+        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
     }
 
     fn test_offscreen_blob(&mut self) {
         println!("\toffscreen blob update.");
 
         assert_eq!(self.wrench.device_pixel_ratio, 1.);
 
         let window_size = self.window.get_inner_size();
@@ -357,16 +359,23 @@ impl<'a> RawtestHarness<'a> {
             txn.add_image(
                 blob_img,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
                 ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
                 None,
             );
         }
 
+        let called = Arc::new(AtomicIsize::new(0));
+        let called_inner = Arc::clone(&called);
+
+        self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
+            assert_eq!(0, called_inner.fetch_add(1, Ordering::SeqCst));
+        });
+
         // draw the blob the first time
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
 
         builder.push_image(
             &info,
             size(200.0, 200.0),
             size(0.0, 0.0),
@@ -374,26 +383,19 @@ impl<'a> RawtestHarness<'a> {
             AlphaType::PremultipliedAlpha,
             blob_img,
         );
 
         let mut epoch = Epoch(0);
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
-        let called = Arc::new(AtomicIsize::new(0));
-        let called_inner = Arc::clone(&called);
-
-        self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
-            called_inner.fetch_add(1, Ordering::SeqCst);
-        });
-
         let pixels_first = self.render_and_get_pixels(window_rect);
 
-        assert!(called.load(Ordering::SeqCst) == 1);
+        assert_eq!(1, called.load(Ordering::SeqCst));
 
         // draw the blob image a second time at a different location
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(1.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
@@ -406,23 +408,26 @@ impl<'a> RawtestHarness<'a> {
 
         txn.resource_updates.clear();
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
         let pixels_second = self.render_and_get_pixels(window_rect);
 
         // make sure we only requested once
-        assert!(called.load(Ordering::SeqCst) == 1);
+        assert_eq!(1, called.load(Ordering::SeqCst));
 
         // use png;
         // png::save_flipped("out1.png", &pixels_first, window_rect.size);
         // png::save_flipped("out2.png", &pixels_second, window_rect.size);
 
         assert!(pixels_first != pixels_second);
+
+        // cleanup
+        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
     }
 
     fn test_blob_update_epoch_test(&mut self) {
         println!("\tblob update epoch test...");
         let (blob_img, blob_img2);
         let window_size = self.window.get_inner_size();
 
         let test_size = DeviceUintSize::new(400, 400);
@@ -534,16 +539,19 @@ impl<'a> RawtestHarness<'a> {
         push_images(&mut builder);
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let _pixels_third = self.render_and_get_pixels(window_rect);
 
         // the first image should be requested 3 times
         assert_eq!(img1_requested.load(Ordering::SeqCst), 3);
         // the second image should've been requested twice
         assert_eq!(img2_requested.load(Ordering::SeqCst), 2);
+
+        // cleanup
+        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
     }
 
     fn test_blob_update_test(&mut self) {
         println!("\tblob update test...");
         let window_size = self.window.get_inner_size();
 
         let test_size = DeviceUintSize::new(400, 400);
 
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -1249,56 +1249,57 @@ impl YamlFrameReader {
     pub fn get_complex_clip_for_item(&mut self, yaml: &Yaml) -> Option<ComplexClipRegion> {
         let complex_clip = &yaml["complex-clip"];
         if complex_clip.is_badvalue() {
             return None;
         }
         Some(self.to_complex_clip_region(complex_clip))
     }
 
+    pub fn get_item_type_from_yaml(item: &Yaml) -> &str {
+        let shorthands = [
+            "rect",
+            "image",
+            "text",
+            "glyphs",
+            "box-shadow", // Note: box_shadow shorthand check has to come before border.
+            "border",
+            "gradient",
+            "radial-gradient",
+        ];
+
+        for shorthand in shorthands.iter() {
+            if !item[*shorthand].is_badvalue() {
+                return shorthand;
+            }
+        }
+        item["type"].as_str().unwrap_or("unknown")
+    }
+
     pub fn add_display_list_items_from_yaml(
         &mut self,
         dl: &mut DisplayListBuilder,
         wrench: &mut Wrench,
         yaml: &Yaml,
     ) {
         // A very large number (but safely far away from finite limits of f32)
         let big_number = 1.0e30;
         // A rect that should in practical terms serve as a no-op for clipping
         let full_clip = LayoutRect::new(
             LayoutPoint::new(-big_number / 2.0, -big_number / 2.0),
             LayoutSize::new(big_number, big_number));
 
         for item in yaml.as_vec().unwrap() {
-            // an explicit type can be skipped with some shorthand
-            let item_type = if !item["rect"].is_badvalue() {
-                "rect"
-            } else if !item["image"].is_badvalue() {
-                "image"
-            } else if !item["text"].is_badvalue() {
-                "text"
-            } else if !item["glyphs"].is_badvalue() {
-                "glyphs"
-            } else if !item["box-shadow"].is_badvalue() {
-                // Note: box_shadow shorthand check has to come before border.
-                "box-shadow"
-            } else if !item["border"].is_badvalue() {
-                "border"
-            } else if !item["gradient"].is_badvalue() {
-                "gradient"
-            } else if !item["radial-gradient"].is_badvalue() {
-                "radial-gradient"
-            } else {
-                item["type"].as_str().unwrap_or("unknown")
-            };
+            let item_type = Self::get_item_type_from_yaml(item);
 
-            // We never skip stacking contexts because they are structural elements
-            // of the display list.
-            if item_type != "stacking-context" && self.include_only.contains(&item_type.to_owned())
-            {
+            // We never skip stacking contexts and reference frames because
+            // they are structural elements of the display list.
+            if item_type != "stacking-context" &&
+                item_type != "reference-frame" &&
+                self.include_only.contains(&item_type.to_owned()) {
                 continue;
             }
 
             let clip_scroll_info = self.to_clip_and_scroll_info(
                 &item["clip-and-scroll"],
                 dl.pipeline_id
             );
             if let Some(clip_scroll_info) = clip_scroll_info {
@@ -1338,16 +1339,17 @@ impl YamlFrameReader {
                 "border" => self.handle_border(dl, wrench, item, &mut info),
                 "gradient" => self.handle_gradient(dl, item, &mut info),
                 "radial-gradient" => self.handle_radial_gradient(dl, item, &mut info),
                 "box-shadow" => self.handle_box_shadow(dl, item, &mut info),
                 "iframe" => self.handle_iframe(dl, item, &mut info),
                 "stacking-context" => {
                     self.add_stacking_context_from_yaml(dl, wrench, item, false, &mut info)
                 }
+                "reference-frame" => self.handle_reference_frame(dl, wrench, item, &mut info),
                 "shadow" => self.handle_push_shadow(dl, item, &mut info),
                 "pop-all-shadows" => self.handle_pop_all_shadows(dl),
                 _ => println!("Skipping unknown item type: {:?}", item),
             }
 
             if pushed_clip {
                 dl.pop_clip_id();
 
@@ -1498,54 +1500,104 @@ impl YamlFrameReader {
 
     pub fn get_root_size_from_yaml(&mut self, wrench: &mut Wrench, yaml: &Yaml) -> LayoutSize {
         yaml["bounds"]
             .as_rect()
             .map(|rect| rect.size)
             .unwrap_or(wrench.window_size_f32())
     }
 
-    pub fn add_stacking_context_from_yaml(
+    pub fn push_reference_frame(
         &mut self,
         dl: &mut DisplayListBuilder,
         wrench: &mut Wrench,
         yaml: &Yaml,
-        is_root: bool,
         info: &mut LayoutPrimitiveInfo,
-    ) {
+    ) -> ClipId {
         let default_bounds = LayoutRect::new(LayoutPoint::zero(), wrench.window_size_f32());
         let bounds = yaml["bounds"].as_rect().unwrap_or(default_bounds);
-
         let default_transform_origin = LayoutPoint::new(
             bounds.origin.x + bounds.size.width * 0.5,
             bounds.origin.y + bounds.size.height * 0.5,
         );
 
+        info.rect = bounds;
+
         let transform_origin = yaml["transform-origin"]
             .as_point()
             .unwrap_or(default_transform_origin);
 
         let perspective_origin = yaml["perspective-origin"]
             .as_point()
             .unwrap_or(default_transform_origin);
 
         let transform = yaml["transform"]
             .as_transform(&transform_origin)
             .map(|transform| transform.into());
 
-        let clip_node_id = self.to_clip_id(&yaml["clip-node"], dl.pipeline_id);
-
         let perspective = match yaml["perspective"].as_f32() {
             Some(value) if value != 0.0 => {
                 Some(make_perspective(perspective_origin, value as f32))
             }
             Some(_) => None,
             _ => yaml["perspective"].as_matrix4d(),
         };
 
+        let reference_frame_id = dl.push_reference_frame(info, transform.into(), perspective);
+
+        let numeric_id = yaml["reference-frame-id"].as_i64();
+        if let Some(numeric_id) = numeric_id {
+            self.add_clip_id_mapping(numeric_id as u64, reference_frame_id);
+        }
+
+        reference_frame_id
+    }
+
+    pub fn handle_reference_frame(
+        &mut self,
+        dl: &mut DisplayListBuilder,
+        wrench: &mut Wrench,
+        yaml: &Yaml,
+        info: &mut LayoutPrimitiveInfo,
+    ) {
+        let reference_frame_id = self.push_reference_frame(dl, wrench, yaml, info);
+
+        if !yaml["items"].is_badvalue() {
+            dl.push_clip_id(reference_frame_id);
+            self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
+            dl.pop_clip_id();
+        }
+
+        dl.pop_reference_frame();
+    }
+
+    pub fn add_stacking_context_from_yaml(
+        &mut self,
+        dl: &mut DisplayListBuilder,
+        wrench: &mut Wrench,
+        yaml: &Yaml,
+        is_root: bool,
+        info: &mut LayoutPrimitiveInfo,
+    ) {
+        let default_bounds = LayoutRect::new(LayoutPoint::zero(), wrench.window_size_f32());
+        let bounds = yaml["bounds"].as_rect().unwrap_or(default_bounds);
+        info.rect = bounds;
+        info.clip_rect = bounds;
+
+        let reference_frame_id = if !yaml["transform"].is_badvalue() ||
+            !yaml["perspective"].is_badvalue() {
+            let reference_frame_id = self.push_reference_frame(dl, wrench, yaml, info);
+            info.rect.origin = LayoutPoint::zero();
+            info.clip_rect.origin = LayoutPoint::zero();
+            Some(reference_frame_id)
+        } else {
+            None
+        };
+
+        let clip_node_id = self.to_clip_id(&yaml["clip-node"], dl.pipeline_id);
         let transform_style = yaml["transform-style"]
             .as_transform_style()
             .unwrap_or(TransformStyle::Flat);
         let mix_blend_mode = yaml["mix-blend-mode"]
             .as_mix_blend_mode()
             .unwrap_or(MixBlendMode::Normal);
         let glyph_raster_space = yaml["glyph-raster-space"]
             .as_glyph_raster_space()
@@ -1554,43 +1606,43 @@ impl YamlFrameReader {
         if is_root {
             if let Some(size) = yaml["scroll-offset"].as_point() {
                 let external_id = ExternalScrollId(0, dl.pipeline_id);
                 self.scroll_offsets.insert(external_id, LayoutPoint::new(size.x, size.y));
             }
         }
 
         let filters = yaml["filters"].as_vec_filter_op().unwrap_or(vec![]);
-        info.rect = bounds;
-        info.clip_rect = bounds;
 
-        let reference_frame_id = dl.push_stacking_context(
+        if let Some(reference_frame_id) = reference_frame_id {
+            dl.push_clip_id(reference_frame_id);
+        }
+
+        dl.push_stacking_context(
             &info,
             clip_node_id,
-            transform.into(),
             transform_style,
-            perspective,
             mix_blend_mode,
             filters,
             glyph_raster_space,
         );
 
-        let numeric_id = yaml["reference-frame-id"].as_i64();
-        match (numeric_id, reference_frame_id) {
-            (Some(numeric_id), Some(reference_frame_id)) => {
-                self.add_clip_id_mapping(numeric_id as u64, reference_frame_id);
-            }
-            _ => {},
-        }
-
         if !yaml["items"].is_badvalue() {
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
         }
 
         dl.pop_stacking_context();
+
+        if reference_frame_id.is_some() {
+            dl.pop_clip_id();
+        }
+
+        if reference_frame_id.is_some() {
+            dl.pop_reference_frame();
+        }
     }
 }
 
 impl WrenchThing for YamlFrameReader {
     fn do_frame(&mut self, wrench: &mut Wrench) -> u32 {
         if !self.frame_built || self.watch_source {
             self.build(wrench);
             self.frame_built = false;
--- a/gfx/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wrench/src/yaml_frame_writer.rs
@@ -173,45 +173,58 @@ fn maybe_radius_yaml(radius: &BorderRadi
         size_node(&mut table, "top-left", &radius.top_left);
         size_node(&mut table, "top-right", &radius.top_right);
         size_node(&mut table, "bottom-left", &radius.bottom_left);
         size_node(&mut table, "bottom-right", &radius.bottom_right);
         Some(Yaml::Hash(table))
     }
 }
 
+fn write_reference_frame(
+    parent: &mut Table,
+    reference_frame: &ReferenceFrame,
+    properties: &SceneProperties,
+    clip_id_mapper: &mut ClipIdMapper,
+) {
+    matrix4d_node(
+        parent,
+        "transform",
+        &properties.resolve_layout_transform(&reference_frame.transform)
+    );
+
+    if let Some(perspective) = reference_frame.perspective {
+        matrix4d_node(parent, "perspective", &perspective);
+    }
+
+    usize_node(parent, "id", clip_id_mapper.add_id(reference_frame.id));
+}
+
 fn write_stacking_context(
     parent: &mut Table,
     sc: &StackingContext,
     properties: &SceneProperties,
     filter_iter: AuxIter<FilterOp>,
     clip_id_mapper: &ClipIdMapper,
 ) {
-    matrix4d_node(parent, "transform", &properties.resolve_layout_transform(&sc.transform));
-
     enum_node(parent, "transform-style", sc.transform_style);
 
     let glyph_raster_space = match sc.glyph_raster_space {
         GlyphRasterSpace::Local(scale) => {
             format!("local({})", scale)
         }
         GlyphRasterSpace::Screen => {
             "screen".to_owned()
         }
     };
     str_node(parent, "glyph-raster-space", &glyph_raster_space);
 
     if let Some(clip_node_id) = sc.clip_node_id {
         yaml_node(parent, "clip-node", clip_id_mapper.map_id(&clip_node_id));
     }
 
-    if let Some(perspective) = sc.perspective {
-        matrix4d_node(parent, "perspective", &perspective);
-    }
-
     // mix_blend_mode
     if sc.mix_blend_mode != MixBlendMode::Normal {
         enum_node(parent, "mix-blend-mode", sc.mix_blend_mode)
     }
     // filters
     let mut filters = vec![];
     for filter in filter_iter {
         match filter {
@@ -1026,16 +1039,29 @@ impl YamlFrameWriter {
                         filters,
                         clip_id_mapper,
                     );
 
                     let mut sub_iter = base.sub_iter();
                     self.write_display_list(&mut v, display_list, scene, &mut sub_iter, clip_id_mapper);
                     continue_traversal = Some(sub_iter);
                 }
+                PushReferenceFrame(item) => {
+                    str_node(&mut v, "type", "reference-frame");
+                    write_reference_frame(
+                        &mut v,
+                        &item.reference_frame,
+                        &scene.properties,
+                        clip_id_mapper
+                    );
+
+                    let mut sub_iter = base.sub_iter();
+                    self.write_display_list(&mut v, display_list, scene, &mut sub_iter, clip_id_mapper);
+                    continue_traversal = Some(sub_iter);
+                }
                 Clip(item) => {
                     str_node(&mut v, "type", "clip");
                     usize_node(&mut v, "id", clip_id_mapper.add_id(item.id));
 
                     let (complex_clips, complex_clip_count) = base.complex_clip();
                     if let Some(complex) = self.make_complex_clips_node(
                         complex_clip_count,
                         complex_clips,
@@ -1116,16 +1142,17 @@ impl YamlFrameWriter {
                     let applied = vec![
                         Yaml::Real(item.previously_applied_offset.x.to_string()),
                         Yaml::Real(item.previously_applied_offset.y.to_string()),
                     ];
                     yaml_node(&mut v, "previously-applied-offset", Yaml::Array(applied));
                 }
 
                 PopStackingContext => return,
+                PopReferenceFrame => return,
                 SetGradientStops => panic!("dummy item yielded?"),
                 PushShadow(shadow) => {
                     str_node(&mut v, "type", "shadow");
                     vector_node(&mut v, "offset", &shadow.offset);
                     color_node(&mut v, "color", shadow.color);
                     f32_node(&mut v, "blur-radius", shadow.blur_radius);
                 }
                 PopAllShadows => {