Bug 1457891 - Update webrender to commit 8da531dc2cd77a4dadbfe632b04e76454f51ac9f. r=jrmuizel
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 03 May 2018 09:00:55 -0400
changeset 416847 2d910b41bff477568a84ab3c65656cb744b2ae84
parent 416846 654e81aa75d11605e94e3e9f3a48fd9d48d9b7ac
child 416848 5d477ed8d62de3ff678519f79fc3ac9f23bb91cc
push id102880
push useraciure@mozilla.com
push dateThu, 03 May 2018 21:56:36 +0000
treeherdermozilla-inbound@1557686206e3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1457891
milestone61.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 1457891 - Update webrender to commit 8da531dc2cd77a4dadbfe632b04e76454f51ac9f. r=jrmuizel MozReview-Commit-ID: HTqjYp9gFwA
gfx/webrender/res/brush.glsl
gfx/webrender/res/brush_blend.glsl
gfx/webrender/res/brush_image.glsl
gfx/webrender/res/brush_linear_gradient.glsl
gfx/webrender/res/brush_mix_blend.glsl
gfx/webrender/res/brush_radial_gradient.glsl
gfx/webrender/res/brush_solid.glsl
gfx/webrender/res/brush_yuv_image.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/border.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/ellipse.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/gpu_types.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/platform/windows/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/scene_builder.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/src/wrench.rs
gfx/wrench/src/yaml_frame_reader.rs
--- a/gfx/webrender/res/brush.glsl
+++ b/gfx/webrender/res/brush.glsl
@@ -3,26 +3,31 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifdef WR_VERTEX_SHADER
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
+    int brush_flags,
     vec4 segment_data
 );
 
 #define VECS_PER_BRUSH_PRIM                 2
 #define VECS_PER_SEGMENT                    2
 
 #define BRUSH_FLAG_PERSPECTIVE_INTERPOLATION    1
+#define BRUSH_FLAG_SEGMENT_RELATIVE             2
+#define BRUSH_FLAG_SEGMENT_REPEAT_X             4
+#define BRUSH_FLAG_SEGMENT_REPEAT_Y             8
 
 struct BrushInstance {
     int picture_address;
     int prim_address;
     int clip_chain_rect_index;
     int scroll_node_id;
     int clip_address;
     int z;
@@ -142,19 +147,21 @@ void main(void) {
     );
 #endif
 
     // Run the specific brush VS code to write interpolators.
     brush_vs(
         vi,
         brush.prim_address + VECS_PER_BRUSH_PRIM,
         brush_prim.local_rect,
+        local_segment_rect,
         brush.user_data,
         scroll_node.transform,
         pic_task,
+        brush.flags,
         segment_data[1]
     );
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 
 struct Fragment {
--- a/gfx/webrender/res/brush_blend.glsl
+++ b/gfx/webrender/res/brush_blend.glsl
@@ -1,14 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#define VECS_PER_SPECIFIC_BRUSH 2
-#define FORCE_NO_PERSPECTIVE
+#define VECS_PER_SPECIFIC_BRUSH 3
 
 #include shared,prim_shared,brush
 
 varying vec3 vUv;
 
 flat varying float vAmount;
 flat varying int vOp;
 flat varying mat3 vColorMat;
@@ -16,19 +15,21 @@ flat varying vec3 vColorOffset;
 flat varying vec4 vUvClipBounds;
 
 #ifdef WR_VERTEX_SHADER
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
+    int brush_flags,
     vec4 unused
 ) {
     PictureTask src_task = fetch_picture_task(user_data.x);
     vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
     vec2 uv = vi.snapped_device_pos +
               src_task.common_data.task_rect.p0 -
               src_task.content_origin;
     vUv = vec3(uv / texture_size, src_task.common_data.texture_layer_index);
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#define VECS_PER_SPECIFIC_BRUSH 2
+#define VECS_PER_SPECIFIC_BRUSH 3
 
 #include shared,prim_shared,brush
 
 #ifdef WR_FEATURE_ALPHA_PASS
 varying vec2 vLocalPos;
 #endif
 
 // Interpolated uv coordinates in xy, and layer in z.
@@ -24,23 +24,25 @@ flat varying vec2 vMaskSwizzle;
 flat varying vec2 vTileRepeat;
 #endif
 
 #ifdef WR_VERTEX_SHADER
 
 struct ImageBrushData {
     vec4 color;
     vec4 background_color;
+    vec2 stretch_size;
 };
 
 ImageBrushData fetch_image_data(int address) {
-    vec4[2] raw_data = fetch_from_resource_cache_2(address);
+    vec4[3] raw_data = fetch_from_resource_cache_3(address);
     ImageBrushData data = ImageBrushData(
         raw_data[0],
-        raw_data[1]
+        raw_data[1],
+        raw_data[2].xy
     );
     return data;
 }
 
 #ifdef WR_FEATURE_ALPHA_PASS
 vec2 transform_point_snapped(
     vec2 local_pos,
     RectWithSize local_rect,
@@ -52,34 +54,62 @@ vec2 transform_point_snapped(
 
     return device_pos + snap_offset;
 }
 #endif
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
-    RectWithSize local_rect,
+    RectWithSize prim_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
-    vec4 repeat
+    int brush_flags,
+    vec4 texel_rect
 ) {
+    ImageBrushData image_data = fetch_image_data(prim_address);
+
     // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use
     // non-normalized texture coordinates.
 #ifdef WR_FEATURE_TEXTURE_RECT
     vec2 texture_size = vec2(1, 1);
 #else
     vec2 texture_size = vec2(textureSize(sColor0, 0));
 #endif
 
     ImageResource res = fetch_image_resource(user_data.x);
     vec2 uv0 = res.uv_rect.p0;
     vec2 uv1 = res.uv_rect.p1;
 
+    RectWithSize local_rect = prim_rect;
+    vec2 stretch_size = image_data.stretch_size;
+
+    // If this segment should interpolate relative to the
+    // segment, modify the parameters for that.
+    if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) {
+        local_rect = segment_rect;
+        stretch_size = local_rect.size;
+
+        // Note: Here we can assume that texels in device
+        //       space map to local space, due to how border-image
+        //       works. That assumption may not hold if this
+        //       is used for other purposes in the future.
+        if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X) != 0) {
+            stretch_size.x = texel_rect.z - texel_rect.x;
+        }
+        if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y) != 0) {
+            stretch_size.y = texel_rect.w - texel_rect.y;
+        }
+
+        uv0 = res.uv_rect.p0 + texel_rect.xy;
+        uv1 = res.uv_rect.p0 + texel_rect.zw;
+    }
+
     vUv.z = res.layer;
 
     // Handle case where the UV coords are inverted (e.g. from an
     // external image).
     vec2 min_uv = min(uv0, uv1);
     vec2 max_uv = max(uv0, uv1);
 
     vUvSampleBounds = vec4(
@@ -87,17 +117,16 @@ void brush_vs(
         max_uv - vec2(0.5)
     ) / texture_size.xyxy;
 
     vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
 
 #ifdef WR_FEATURE_ALPHA_PASS
     int color_mode = user_data.y >> 16;
     int raster_space = user_data.y & 0xffff;
-    ImageBrushData image_data = fetch_image_data(prim_address);
 
     if (color_mode == COLOR_MODE_FROM_PASS) {
         color_mode = uMode;
     }
 
     // Derive the texture coordinates for this image, based on
     // whether the source image is a local-space or screen-space
     // image.
@@ -113,16 +142,17 @@ void brush_vs(
             break;
         }
         default:
             break;
     }
 #endif
 
     // Offset and scale vUv here to avoid doing it in the fragment shader.
+    vec2 repeat = local_rect.size / stretch_size;
     vUv.xy = mix(uv0, uv1, f) - min_uv;
     vUv.xy /= texture_size;
     vUv.xy *= repeat.xy;
 
 #ifdef WR_FEATURE_TEXTURE_RECT
     vUvBounds = vec4(0.0, 0.0, vec2(textureSize(sColor0)));
 #else
     vUvBounds = vec4(min_uv, max_uv) / texture_size.xyxy;
--- a/gfx/webrender/res/brush_linear_gradient.glsl
+++ b/gfx/webrender/res/brush_linear_gradient.glsl
@@ -22,53 +22,61 @@ varying vec2 vPos;
 varying vec2 vLocalPos;
 flat varying vec2 vTileRepeat;
 #endif
 
 #ifdef WR_VERTEX_SHADER
 
 struct Gradient {
     vec4 start_end_point;
-    vec4 extend_mode;
+    int extend_mode;
+    vec2 stretch_size;
 };
 
 Gradient fetch_gradient(int address) {
     vec4 data[2] = fetch_from_resource_cache_2(address);
-    return Gradient(data[0], data[1]);
+    return Gradient(
+        data[0],
+        int(data[1].x),
+        data[1].yz
+    );
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
-    vec4 tile_repeat
+    int brush_flags,
+    vec4 unused
 ) {
     Gradient gradient = fetch_gradient(prim_address);
 
     vPos = vi.local_pos - local_rect.p0;
 
     vec2 start_point = gradient.start_end_point.xy;
     vec2 end_point = gradient.start_end_point.zw;
     vec2 dir = end_point - start_point;
 
     vStartPoint = start_point;
     vScaledDir = dir / dot(dir, dir);
 
-    vRepeatedSize = local_rect.size / tile_repeat.xy;
+    vec2 tile_repeat = local_rect.size / gradient.stretch_size;
+    vRepeatedSize = gradient.stretch_size;
 
     vGradientAddress = user_data.x;
 
     // Whether to repeat the gradient along the line instead of clamping.
-    vGradientRepeat = float(int(gradient.extend_mode.x) != EXTEND_MODE_CLAMP);
+    vGradientRepeat = float(gradient.extend_mode != EXTEND_MODE_CLAMP);
 
 #ifdef WR_FEATURE_ALPHA_PASS
-    vTileRepeat = tile_repeat.xy;
+    vTileRepeat = tile_repeat;
     vLocalPos = vi.local_pos;
 #endif
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 Fragment brush_fs() {
 
--- a/gfx/webrender/res/brush_mix_blend.glsl
+++ b/gfx/webrender/res/brush_mix_blend.glsl
@@ -1,29 +1,31 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#define VECS_PER_SPECIFIC_BRUSH 2
+#define VECS_PER_SPECIFIC_BRUSH 3
 
 #include shared,prim_shared,brush
 
 varying vec3 vSrcUv;
 varying vec3 vBackdropUv;
 flat varying int vOp;
 
 #ifdef WR_VERTEX_SHADER
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
+    int brush_flags,
     vec4 unused
 ) {
     vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
     vOp = user_data.x;
 
     PictureTask src_task = fetch_picture_task(user_data.z);
     vec2 src_uv = vi.snapped_device_pos +
                   src_task.common_data.task_rect.p0 -
--- a/gfx/webrender/res/brush_radial_gradient.glsl
+++ b/gfx/webrender/res/brush_radial_gradient.glsl
@@ -20,53 +20,62 @@ flat varying vec2 vRepeatedSize;
 varying vec2 vLocalPos;
 flat varying vec2 vTileRepeat;
 #endif
 
 #ifdef WR_VERTEX_SHADER
 
 struct RadialGradient {
     vec4 center_start_end_radius;
-    vec4 ratio_xy_extend_mode;
+    float ratio_xy;
+    int extend_mode;
+    vec2 stretch_size;
 };
 
 RadialGradient fetch_radial_gradient(int address) {
     vec4 data[2] = fetch_from_resource_cache_2(address);
-    return RadialGradient(data[0], data[1]);
+    return RadialGradient(
+        data[0],
+        data[1].x,
+        int(data[1].y),
+        data[1].zw
+    );
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
-    vec4 tile_repeat
+    int brush_flags,
+    vec4 unused
 ) {
     RadialGradient gradient = fetch_radial_gradient(prim_address);
 
     vPos = vi.local_pos - local_rect.p0;
 
     vCenter = gradient.center_start_end_radius.xy;
     vStartRadius = gradient.center_start_end_radius.z;
     vEndRadius = gradient.center_start_end_radius.w;
 
     // Transform all coordinates by the y scale so the
     // fragment shader can work with circles
-    float ratio_xy = gradient.ratio_xy_extend_mode.x;
-    vPos.y *= ratio_xy;
-    vCenter.y *= ratio_xy;
-    vRepeatedSize = local_rect.size / tile_repeat.xy;
-    vRepeatedSize.y *=  ratio_xy;
+    vec2 tile_repeat = local_rect.size / gradient.stretch_size;
+    vPos.y *= gradient.ratio_xy;
+    vCenter.y *= gradient.ratio_xy;
+    vRepeatedSize = gradient.stretch_size;
+    vRepeatedSize.y *=  gradient.ratio_xy;
 
     vGradientAddress = user_data.x;
 
     // Whether to repeat the gradient instead of clamping.
-    vGradientRepeat = float(int(gradient.ratio_xy_extend_mode.y) != EXTEND_MODE_CLAMP);
+    vGradientRepeat = float(gradient.extend_mode != EXTEND_MODE_CLAMP);
 
 #ifdef WR_FEATURE_ALPHA_PASS
     vTileRepeat = tile_repeat.xy;
     vLocalPos = vi.local_pos;
 #endif
 }
 #endif
 
--- a/gfx/webrender/res/brush_solid.glsl
+++ b/gfx/webrender/res/brush_solid.glsl
@@ -22,19 +22,21 @@ SolidBrush fetch_solid_primitive(int add
     vec4 data = fetch_from_resource_cache_1(address);
     return SolidBrush(data);
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
+    int brush_flags,
     vec4 unused
 ) {
     SolidBrush prim = fetch_solid_primitive(prim_address);
     vColor = prim.color;
 
 #ifdef WR_FEATURE_ALPHA_PASS
     vLocalPos = vi.local_pos;
 #endif
--- a/gfx/webrender/res/brush_yuv_image.glsl
+++ b/gfx/webrender/res/brush_yuv_image.glsl
@@ -69,19 +69,21 @@ void write_uv_rect(
         uv_bounds /= texture_size.xyxy;
     #endif
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
+    RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
+    int brush_flags,
     vec4 unused
 ) {
     vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
 
 #ifdef WR_FEATURE_ALPHA_PASS
     vLocalPos = vi.local_pos;
 #endif
 
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -14,18 +14,19 @@ use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheAddress};
 use gpu_types::{BrushFlags, BrushInstance, ClipChainRectIndex, ClipMaskBorderCornerDotDash};
 use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex, CompositePrimitiveInstance};
 use gpu_types::{PrimitiveInstance, RasterizationSpace, SimplePrimitiveInstance, ZBufferId};
 use gpu_types::ZBufferIdGenerator;
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Polygon, Splitter};
-use prim_store::{CachedGradient, ImageSource, PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
-use prim_store::{BrushPrimitive, BrushKind, DeferredResolve, EdgeAaSegmentMask, PictureIndex, PrimitiveRun};
+use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, CachedGradient, DeferredResolve};
+use prim_store::{EdgeAaSegmentMask, ImageSource, PictureIndex, PrimitiveIndex, PrimitiveKind};
+use prim_store::{PrimitiveMetadata, PrimitiveRun, PrimitiveStore};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind};
 use renderer::{BLOCKS_PER_UV_RECT, ShaderColorMode};
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
 use scene::FilterOpHelpers;
 use std::{usize, f32, i32};
 use tiling::{RenderTargetContext};
 use util::{MatrixHelpers, TransformedRectKind};
@@ -1265,27 +1266,31 @@ impl AlphaBatchBuilder {
                 let opaque_batch = self.batch_list.opaque_batch_list.get_suitable_batch(
                     opaque_batch_key,
                     task_relative_bounding_rect
                 );
 
                 for (i, segment) in segment_desc.segments.iter().enumerate() {
                     let is_inner = segment.edge_flags.is_empty();
                     let needs_blending = !prim_metadata.opacity.is_opaque ||
-                                         segment.clip_task_id.is_some() ||
+                                         segment.clip_task_id.needs_blending() ||
                                          (!is_inner && transform_kind == TransformedRectKind::Complex);
 
-                    let clip_task_address = segment
-                        .clip_task_id
-                        .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
+                    let clip_task_address = match segment.clip_task_id {
+                        BrushSegmentTaskId::RenderTaskId(id) =>
+                            render_tasks.get_task_address(id),
+                        BrushSegmentTaskId::Opaque => OPAQUE_TASK_ADDRESS,
+                        BrushSegmentTaskId::Empty => continue,
+                    };
 
                     let instance = PrimitiveInstance::from(BrushInstance {
                         segment_index: i as i32,
                         edge_flags: segment.edge_flags,
                         clip_task_address,
+                        brush_flags: base_instance.brush_flags | segment.brush_flags,
                         ..base_instance
                     });
 
                     if needs_blending {
                         alpha_batch.push(instance);
                     } else {
                         opaque_batch.push(instance);
                     }
@@ -1356,16 +1361,41 @@ impl BrushPrimitive {
                             cache_item.uv_rect_handle.as_int(gpu_cache),
                             (ShaderColorMode::ColorBitmap as i32) << 16|
                              RasterizationSpace::Local as i32,
                             0,
                         ],
                     ))
                 }
             }
+            BrushKind::Border { request, .. } => {
+                let cache_item = resolve_image(
+                    request,
+                    resource_cache,
+                    gpu_cache,
+                    deferred_resolves,
+                );
+
+                if cache_item.texture_id == SourceTexture::Invalid {
+                    None
+                } else {
+                    let textures = BatchTextures::color(cache_item.texture_id);
+
+                    Some((
+                        BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
+                        textures,
+                        [
+                            cache_item.uv_rect_handle.as_int(gpu_cache),
+                            (ShaderColorMode::ColorBitmap as i32) << 16|
+                             RasterizationSpace::Local as i32,
+                            0,
+                        ],
+                    ))
+                }
+            }
             BrushKind::Picture { .. } => {
                 panic!("bug: get_batch_key is handled at higher level for pictures");
             }
             BrushKind::Solid { .. } => {
                 Some((
                     BrushBatchKind::Solid,
                     BatchTextures::no_texture(),
                     [0; 3],
@@ -1487,16 +1517,17 @@ impl AlphaBatchHelpers for PrimitiveStor
                             AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
                             AlphaType::Alpha => BlendMode::Alpha,
                         }
                     }
                     BrushKind::Solid { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::LinearGradient { .. } |
+                    BrushKind::Border { .. } |
                     BrushKind::Picture { .. } => {
                         BlendMode::PremultipliedAlpha
                     }
                 }
             }
             PrimitiveKind::Image => {
                 let image_cpu = &self.cpu_images[metadata.cpu_prim_index.0];
                 match image_cpu.alpha_type {
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -1,17 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ClipMode, ColorF, LayoutPoint};
-use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, NormalBorder, RepeatMode, TexelRect};
+use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, NormalBorder};
 use clip::ClipSource;
 use ellipse::Ellipse;
 use display_list_flattener::DisplayListFlattener;
+use gpu_types::BrushFlags;
 use gpu_cache::GpuDataRequest;
 use prim_store::{BorderPrimitiveCpu, BrushClipMaskKind, BrushSegment, BrushSegmentDescriptor};
 use prim_store::{EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
 use util::{lerp, pack_as_float};
 
 #[repr(u8)]
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum BorderCornerInstance {
@@ -502,17 +503,19 @@ impl<'a> DisplayListFlattener<'a> {
                 info.rect.origin.y + info.rect.size.height - bottom_len,
             );
             let p3 = info.rect.bottom_right();
 
             let segment = |x0, y0, x1, y1| BrushSegment::new(
                 LayoutPoint::new(x0, y0),
                 LayoutSize::new(x1-x0, y1-y0),
                 true,
-                EdgeAaSegmentMask::all() // Note: this doesn't seem right, needs revision
+                EdgeAaSegmentMask::all(), // Note: this doesn't seem right, needs revision
+                [0.0; 4],
+                BrushFlags::empty(),
             );
 
             // Add a solid rectangle for each visible edge/corner combination.
             if top_edge == BorderEdgeKind::Solid {
                 let descriptor = BrushSegmentDescriptor {
                     segments: vec![
                         segment(p0.x, p0.y, p1.x, p1.y),
                         segment(p2.x, p0.y, p3.x, p1.y),
@@ -921,60 +924,8 @@ struct DotInfo {
     diameter: f32,
 }
 
 impl DotInfo {
     fn new(arc_pos: f32, diameter: f32) -> DotInfo {
         DotInfo { arc_pos, diameter }
     }
 }
-
-#[derive(Debug, Clone)]
-pub struct ImageBorderSegment {
-    pub geom_rect: LayoutRect,
-    pub sub_rect: TexelRect,
-    pub stretch_size: LayoutSize,
-    pub tile_spacing: LayoutSize,
-}
-
-impl ImageBorderSegment {
-    pub fn new(
-        rect: LayoutRect,
-        sub_rect: TexelRect,
-        repeat_horizontal: RepeatMode,
-        repeat_vertical: RepeatMode,
-    ) -> ImageBorderSegment {
-        let tile_spacing = LayoutSize::zero();
-
-        debug_assert!(sub_rect.uv1.x >= sub_rect.uv0.x);
-        debug_assert!(sub_rect.uv1.y >= sub_rect.uv0.y);
-
-        let image_size = LayoutSize::new(
-            sub_rect.uv1.x - sub_rect.uv0.x,
-            sub_rect.uv1.y - sub_rect.uv0.y,
-        );
-
-        let stretch_size_x = match repeat_horizontal {
-            RepeatMode::Stretch => rect.size.width,
-            RepeatMode::Repeat => image_size.width,
-            RepeatMode::Round | RepeatMode::Space => {
-                error!("Round/Space not supported yet!");
-                rect.size.width
-            }
-        };
-
-        let stretch_size_y = match repeat_vertical {
-            RepeatMode::Stretch => rect.size.height,
-            RepeatMode::Repeat => image_size.height,
-            RepeatMode::Round | RepeatMode::Space => {
-                error!("Round/Space not supported yet!");
-                rect.size.height
-            }
-        };
-
-        ImageBorderSegment {
-            geom_rect: rect,
-            sub_rect,
-            stretch_size: LayoutSize::new(stretch_size_x, stretch_size_y),
-            tile_spacing,
-        }
-    }
-}
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -9,30 +9,30 @@ use api::{DevicePixelScale, DeviceUintRe
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, GlyphRasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, LayoutPrimitiveInfo};
 use api::{LayoutRect, LayoutVector2D, LayoutSize, LayoutTransform};
 use api::{LineOrientation, LineStyle, LocalClip, PipelineId, PropertyBinding};
 use api::{RepeatMode, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity, Shadow};
 use api::{SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect, TileOffset};
 use api::{TransformStyle, YuvColorSpace, YuvData};
 use app_units::Au;
-use border::ImageBorderSegment;
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
 use clip_scroll_node::{ClipScrollNode, NodeType, StickyFrameInfo};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
 use euclid::{SideOffsets2D, vec2};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
+use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::{decompose_image, TiledImageInfo};
 use internal_types::{FastHashMap, FastHashSet};
 use picture::PictureCompositeMode;
-use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient};
-use prim_store::{CachedGradientIndex, ImageCacheKey, ImagePrimitiveCpu, ImageSource};
-use prim_store::{PictureIndex, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
+use prim_store::{BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient};
+use prim_store::{CachedGradientIndex, EdgeAaSegmentMask, ImageCacheKey, ImagePrimitiveCpu, ImageSource};
+use prim_store::{BrushSegment, PictureIndex, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitiveCpu};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest, TiledImageMap};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::{BuiltScene, SceneRequest};
 use std::{f32, mem, usize};
 use tiling::{CompositeOps, ScrollbarPrimitive};
 use util::{MaxRect, RectHelpers, recycle_vec};
@@ -1671,29 +1671,52 @@ impl<'a> DisplayListFlattener<'a> {
 
                 let br_outer = LayoutPoint::new(
                     rect.origin.x + rect.size.width,
                     rect.origin.y + rect.size.height,
                 );
                 let br_inner = br_outer - vec2(border_item.widths.right, border_item.widths.bottom);
 
                 fn add_segment(
-                    segments: &mut Vec<ImageBorderSegment>,
+                    segments: &mut Vec<BrushSegment>,
                     rect: LayoutRect,
                     uv_rect: TexelRect,
                     repeat_horizontal: RepeatMode,
-                    repeat_vertical: RepeatMode) {
+                    repeat_vertical: RepeatMode
+                ) {
                     if uv_rect.uv1.x > uv_rect.uv0.x &&
                        uv_rect.uv1.y > uv_rect.uv0.y {
-                        segments.push(ImageBorderSegment::new(
-                            rect,
-                            uv_rect,
-                            repeat_horizontal,
-                            repeat_vertical,
-                        ));
+
+                        // Use segment relative interpolation for all
+                        // instances in this primitive.
+                        let mut brush_flags = BrushFlags::SEGMENT_RELATIVE;
+
+                        // Enable repeat modes on the segment.
+                        if repeat_horizontal == RepeatMode::Repeat {
+                            brush_flags |= BrushFlags::SEGMENT_REPEAT_X;
+                        }
+                        if repeat_vertical == RepeatMode::Repeat {
+                            brush_flags |= BrushFlags::SEGMENT_REPEAT_Y;
+                        }
+
+                        let segment = BrushSegment::new(
+                            rect.origin,
+                            rect.size,
+                            true,
+                            EdgeAaSegmentMask::empty(),
+                            [
+                                uv_rect.uv0.x,
+                                uv_rect.uv0.y,
+                                uv_rect.uv1.x,
+                                uv_rect.uv1.y,
+                            ],
+                            brush_flags,
+                        );
+
+                        segments.push(segment);
                     }
                 }
 
                 // Build the list of image segments
                 let mut segments = vec![];
 
                 // Top left
                 add_segment(
@@ -1769,31 +1792,40 @@ impl<'a> DisplayListFlattener<'a> {
                 add_segment(
                     &mut segments,
                     LayoutRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
                     TexelRect::new(px2, py1, px3, py2),
                     RepeatMode::Stretch,
                     border.repeat_vertical,
                 );
 
-                for segment in segments {
-                    let mut info = info.clone();
-                    info.rect = segment.geom_rect;
-                    self.add_image(
-                        clip_and_scroll,
-                        &info,
-                        segment.stretch_size,
-                        segment.tile_spacing,
-                        Some(segment.sub_rect),
-                        border.image_key,
-                        ImageRendering::Auto,
-                        AlphaType::PremultipliedAlpha,
-                        None,
-                    );
-                }
+                let descriptor = BrushSegmentDescriptor {
+                    segments,
+                    clip_mask_kind: BrushClipMaskKind::Unknown,
+                };
+
+                let prim = BrushPrimitive::new(
+                    BrushKind::Border {
+                        request: ImageRequest {
+                            key: border.image_key,
+                            rendering: ImageRendering::Auto,
+                            tile: None,
+                        },
+                    },
+                    Some(descriptor),
+                );
+
+                let prim = PrimitiveContainer::Brush(prim);
+
+                self.add_primitive(
+                    clip_and_scroll,
+                    info,
+                    Vec::new(),
+                    prim,
+                );
             }
             BorderDetails::Normal(ref border) => {
                 self.add_normal_border(info, border, &border_item.widths, clip_and_scroll);
             }
             BorderDetails::Gradient(ref border) => for segment in create_segments(border.outset) {
                 let segment_rel = segment.origin - rect.origin;
                 let mut info = info.clone();
                 info.rect = segment;
--- a/gfx/webrender/src/ellipse.rs
+++ b/gfx/webrender/src/ellipse.rs
@@ -32,28 +32,39 @@ impl Ellipse {
     pub fn find_angle_for_arc_length(&self, arc_length: f32) -> f32 {
         // Clamp arc length to [0, pi].
         let arc_length = arc_length.max(0.0).min(self.total_arc_length);
 
         let epsilon = 0.01;
         let mut low = 0.0;
         let mut high = FRAC_PI_2;
         let mut theta = 0.0;
+        let mut new_low = 0.0;
+        let mut new_high = FRAC_PI_2;
 
         while low <= high {
             theta = 0.5 * (low + high);
             let length = get_simpson_length(theta, self.radius.width, self.radius.height);
 
             if (length - arc_length).abs() < epsilon {
                 break;
             } else if length < arc_length {
-                low = theta;
+                new_low = theta;
             } else {
-                high = theta;
+                new_high = theta;
             }
+
+            // If we have stopped moving down the arc, the answer that we have is as good as
+            // it is going to get. We break to avoid going into an infinite loop.
+            if new_low == low && new_high == high {
+                break;
+            }
+
+            high = new_high;
+            low = new_low;
         }
 
         theta
     }
 
     /// Get a point and tangent on this ellipse from a given angle.
     /// This only works for the first quadrant of the ellipse.
     pub fn get_point_and_tangent(&self, theta: f32) -> (LayoutPoint, LayoutPoint) {
@@ -149,8 +160,26 @@ fn get_simpson_length(theta: f32, rx: f3
             4.0
         };
 
         sum += q * y;
     }
 
     (df / 3.0) * sum
 }
+
+#[cfg(test)]
+pub mod test {
+    use super::*;
+
+    #[test]
+    fn find_angle_for_arc_length_for_long_eclipse() {
+        // Ensure that finding the angle on giant ellipses produces and answer and
+        // doesn't send us into an infinite loop.
+        let ellipse = Ellipse::new(LayoutSize::new(57500.0, 25.0));
+        let _ = ellipse.find_angle_for_arc_length(55674.53);
+        assert!(true);
+
+        let ellipse = Ellipse::new(LayoutSize::new(25.0, 57500.0));
+        let _ = ellipse.find_angle_for_arc_length(55674.53);
+        assert!(true);
+    }
+}
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -219,16 +219,20 @@ impl FontInstance {
         color: ColorF,
         bg_color: ColorU,
         render_mode: FontRenderMode,
         subpx_dir: SubpixelDirection,
         flags: FontInstanceFlags,
         platform_options: Option<FontInstancePlatformOptions>,
         variations: Vec<FontVariation>,
     ) -> Self {
+        // If a background color is enabled, it only makes sense
+        // for it to be completely opaque.
+        debug_assert!(bg_color.a == 0 || bg_color.a == 255);
+
         FontInstance {
             font_key,
             size,
             color: color.into(),
             bg_color,
             render_mode,
             subpx_dir,
             flags,
@@ -1071,9 +1075,9 @@ fn request_render_task_from_pathfinder(g
         FontRenderMode::Subpixel => &mut render_passes.color_glyph_pass,
     };
     render_pass.add_render_task(root_task_id, *glyph_size, RenderTargetKind::Color);
 
     Ok(root_task_id)
 }
 
 #[cfg(feature = "pathfinder")]
-pub struct NativeFontHandleWrapper<'a>(pub &'a NativeFontHandle);
+pub struct NativeFontHandleWrapper<'a>(pub &'a NativeFontHandle);
\ No newline at end of file
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -196,17 +196,25 @@ impl From<CompositePrimitiveInstance> fo
         }
     }
 }
 
 bitflags! {
     /// Flags that define how the common brush shader
     /// code should process this instance.
     pub struct BrushFlags: u8 {
+        /// Apply perspective interpolation to UVs
         const PERSPECTIVE_INTERPOLATION = 0x1;
+        /// Do interpolation relative to segment rect,
+        /// rather than primitive rect.
+        const SEGMENT_RELATIVE = 0x2;
+        /// Repeat UVs horizontally.
+        const SEGMENT_REPEAT_X = 0x4;
+        /// Repeat UVs vertically.
+        const SEGMENT_REPEAT_Y = 0x8;
     }
 }
 
 // TODO(gw): While we are converting things over, we
 //           need to have the instance be the same
 //           size as an old PrimitiveInstance. In the
 //           future, we can compress this vertex
 //           format a lot - e.g. z, render task
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -474,31 +474,37 @@ impl PicturePrimitive {
                     //           rect for the shadow. To tidy this up in future,
                     //           we could consider abstracting the code in prim_store.rs
                     //           that writes a brush primitive header.
 
                     // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
                     //  local_rect
                     //  clip_rect
                     //  [brush specific data]
-                    //  [segment_rect, (repetitions.xy, 0.0, 0.0)]
+                    //  [segment_rect, segment data]
                     let shadow_rect = prim_metadata.local_rect.translate(&offset);
                     let shadow_clip_rect = prim_metadata.local_clip_rect.translate(&offset);
 
                     // local_rect, clip_rect
                     request.push(shadow_rect);
                     request.push(shadow_clip_rect);
 
                     // ImageBrush colors
                     request.push(color.premultiplied());
                     request.push(PremultipliedColorF::WHITE);
+                    request.push([
+                        prim_metadata.local_rect.size.width,
+                        prim_metadata.local_rect.size.height,
+                        0.0,
+                        0.0,
+                    ]);
 
-                    // segment rect / repetitions
+                    // segment rect / extra data
                     request.push(shadow_rect);
-                    request.push([1.0, 1.0, 0.0, 0.0]);
+                    request.push([0.0, 0.0, 0.0, 0.0]);
                 }
             }
             Some(PictureCompositeMode::MixBlend(..)) => {
                 let uv_rect_kind = calculate_uv_rect_kind(
                     &prim_metadata.local_rect,
                     &prim_run_context.scroll_node,
                     &prim_screen_rect.clipped,
                     frame_context.device_pixel_scale,
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -6,16 +6,20 @@ use api::{FontInstanceFlags, FontKey, Fo
 use api::{ColorU, GlyphDimensions, GlyphKey, SubpixelDirection};
 use dwrote;
 use gamma_lut::{ColorLut, GammaLut};
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphFormat};
 use glyph_rasterizer::{GlyphRasterResult, RasterizedGlyph};
 use internal_types::{FastHashMap, ResourceCacheError};
 use std::collections::hash_map::Entry;
 use std::sync::Arc;
+#[cfg(feature = "pathfinder")]
+use pathfinder_font_renderer::{PathfinderComPtr, IDWriteFontFace};
+#[cfg(feature = "pathfinder")]
+use glyph_rasterizer::NativeFontHandleWrapper;
 
 lazy_static! {
     static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor {
         family_name: "Arial".to_owned(),
         weight: dwrote::FontWeight::Regular,
         stretch: dwrote::FontStretch::Normal,
         style: dwrote::FontStyle::Normal,
     };
@@ -434,8 +438,21 @@ impl FontContext {
             width,
             height,
             scale: if bitmaps { y_scale.recip() as f32 } else { 1.0 },
             format: if bitmaps { GlyphFormat::Bitmap } else { font.get_glyph_format() },
             bytes: bgra_pixels,
         })
     }
 }
+
+#[cfg(feature = "pathfinder")]
+impl<'a> From<NativeFontHandleWrapper<'a>> for PathfinderComPtr<IDWriteFontFace> {
+    fn from(font_handle: NativeFontHandleWrapper<'a>) -> Self {
+        let system_fc = ::dwrote::FontCollection::system();
+        let font = match system_fc.get_font_from_descriptor(&font_handle.0) {
+            Some(font) => font,
+            None => panic!("missing descriptor {:?}", font_handle.0),
+        };
+        let face = font.create_font_face();
+        PathfinderComPtr::new(face.as_ptr())
+    }
+}
\ No newline at end of file
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -13,17 +13,17 @@ use clip_scroll_tree::{ClipChainIndex, C
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
 use clip::{ClipSourcesHandle, ClipWorkItem};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveRunContext;
 use glyph_rasterizer::{FontInstance, FontTransform};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
-use gpu_types::{ClipChainRectIndex};
+use gpu_types::{BrushFlags, ClipChainRectIndex};
 use picture::{PictureCompositeMode, PictureId, PicturePrimitive};
 #[cfg(debug_assertions)]
 use render_backend::FrameId;
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest};
 use scene::SceneProperties;
@@ -282,26 +282,30 @@ pub enum BrushKind {
         gradient_index: CachedGradientIndex,
         stops_range: ItemRange<GradientStop>,
         stops_count: usize,
         extend_mode: ExtendMode,
         reverse_stops: bool,
         start_point: LayoutPoint,
         end_point: LayoutPoint,
         stretch_size: LayoutSize,
-    }
+    },
+    Border {
+        request: ImageRequest,
+    },
 }
 
 impl BrushKind {
     fn supports_segments(&self) -> bool {
         match *self {
             BrushKind::Solid { .. } |
             BrushKind::Image { .. } |
             BrushKind::YuvImage { .. } |
             BrushKind::RadialGradient { .. } |
+            BrushKind::Border { .. } |
             BrushKind::LinearGradient { .. } => true,
 
             // TODO(gw): Allow batch.rs to add segment instances
             //           for Picture primitives.
             BrushKind::Picture { .. } => false,
 
             BrushKind::Clear => false,
         }
@@ -327,35 +331,57 @@ bitflags! {
         const LEFT = 0x1;
         const TOP = 0x2;
         const RIGHT = 0x4;
         const BOTTOM = 0x8;
     }
 }
 
 #[derive(Debug)]
+pub enum BrushSegmentTaskId {
+    RenderTaskId(RenderTaskId),
+    Opaque,
+    Empty,
+}
+
+impl BrushSegmentTaskId {
+    pub fn needs_blending(&self) -> bool {
+        match *self {
+            BrushSegmentTaskId::RenderTaskId(..) => true,
+            BrushSegmentTaskId::Opaque | BrushSegmentTaskId::Empty => false,
+        }
+    }
+}
+
+#[derive(Debug)]
 pub struct BrushSegment {
     pub local_rect: LayoutRect,
-    pub clip_task_id: Option<RenderTaskId>,
+    pub clip_task_id: BrushSegmentTaskId,
     pub may_need_clip_mask: bool,
     pub edge_flags: EdgeAaSegmentMask,
+    pub extra_data: [f32; 4],
+    pub brush_flags: BrushFlags,
 }
 
 impl BrushSegment {
     pub fn new(
         origin: LayoutPoint,
         size: LayoutSize,
         may_need_clip_mask: bool,
         edge_flags: EdgeAaSegmentMask,
+        extra_data: [f32; 4],
+        brush_flags: BrushFlags,
     ) -> BrushSegment {
         BrushSegment {
             local_rect: LayoutRect::new(origin, size),
-            clip_task_id: None,
+            clip_task_id: BrushSegmentTaskId::Opaque,
             may_need_clip_mask,
             edge_flags,
+            extra_data,
+            brush_flags,
         }
     }
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum BrushClipMaskKind {
     Unknown,
     Individual,
@@ -392,64 +418,85 @@ impl BrushPrimitive {
             },
             segment_desc: None,
         }
     }
 
     fn write_gpu_blocks(
         &self,
         request: &mut GpuDataRequest,
+        local_rect: LayoutRect,
     ) {
         // has to match VECS_PER_SPECIFIC_BRUSH
         match self.kind {
+            BrushKind::Border { .. } => {
+                // Border primitives currently used for
+                // image borders, and run through the
+                // normal brush_image shader.
+                request.push(PremultipliedColorF::WHITE);
+                request.push(PremultipliedColorF::WHITE);
+                request.push([
+                    local_rect.size.width,
+                    local_rect.size.height,
+                    0.0,
+                    0.0,
+                ]);
+            }
             BrushKind::YuvImage { .. } => {}
             BrushKind::Picture { .. } => {
                 request.push(PremultipliedColorF::WHITE);
                 request.push(PremultipliedColorF::WHITE);
+                request.push([
+                    local_rect.size.width,
+                    local_rect.size.height,
+                    0.0,
+                    0.0,
+                ]);
             }
             // Images are drawn as a white color, modulated by the total
             // opacity coming from any collapsed property bindings.
-            BrushKind::Image { ref opacity_binding, .. } => {
+            BrushKind::Image { stretch_size, ref opacity_binding, .. } => {
                 request.push(ColorF::new(1.0, 1.0, 1.0, opacity_binding.current).premultiplied());
                 request.push(PremultipliedColorF::WHITE);
+                request.push([stretch_size.width, stretch_size.height, 0.0, 0.0]);
             }
             // Solid rects also support opacity collapsing.
             BrushKind::Solid { color, ref opacity_binding, .. } => {
                 request.push(color.scale_alpha(opacity_binding.current).premultiplied());
             }
             BrushKind::Clear => {
                 // Opaque black with operator dest out
                 request.push(PremultipliedColorF::BLACK);
             }
-            BrushKind::LinearGradient { start_point, end_point, extend_mode, .. } => {
+            BrushKind::LinearGradient { stretch_size, start_point, end_point, extend_mode, .. } => {
                 request.push([
                     start_point.x,
                     start_point.y,
                     end_point.x,
                     end_point.y,
                 ]);
                 request.push([
                     pack_as_float(extend_mode as u32),
-                    0.0,
-                    0.0,
+                    stretch_size.width,
+                    stretch_size.height,
                     0.0,
                 ]);
             }
-            BrushKind::RadialGradient { center, start_radius, end_radius, ratio_xy, extend_mode, .. } => {
+            BrushKind::RadialGradient { stretch_size, center, start_radius, end_radius, ratio_xy, extend_mode, .. } => {
                 request.push([
                     center.x,
                     center.y,
                     start_radius,
                     end_radius,
                 ]);
                 request.push([
                     ratio_xy,
                     pack_as_float(extend_mode as u32),
-                    0.,
-                    0.,
+                    stretch_size.width,
+                    stretch_size.height,
                 ]);
             }
         }
     }
 }
 
 // Key that identifies a unique (partial) image that is being
 // stored in the render task cache.
@@ -590,19 +637,26 @@ impl<'a> GradientGpuBlockBuilder<'a> {
         let src_stops = self.display_list.get(self.stops_range);
 
         // Preconditions (should be ensured by DisplayListBuilder):
         // * we have at least two stops
         // * first stop has offset 0.0
         // * last stop has offset 1.0
 
         let mut src_stops = src_stops.into_iter();
-        let first = src_stops.next().unwrap();
-        let mut cur_color = first.color.premultiplied();
-        debug_assert_eq!(first.offset, 0.0);
+        let mut cur_color = match src_stops.next() {
+            Some(stop) => {
+                debug_assert_eq!(stop.offset, 0.0);
+                stop.color.premultiplied()
+            }
+            None => {
+                error!("Zero gradient stops found!");
+                PremultipliedColorF::BLACK
+            }
+        };
 
         // A table of gradient entries, with two colors per entry, that specify the start and end color
         // within the segment of the gradient space represented by that entry. To lookup a gradient result,
         // first the entry index is calculated to determine which two colors to interpolate between, then
         // the offset within that entry bucket is used to interpolate between the two colors in that entry.
         // This layout preserves hard stops, as the end color for a given entry can differ from the start
         // color for the following entry, despite them being adjacent. Colors are stored within in BGRA8
         // format for texture upload. This table requires the gradient color stops to be normalized to the
@@ -630,17 +684,20 @@ impl<'a> GradientGpuBlockBuilder<'a> {
 
                 if next_idx < cur_idx {
                     self.fill_colors(next_idx, cur_idx, &next_color, &cur_color, &mut entries);
                     cur_idx = next_idx;
                 }
 
                 cur_color = next_color;
             }
-            debug_assert_eq!(cur_idx, GRADIENT_DATA_TABLE_BEGIN);
+            if cur_idx != GRADIENT_DATA_TABLE_BEGIN {
+                error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
+                self.fill_colors(GRADIENT_DATA_TABLE_BEGIN, cur_idx, &PremultipliedColorF::WHITE, &cur_color, &mut entries);
+            }
 
             // Fill in the last entry (for reversed stops) with the last color stop
             self.fill_colors(
                 GRADIENT_DATA_FIRST_STOP,
                 GRADIENT_DATA_FIRST_STOP + 1,
                 &cur_color,
                 &cur_color,
                 &mut entries,
@@ -665,17 +722,21 @@ impl<'a> GradientGpuBlockBuilder<'a> {
 
                 if next_idx > cur_idx {
                     self.fill_colors(cur_idx, next_idx, &cur_color, &next_color, &mut entries);
                     cur_idx = next_idx;
                 }
 
                 cur_color = next_color;
             }
-            debug_assert_eq!(cur_idx, GRADIENT_DATA_TABLE_END);
+            if cur_idx != GRADIENT_DATA_TABLE_END {
+                error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
+                self.fill_colors(cur_idx, GRADIENT_DATA_TABLE_END, &PremultipliedColorF::WHITE, &cur_color, &mut entries);
+            }
+
 
             // Fill in the last entry with the last color stop
             self.fill_colors(
                 GRADIENT_DATA_LAST_STOP,
                 GRADIENT_DATA_LAST_STOP + 1,
                 &cur_color,
                 &cur_color,
                 &mut entries,
@@ -723,17 +784,17 @@ impl TextRunPrimitiveCpu {
     fn prepare_for_render(
         &mut self,
         device_pixel_scale: DevicePixelScale,
         transform: Option<LayoutToWorldTransform>,
         allow_subpixel_aa: bool,
         display_list: &BuiltDisplayList,
         frame_building_state: &mut FrameBuildingState,
     ) {
-        if !allow_subpixel_aa {
+        if !allow_subpixel_aa && self.font.bg_color.a == 0 {
             self.font.render_mode = self.font.render_mode.limit_by(FontRenderMode::Alpha);
         }
 
         let font = self.get_font(device_pixel_scale, transform);
 
         // Cache the glyph positions, if not in the cache already.
         // TODO(gw): In the future, remove `glyph_instances`
         //           completely, and just reference the glyphs
@@ -1003,16 +1064,17 @@ impl PrimitiveContainer {
                     BrushKind::Solid { ref color, .. } => {
                         color.a > 0.0
                     }
                     BrushKind::Clear |
                     BrushKind::Picture { .. } |
                     BrushKind::Image { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
+                    BrushKind::Border { .. } |
                     BrushKind::LinearGradient { .. } => {
                         true
                     }
                 }
             }
             PrimitiveContainer::Image(..) |
             PrimitiveContainer::Border(..) => {
                 true
@@ -1053,16 +1115,17 @@ impl PrimitiveContainer {
                             BrushKind::new_solid(shadow.color),
                             None,
                         ))
                     }
                     BrushKind::Clear |
                     BrushKind::Picture { .. } |
                     BrushKind::Image { .. } |
                     BrushKind::YuvImage { .. } |
+                    BrushKind::Border { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::LinearGradient { .. } => {
                         panic!("bug: other brush kinds not expected here yet");
                     }
                 }
             }
             PrimitiveContainer::Image(..) |
             PrimitiveContainer::Border(..) => {
@@ -1169,16 +1232,17 @@ impl PrimitiveStore {
                 let opacity = match brush.kind {
                     BrushKind::Clear => PrimitiveOpacity::translucent(),
                     BrushKind::Solid { ref color, .. } => PrimitiveOpacity::from_alpha(color.a),
                     BrushKind::Image { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::YuvImage { .. } => PrimitiveOpacity::opaque(),
                     BrushKind::RadialGradient { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::LinearGradient { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Picture { .. } => PrimitiveOpacity::translucent(),
+                    BrushKind::Border { .. } => PrimitiveOpacity::translucent(),
                 };
 
                 let metadata = PrimitiveMetadata {
                     opacity,
                     prim_kind: PrimitiveKind::Brush,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_brushes.len()),
                     ..base_metadata
                 };
@@ -1265,16 +1329,17 @@ impl PrimitiveStore {
                             return self.get_opacity_collapse_prim(pic_index);
                         }
                     }
                     // If we find a single rect or image, we can use that
                     // as the primitive to collapse the opacity into.
                     BrushKind::Solid { .. } | BrushKind::Image { .. } => {
                         return Some(run.base_prim_index)
                     }
+                    BrushKind::Border { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::LinearGradient { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::Clear => {}
                 }
             }
             PrimitiveKind::TextRun |
             PrimitiveKind::Image |
@@ -1315,16 +1380,17 @@ impl PrimitiveStore {
                     match brush.kind {
                         BrushKind::Solid { ref mut opacity_binding, .. } |
                         BrushKind::Image { ref mut opacity_binding, .. } => {
                             opacity_binding.push(binding);
                         }
                         BrushKind::Clear { .. } |
                         BrushKind::Picture { .. } |
                         BrushKind::YuvImage { .. } |
+                        BrushKind::Border { .. } |
                         BrushKind::LinearGradient { .. } |
                         BrushKind::RadialGradient { .. } => {
                             unreachable!("bug: invalid prim type for opacity collapse");
                         }
                     };
                 }
                 PrimitiveKind::TextRun |
                 PrimitiveKind::Image |
@@ -1607,16 +1673,33 @@ impl PrimitiveStore {
                                     key: yuv_key[channel],
                                     rendering: image_rendering,
                                     tile: None,
                                 },
                                 frame_state.gpu_cache,
                             );
                         }
                     }
+                    BrushKind::Border { request, .. } => {
+                        let image_properties = frame_state
+                            .resource_cache
+                            .get_image_properties(request.key);
+
+                        if let Some(image_properties) = image_properties {
+                            // Update opacity for this primitive to ensure the correct
+                            // batching parameters are used.
+                            metadata.opacity.is_opaque =
+                                image_properties.descriptor.is_opaque;
+
+                            frame_state.resource_cache.request_image(
+                                request,
+                                frame_state.gpu_cache,
+                            );
+                        }
+                    }
                     BrushKind::RadialGradient { gradient_index, stops_range, .. } => {
                         let stops_handle = &mut frame_state.cached_gradients[gradient_index.0].handle;
                         if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) {
                             let gradient_builder = GradientGpuBlockBuilder::new(
                                 stops_range,
                                 pic_context.display_list,
                             );
                             gradient_builder.build(
@@ -1681,43 +1764,33 @@ impl PrimitiveStore {
                     image.write_gpu_blocks(request);
                 }
                 PrimitiveKind::TextRun => {
                     let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
                     text.write_gpu_blocks(&mut request);
                 }
                 PrimitiveKind::Brush => {
                     let brush = &self.cpu_brushes[metadata.cpu_prim_index.0];
-                    brush.write_gpu_blocks(&mut request);
-
-                    let repeat = match brush.kind {
-                        BrushKind::Image { stretch_size, .. } |
-                        BrushKind::LinearGradient { stretch_size, .. } |
-                        BrushKind::RadialGradient { stretch_size, .. } => {
-                            [
-                                metadata.local_rect.size.width / stretch_size.width,
-                                metadata.local_rect.size.height / stretch_size.height,
-                                0.0,
-                                0.0,
-                            ]
-                        }
-                        _ => {
-                            [1.0, 1.0, 0.0, 0.0]
-                        }
-                    };
+                    brush.write_gpu_blocks(&mut request, metadata.local_rect);
 
                     match brush.segment_desc {
                         Some(ref segment_desc) => {
                             for segment in &segment_desc.segments {
                                 // has to match VECS_PER_SEGMENT
-                                request.write_segment(segment.local_rect, repeat);
+                                request.write_segment(
+                                    segment.local_rect,
+                                    segment.extra_data,
+                                );
                             }
                         }
                         None => {
-                            request.write_segment(metadata.local_rect, repeat);
+                            request.write_segment(
+                                metadata.local_rect,
+                                [0.0; 4],
+                            );
                         }
                     }
                 }
             }
         }
     }
 
     fn write_brush_segment_description(
@@ -1866,16 +1939,18 @@ impl PrimitiveStore {
 
                     segment_builder.build(|segment| {
                         segments.push(
                             BrushSegment::new(
                                 segment.rect.origin,
                                 segment.rect.size,
                                 segment.has_mask,
                                 segment.edge_flags,
+                                [0.0; 4],
+                                BrushFlags::empty(),
                             ),
                         );
                     });
 
                     brush.segment_desc = Some(BrushSegmentDescriptor {
                         segments,
                         clip_mask_kind,
                     });
@@ -1918,55 +1993,59 @@ impl PrimitiveStore {
         let segment_desc = match brush.segment_desc {
             Some(ref mut description) => description,
             None => return false,
         };
         let clip_mask_kind = segment_desc.clip_mask_kind;
 
         for segment in &mut segment_desc.segments {
             if !segment.may_need_clip_mask && clip_mask_kind != BrushClipMaskKind::Global {
-                segment.clip_task_id = None;
+                segment.clip_task_id = BrushSegmentTaskId::Opaque;
                 continue;
             }
 
             let segment_screen_rect = calculate_screen_bounding_rect(
                 &prim_run_context.scroll_node.world_content_transform,
                 &segment.local_rect,
                 frame_context.device_pixel_scale,
             );
 
-            let intersected_rect = combined_outer_rect.intersection(&segment_screen_rect);
-            segment.clip_task_id = intersected_rect.map(|bounds| {
-                let clip_task = RenderTask::new_mask(
-                    bounds,
-                    clips.clone(),
-                    prim_run_context.scroll_node.coordinate_system_id,
-                    frame_state.clip_store,
-                    frame_state.gpu_cache,
-                    frame_state.resource_cache,
-                    frame_state.render_tasks,
-                );
+            let bounds = match combined_outer_rect.intersection(&segment_screen_rect) {
+                Some(bounds) => bounds,
+                None => {
+                    segment.clip_task_id = BrushSegmentTaskId::Empty;
+                    continue;
+                }
+            };
 
-                let clip_task_id = frame_state.render_tasks.add(clip_task);
-                pic_state.tasks.push(clip_task_id);
+            let clip_task = RenderTask::new_mask(
+                bounds,
+                clips.clone(),
+                prim_run_context.scroll_node.coordinate_system_id,
+                frame_state.clip_store,
+                frame_state.gpu_cache,
+                frame_state.resource_cache,
+                frame_state.render_tasks,
+            );
 
-                clip_task_id
-            })
+            let clip_task_id = frame_state.render_tasks.add(clip_task);
+            pic_state.tasks.push(clip_task_id);
+            segment.clip_task_id = BrushSegmentTaskId::RenderTaskId(clip_task_id);
         }
 
         true
     }
 
     fn reset_clip_task(&mut self, prim_index: PrimitiveIndex) {
         let metadata = &mut self.cpu_metadata[prim_index.0];
         metadata.clip_task_id = None;
         if metadata.prim_kind == PrimitiveKind::Brush {
             if let Some(ref mut desc) = self.cpu_brushes[metadata.cpu_prim_index.0].segment_desc {
                 for segment in &mut desc.segments {
-                    segment.clip_task_id = None;
+                    segment.clip_task_id = BrushSegmentTaskId::Opaque;
                 }
             }
         }
     }
 
     fn update_clip_task(
         &mut self,
         prim_index: PrimitiveIndex,
@@ -2470,21 +2549,17 @@ fn get_local_clip_rect_for_nodes(
     match local_rect {
         Some(local_rect) => scroll_node.coordinate_system_relative_transform.unapply(&local_rect),
         None => None,
     }
 }
 
 impl<'a> GpuDataRequest<'a> {
     // Write the GPU cache data for an individual segment.
-    // TODO(gw): The second block is currently unused. In
-    //           the future, it will be used to store a
-    //           UV rect, allowing segments to reference
-    //           part of an image.
     fn write_segment(
         &mut self,
         local_rect: LayoutRect,
-        extra_params: [f32; 4],
+        extra_data: [f32; 4],
     ) {
         self.push(local_rect);
-        self.push(extra_params);
+        self.push(extra_data);
     }
 }
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -35,17 +35,17 @@ use scene_builder::*;
 #[cfg(feature = "serialize")]
 use serde::{Serialize, Deserialize};
 #[cfg(feature = "debugger")]
 use serde_json;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use std::path::PathBuf;
 use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
 use std::mem::replace;
-use std::sync::mpsc::{Sender, Receiver};
+use std::sync::mpsc::{channel, Sender, Receiver};
 use std::u32;
 use tiling::Frame;
 use time::precise_time_ns;
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Clone)]
 pub struct DocumentView {
@@ -259,17 +259,16 @@ impl Document {
         };
 
         scene_tx.send(SceneBuilderRequest::Transaction {
             scene: scene_request,
             resource_updates: transaction_msg.resource_updates,
             frame_ops: transaction_msg.frame_ops,
             render: transaction_msg.generate_frame,
             document_id,
-            current_epochs: self.current.scene.pipeline_epochs.clone(),
         }).unwrap();
     }
 
     fn render(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         resource_profile: &mut ResourceProfileCounters,
@@ -701,22 +700,32 @@ impl RenderBackend {
                         render,
                         result_tx,
                     } => {
                         if let Some(doc) = self.documents.get_mut(&document_id) {
                             if let Some(mut built_scene) = built_scene.take() {
                                 doc.new_async_scene_ready(built_scene);
                                 doc.render_on_hittest = true;
                             }
-                            result_tx.send(SceneSwapResult::Complete).unwrap();
+                            if let Some(tx) = result_tx {
+                                let (resume_tx, resume_rx) = channel();
+                                tx.send(SceneSwapResult::Complete(resume_tx)).unwrap();
+                                // Block until the post-swap hook has completed on
+                                // the scene builder thread. We need to do this before
+                                // we can sample from the sampler hook which might happen
+                                // in the update_document call below.
+                                resume_rx.recv().ok();
+                            }
                         } else {
                             // The document was removed while we were building it, skip it.
                             // TODO: we might want to just ensure that removed documents are
                             // always forwarded to the scene builder thread to avoid this case.
-                            result_tx.send(SceneSwapResult::Aborted).unwrap();
+                            if let Some(tx) = result_tx {
+                                tx.send(SceneSwapResult::Aborted).unwrap();
+                            }
                             continue;
                         }
 
                         let transaction_msg = TransactionMsg {
                             scene_ops: Vec::new(),
                             frame_ops,
                             resource_updates,
                             generate_frame: render,
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -2886,16 +2886,23 @@ impl Renderer {
 
         for alpha_batch_container in &target.alpha_batch_containers {
             if let Some(target_rect) = alpha_batch_container.target_rect {
                 self.device.enable_scissor();
                 self.device.set_scissor_rect(target_rect);
             }
 
             for batch in &alpha_batch_container.alpha_batches {
+                self.shaders
+                    .get(&batch.key)
+                    .bind(
+                        &mut self.device, projection,
+                        &mut self.renderer_errors,
+                    );
+
                 if batch.key.blend_mode != prev_blend_mode {
                     match batch.key.blend_mode {
                         BlendMode::None => {
                             unreachable!("bug: opaque blend in alpha pass");
                         }
                         BlendMode::Alpha => {
                             self.device.set_blend_mode_alpha();
                         }
@@ -2919,23 +2926,16 @@ impl Renderer {
                             //
                             self.device.set_blend_mode_subpixel_with_bg_color_pass0();
                             self.device.switch_mode(ShaderColorMode::SubpixelWithBgColorPass0 as _);
                         }
                     }
                     prev_blend_mode = batch.key.blend_mode;
                 }
 
-                self.shaders
-                    .get(&batch.key)
-                    .bind(
-                        &mut self.device, projection,
-                        &mut self.renderer_errors,
-                    );
-
                 // Handle special case readback for composites.
                 if let BatchKind::Brush(BrushBatchKind::MixBlend { task_id, source_id, backdrop_id }) = batch.key.kind {
                     // composites can't be grouped together because
                     // they may overlap and affect each other.
                     debug_assert_eq!(batch.instances.len(), 1);
                     self.handle_readback_composite(
                         render_target,
                         target_size,
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -1,58 +1,57 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{DocumentId, Epoch, PipelineId, ApiMsg, FrameMsg, ResourceUpdates};
+use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdates};
 use api::channel::MsgSender;
 use display_list_flattener::build_scene;
 use frame_builder::{FrameBuilderConfig, FrameBuilder};
 use clip_scroll_tree::ClipScrollTree;
-use internal_types::{FastHashMap, FastHashSet};
+use internal_types::FastHashSet;
 use resource_cache::{FontInstanceMap, TiledImageMap};
 use render_backend::DocumentView;
 use renderer::{PipelineInfo, SceneBuilderHooks};
 use scene::Scene;
 use std::sync::mpsc::{channel, Receiver, Sender};
 
 // Message from render backend to scene builder.
 pub enum SceneBuilderRequest {
     Transaction {
         document_id: DocumentId,
         scene: Option<SceneRequest>,
         resource_updates: ResourceUpdates,
         frame_ops: Vec<FrameMsg>,
         render: bool,
-        current_epochs: FastHashMap<PipelineId, Epoch>,
     },
     WakeUp,
     Flush(MsgSender<()>),
     Stop
 }
 
 // Message from scene builder to render backend.
 pub enum SceneBuilderResult {
     Transaction {
         document_id: DocumentId,
         built_scene: Option<BuiltScene>,
         resource_updates: ResourceUpdates,
         frame_ops: Vec<FrameMsg>,
         render: bool,
-        result_tx: Sender<SceneSwapResult>,
+        result_tx: Option<Sender<SceneSwapResult>>,
     },
     FlushComplete(MsgSender<()>),
     Stopped,
 }
 
 // Message from render backend to scene builder to indicate the
 // scene swap was completed. We need a separate channel for this
 // so that they don't get mixed with SceneBuilderRequest messages.
 pub enum SceneSwapResult {
-    Complete,
+    Complete(Sender<()>),
     Aborted,
 }
 
 /// Contains the render backend data needed to build a scene.
 pub struct SceneRequest {
     pub scene: Scene,
     pub view: DocumentView,
     pub font_instances: FontInstanceMap,
@@ -132,54 +131,65 @@ impl SceneBuilder {
                 let _ = self.api_tx.send(ApiMsg::WakeUp);
             }
             SceneBuilderRequest::Transaction {
                 document_id,
                 scene,
                 resource_updates,
                 frame_ops,
                 render,
-                current_epochs,
             } => {
                 let built_scene = scene.map(|request|{
                     build_scene(&self.config, request)
                 });
-                let pipeline_info = if let Some(ref built) = built_scene {
-                    PipelineInfo {
-                        epochs: built.scene.pipeline_epochs.clone(),
-                        removed_pipelines: built.removed_pipelines.clone(),
-                    }
-                } else {
-                    PipelineInfo {
-                        epochs: current_epochs,
-                        removed_pipelines: vec![],
-                    }
-                };
 
                 // TODO: pre-rasterization.
 
-                if let Some(ref hooks) = self.hooks {
-                    hooks.pre_scene_swap();
-                }
-                let (result_tx, result_rx) = channel();
+                // We only need the pipeline info and the result channel if we
+                // have a hook callback *and* if this transaction actually built
+                // a new scene that is going to get swapped in. In other cases
+                // pipeline_info can be None and we can avoid some overhead from
+                // invoking the hooks and blocking on the channel.
+                let (pipeline_info, result_tx, result_rx) = match (&self.hooks, &built_scene) {
+                    (&Some(ref hooks), &Some(ref built)) => {
+                        let info = PipelineInfo {
+                            epochs: built.scene.pipeline_epochs.clone(),
+                            removed_pipelines: built.removed_pipelines.clone(),
+                        };
+                        let (tx, rx) = channel();
+
+                        hooks.pre_scene_swap();
+
+                        (Some(info), Some(tx), Some(rx))
+                    }
+                    _ => (None, None, None),
+                };
+
                 self.tx.send(SceneBuilderResult::Transaction {
                     document_id,
                     built_scene,
                     resource_updates,
                     frame_ops,
                     render,
                     result_tx,
                 }).unwrap();
 
                 let _ = self.api_tx.send(ApiMsg::WakeUp);
 
-                // Block until the swap is done, then invoke the hook
-                let _ = result_rx.recv();
-                if let Some(ref hooks) = self.hooks {
-                    hooks.post_scene_swap(pipeline_info);
+                if let Some(pipeline_info) = pipeline_info {
+                    // Block until the swap is done, then invoke the hook.
+                    let swap_result = result_rx.unwrap().recv();
+                    self.hooks.as_ref().unwrap().post_scene_swap(pipeline_info);
+                    // Once the hook is done, allow the RB thread to resume
+                    match swap_result {
+                        Ok(SceneSwapResult::Complete(resume_tx)) => {
+                            resume_tx.send(()).ok();
+                        },
+                        _ => (),
+                    };
                 }
             }
             SceneBuilderRequest::Stop => {
                 self.tx.send(SceneBuilderResult::Stopped).unwrap();
                 // We don't need to send a WakeUp to api_tx because we only
                 // get the Stop when the RenderBackend loop is exiting.
                 return false;
             }
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -84,17 +84,17 @@ impl LayoutPrimitiveInfo {
             is_backface_visible: true,
             tag: None,
         }
     }
 }
 
 pub type LayoutPrimitiveInfo = PrimitiveInfo<LayoutPixel>;
 
-#[repr(u8)]
+#[repr(u64)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum SpecificDisplayItem {
     Clip(ClipDisplayItem),
     ScrollFrame(ScrollFrameDisplayItem),
     StickyFrame(StickyFrameDisplayItem),
     Rectangle(RectangleDisplayItem),
     ClearRectangle,
     Line(LineDisplayItem),
@@ -467,17 +467,17 @@ pub enum TransformStyle {
     Flat = 0,
     Preserve3D = 1,
 }
 
 // TODO(gw): In the future, we may modify this to apply to all elements
 //           within a stacking context, rather than just the glyphs. If
 //           this change occurs, we'll update the naming of this.
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
-#[repr(C, u8)]
+#[repr(u32)]
 pub enum GlyphRasterSpace {
     // Rasterize glyphs in local-space, applying supplied scale to glyph sizes.
     // Best performance, but lower quality.
     Local(f32),
 
     // Rasterize the glyphs in screen-space, including rotation / skew etc in
     // the rasterized glyph. Best quality, but slower performance. Note that
     // any stacking context with a perspective transform will be rasterized
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -231,45 +231,56 @@ impl<'a> BuiltDisplayListIter<'a> {
         }
 
         // Don't let these bleed into another item
         self.cur_stops = ItemRange::default();
         self.cur_complex_clip = (ItemRange::default(), 0);
         self.cur_clip_chain_items = ItemRange::default();
 
         loop {
-            if self.data.is_empty() {
-                return None;
+            self.next_raw();
+            if let SetGradientStops = self.cur_item.item {
+                // SetGradientStops is a dummy item that most consumers should ignore
+                continue;
             }
+            break;
+        }
 
-            {
-                let reader = bincode::IoReader::new(UnsafeReader::new(&mut self.data));
-                bincode::deserialize_in_place(reader, &mut self.cur_item)
-                    .expect("MEH: malicious process?");
-            }
+        Some(self.as_ref())
+    }
 
-            match self.cur_item.item {
-                SetGradientStops => {
-                    self.cur_stops = skip_slice::<GradientStop>(self.list, &mut self.data).0;
+    /// Gets the next display item, even if it's a dummy. Also doesn't handle peeking
+    /// and may leave irrelevant ranges live (so a Clip may have GradientStops if
+    /// for some reason you ask).
+    pub fn next_raw<'b>(&'b mut self) -> Option<DisplayItemRef<'a, 'b>> {
+        use SpecificDisplayItem::*;
 
-                    // This is a dummy item, skip over it
-                    continue;
-                }
-                ClipChain(_) => {
-                    self.cur_clip_chain_items = skip_slice::<ClipId>(self.list, &mut self.data).0;
-                }
-                Clip(_) | ScrollFrame(_) => {
-                    self.cur_complex_clip = self.skip_slice::<ComplexClipRegion>()
-                }
-                Text(_) => self.cur_glyphs = self.skip_slice::<GlyphInstance>().0,
-                PushStackingContext(_) => self.cur_filters = self.skip_slice::<FilterOp>().0,
-                _ => { /* do nothing */ }
+        if self.data.is_empty() {
+            return None;
+        }
+
+        {
+            let reader = bincode::IoReader::new(UnsafeReader::new(&mut self.data));
+            bincode::deserialize_in_place(reader, &mut self.cur_item)
+                .expect("MEH: malicious process?");
+        }
+
+        match self.cur_item.item {
+            SetGradientStops => {
+                self.cur_stops = skip_slice::<GradientStop>(self.list, &mut self.data).0;
             }
-
-            break;
+            ClipChain(_) => {
+                self.cur_clip_chain_items = skip_slice::<ClipId>(self.list, &mut self.data).0;
+            }
+            Clip(_) | ScrollFrame(_) => {
+                self.cur_complex_clip = self.skip_slice::<ComplexClipRegion>()
+            }
+            Text(_) => self.cur_glyphs = self.skip_slice::<GlyphInstance>().0,
+            PushStackingContext(_) => self.cur_filters = self.skip_slice::<FilterOp>().0,
+            _ => { /* do nothing */ }
         }
 
         Some(self.as_ref())
     }
 
     fn skip_slice<T: for<'de> Deserialize<'de>>(&mut self) -> (ItemRange<T>, usize) {
         skip_slice::<T>(self.list, &mut self.data)
     }
@@ -429,17 +440,17 @@ impl<'a, T: for<'de> Deserialize<'de>> :
 #[cfg(feature = "serialize")]
 impl Serialize for BuiltDisplayList {
     fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
         use display_item::CompletelySpecificDisplayItem::*;
         use display_item::GenericDisplayItem;
 
         let mut seq = serializer.serialize_seq(None)?;
         let mut traversal = self.iter();
-        while let Some(item) = traversal.next() {
+        while let Some(item) = traversal.next_raw() {
             let display_item = item.display_item();
             let serial_di = GenericDisplayItem {
                 item: match display_item.item {
                     SpecificDisplayItem::Clip(v) => Clip(
                         v,
                         item.iter.list.get(item.iter.cur_complex_clip.0).collect()
                     ),
                     SpecificDisplayItem::ClipChain(v) => ClipChain(
@@ -881,17 +892,17 @@ impl DisplayListBuilder {
     }
 
     pub fn print_display_list(&mut self) {
         let mut temp = BuiltDisplayList::default();
         mem::swap(&mut temp.data, &mut self.data);
 
         {
             let mut iter = BuiltDisplayListIter::new(&temp);
-            while let Some(item) = iter.next() {
+            while let Some(item) = iter.next_raw() {
                 println!("{:?}", item.display_item());
             }
         }
 
         self.data = temp.data;
     }
 
     fn push_item(&mut self, item: SpecificDisplayItem, info: &LayoutPrimitiveInfo) {
@@ -1272,17 +1283,17 @@ impl DisplayListBuilder {
         clip_node_id: Option<ClipId>,
         scroll_policy: ScrollPolicy,
         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 {
@@ -1294,16 +1305,18 @@ impl DisplayListBuilder {
                 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 @@
-4b65822a2f7e1fed246a492f9fe193ede2f37d74
+8da531dc2cd77a4dadbfe632b04e76454f51ac9f
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -462,24 +462,28 @@ impl Wrench {
         key
     }
 
     pub fn add_font_instance(&mut self,
         font_key: FontKey,
         size: Au,
         flags: FontInstanceFlags,
         render_mode: Option<FontRenderMode>,
+        bg_color: Option<ColorU>,
     ) -> FontInstanceKey {
         let key = self.api.generate_font_instance_key();
         let mut update = ResourceUpdates::new();
         let mut options: FontInstanceOptions = Default::default();
         options.flags |= flags;
         if let Some(render_mode) = render_mode {
             options.render_mode = render_mode;
         }
+        if let Some(bg_color) = bg_color {
+            options.bg_color = bg_color;
+        }
         update.add_font_instance(key, font_key, size, Some(options), None, Vec::new());
         self.api.update_resources(update);
         key
     }
 
     #[allow(dead_code)]
     pub fn delete_font_instance(&mut self, key: FontInstanceKey) {
         let mut update = ResourceUpdates::new();
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -193,17 +193,17 @@ pub struct YamlFrameReader {
 
     /// A HashMap of offsets which specify what scroll offsets particular
     /// scroll layers should be initialized with.
     scroll_offsets: HashMap<ExternalScrollId, LayoutPoint>,
 
     image_map: HashMap<(PathBuf, Option<i64>), (ImageKey, LayoutSize)>,
 
     fonts: HashMap<FontDescriptor, FontKey>,
-    font_instances: HashMap<(FontKey, Au, FontInstanceFlags), FontInstanceKey>,
+    font_instances: HashMap<(FontKey, Au, FontInstanceFlags, Option<ColorU>), FontInstanceKey>,
     font_render_mode: Option<FontRenderMode>,
     allow_mipmaps: bool,
 
     /// A HashMap that allows specifying a numeric id for clip and clip chains in YAML
     /// and having each of those ids correspond to a unique ClipId.
     clip_id_map: HashMap<u64, ClipId>,
 }
 
@@ -539,29 +539,31 @@ impl YamlFrameReader {
     pub fn set_font_render_mode(&mut self, render_mode: Option<FontRenderMode>) {
         self.font_render_mode = render_mode;
     }
 
     fn get_or_create_font_instance(
         &mut self,
         font_key: FontKey,
         size: Au,
+        bg_color: Option<ColorU>,
         flags: FontInstanceFlags,
         wrench: &mut Wrench,
     ) -> FontInstanceKey {
         let font_render_mode = self.font_render_mode;
 
         *self.font_instances
-            .entry((font_key, size, flags))
+            .entry((font_key, size, flags, bg_color))
             .or_insert_with(|| {
                 wrench.add_font_instance(
                     font_key,
                     size,
                     flags,
                     font_render_mode,
+                    bg_color,
                 )
             })
     }
 
     fn to_image_mask(&mut self, item: &Yaml, wrench: &mut Wrench) -> Option<ImageMask> {
         if item.as_hash().is_none() {
             return None;
         }
@@ -1112,16 +1114,18 @@ impl YamlFrameReader {
         &mut self,
         dl: &mut DisplayListBuilder,
         wrench: &mut Wrench,
         item: &Yaml,
         info: &mut LayoutPrimitiveInfo,
     ) {
         let size = item["size"].as_pt_to_au().unwrap_or(Au::from_f32_px(16.0));
         let color = item["color"].as_colorf().unwrap_or(*BLACK_COLOR);
+        let bg_color = item["bg-color"].as_colorf().map(|c| c.into());
+
         let mut flags = FontInstanceFlags::empty();
         if item["synthetic-italics"].as_bool().unwrap_or(false) {
             flags |= FontInstanceFlags::SYNTHETIC_ITALICS;
         }
         if item["synthetic-bold"].as_bool().unwrap_or(false) {
             flags |= FontInstanceFlags::SYNTHETIC_BOLD;
         }
         if item["embedded-bitmaps"].as_bool().unwrap_or(false) {
@@ -1141,16 +1145,17 @@ impl YamlFrameReader {
             item["blur-radius"].is_badvalue(),
             "text no longer has a blur radius, use PushShadow and PopAllShadows"
         );
 
         let desc = FontDescriptor::from_yaml(item, &self.aux_dir);
         let font_key = self.get_or_create_font(desc, wrench);
         let font_instance_key = self.get_or_create_font_instance(font_key,
                                                                  size,
+                                                                 bg_color,
                                                                  flags,
                                                                  wrench);
 
         assert!(
             !(item["glyphs"].is_badvalue() && item["text"].is_badvalue()),
             "text item had neither text nor glyphs!"
         );