Bug 1465058 - Update webrender to commit 8e697f8cb1f1aab2e5f6b9b903eb7191340b10c5. r=Gankro
authorKartikaya Gupta <kgupta@mozilla.com>
Mon, 04 Jun 2018 10:53:49 -0400
changeset 421235 a3f7b4a6d494ff6da9511350ad3754c4b7810319
parent 421234 d08944831e26fc9ec613e1fa2dc35d00332586ac
child 421236 ccda83410bf45dedd0e9fc917eb3bb1e819776f6
push id103992
push userdluca@mozilla.com
push dateMon, 04 Jun 2018 18:57:34 +0000
treeherdermozilla-inbound@425c5899aa74 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGankro
bugs1465058
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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 => {