Bug 1459935 - Update webrender to commit 4811a6e7c06f9dd1b4056e5f5e66983842983ba0. r=jrmuizel
authorKartikaya Gupta <kgupta@mozilla.com>
Fri, 11 May 2018 09:14:03 -0400
changeset 417982 972eab977ff34fd59a6a6d829d426c2407d703e0
parent 417981 fc4f2d0a1dfb663404260c36c1b5f85fc1b14f96
child 417983 3ad8053cee91530997fb2eaec8a4913195076a7a
push id103198
push usercbrindusan@mozilla.com
push dateSat, 12 May 2018 09:52:26 +0000
treeherdermozilla-inbound@39d6a4285596 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1459935
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 1459935 - Update webrender to commit 4811a6e7c06f9dd1b4056e5f5e66983842983ba0. r=jrmuizel MozReview-Commit-ID: HUUVwbPauIk
gfx/webrender/Cargo.toml
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/cs_clip_border.glsl
gfx/webrender/res/ps_image.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/freelist.rs
gfx/webrender/src/geometry.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/glyph_rasterizer/mod.rs
gfx/webrender/src/glyph_rasterizer/no_pathfinder.rs
gfx/webrender/src/glyph_rasterizer/pathfinder.rs
gfx/webrender/src/hit_test.rs
gfx/webrender/src/image.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/platform/windows/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/scene_builder.rs
gfx/webrender/src/shade.rs
gfx/webrender/src/util.rs
gfx/webrender/tests/angle_shader_validation.rs
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/Cargo.toml
gfx/wrench/src/main.rs
gfx/wrench/src/rawtest.rs
gfx/wrench/src/yaml_frame_reader.rs
gfx/wrench/src/yaml_frame_writer.rs
gfx/wrench/src/yaml_helper.rs
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -18,24 +18,24 @@ pathfinder = ["pathfinder_font_renderer"
 
 [dependencies]
 app_units = "0.6"
 base64 = { optional = true, version = "0.6" }
 bincode = "1.0"
 bitflags = "1.0"
 byteorder = "1.0"
 cfg-if = "0.1.2"
-euclid = "0.17"
+euclid = "0.17.3"
 fxhash = "0.2.1"
 gleam = "0.5"
 image = { optional = true, version = "0.18" }
 lazy_static = "1"
 log = "0.4"
 num-traits = "0.1.43"
-plane-split = "0.8"
+plane-split = "0.9.1"
 png = { optional = true, version = "0.11" }
 rayon = "1"
 ron = { optional = true, version = "0.1.7" }
 serde = { optional = true, version = "1.0", features = ["serde_derive"] }
 serde_json = { optional = true, version = "1.0" }
 smallvec = "0.6"
 thread_profiler = "0.1.1"
 time = "0.1"
--- a/gfx/webrender/examples/alpha_perf.rs
+++ b/gfx/webrender/examples/alpha_perf.rs
@@ -29,17 +29,16 @@ 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,
-            ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
--- a/gfx/webrender/examples/animation.rs
+++ b/gfx/webrender/examples/animation.rs
@@ -45,17 +45,16 @@ impl Example for App {
         let filters = vec![
             FilterOp::Opacity(PropertyBinding::Binding(self.opacity_key), self.opacity),
         ];
 
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
             None,
-            ScrollPolicy::Scrollable,
             Some(PropertyBinding::Binding(self.property_key)),
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             filters,
             GlyphRasterSpace::Screen,
         );
 
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -187,17 +187,16 @@ 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,
-            ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -246,17 +246,16 @@ 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,
-            api::ScrollPolicy::Scrollable,
             None,
             api::TransformStyle::Flat,
             None,
             api::MixBlendMode::Normal,
             Vec::new(),
             api::GlyphRasterSpace::Screen,
         );
 
--- a/gfx/webrender/examples/document.rs
+++ b/gfx/webrender/examples/document.rs
@@ -108,17 +108,16 @@ 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,
-                ScrollPolicy::Fixed,
                 None,
                 TransformStyle::Flat,
                 None,
                 MixBlendMode::Normal,
                 Vec::new(),
                 GlyphRasterSpace::Screen,
             );
             builder.push_rect(
--- a/gfx/webrender/examples/frame_output.rs
+++ b/gfx/webrender/examples/frame_output.rs
@@ -96,17 +96,16 @@ impl App {
         let mut builder = DisplayListBuilder::new(
             document.pipeline_id,
             document.content_rect.size,
         );
 
         builder.push_stacking_context(
             &info,
             None,
-            ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
@@ -145,17 +144,16 @@ 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,
-            ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
--- a/gfx/webrender/examples/iframe.rs
+++ b/gfx/webrender/examples/iframe.rs
@@ -34,17 +34,16 @@ 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,
-            ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
@@ -61,17 +60,16 @@ impl Example for App {
             true,
         );
         api.send_transaction(document_id, txn);
 
         // And this is for the root pipeline
         builder.push_stacking_context(
             &info,
             None,
-            ScrollPolicy::Scrollable,
             Some(PropertyBinding::Binding(PropertyBindingKey::new(42))),
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
         // red rect under the iframe: if this is visible, things have gone wrong
--- a/gfx/webrender/examples/image_resize.rs
+++ b/gfx/webrender/examples/image_resize.rs
@@ -36,17 +36,16 @@ impl Example for App {
             None,
         );
 
         let bounds = (0, 0).to(512, 512);
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
             None,
-            ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
--- a/gfx/webrender/examples/multiwindow.rs
+++ b/gfx/webrender/examples/multiwindow.rs
@@ -176,17 +176,16 @@ 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,
-            ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -29,33 +29,31 @@ impl Example for App {
         _document_id: DocumentId,
     ) {
         let info = LayoutPrimitiveInfo::new(
             LayoutRect::new(LayoutPoint::zero(), builder.content_size())
         );
         builder.push_stacking_context(
             &info,
             None,
-            ScrollPolicy::Scrollable,
             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,
-                ScrollPolicy::Scrollable,
                 None,
                 TransformStyle::Flat,
                 None,
                 MixBlendMode::Normal,
                 Vec::new(),
                 GlyphRasterSpace::Screen,
             );
             // set the scrolling clip
--- a/gfx/webrender/examples/texture_cache_stress.rs
+++ b/gfx/webrender/examples/texture_cache_stress.rs
@@ -88,17 +88,16 @@ 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,
-            ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -83,17 +83,16 @@ 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,
-            ScrollPolicy::Scrollable,
             None,
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
 
--- a/gfx/webrender/res/cs_clip_border.glsl
+++ b/gfx/webrender/res/cs_clip_border.glsl
@@ -1,28 +1,29 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include shared,clip_shared
 
-in vec4 aDashOrDot0;
-in vec4 aDashOrDot1;
-
 varying vec3 vPos;
 
 flat varying vec2 vClipCenter;
 
 flat varying vec4 vPoint_Tangent0;
 flat varying vec4 vPoint_Tangent1;
 flat varying vec3 vDotParams;
 flat varying vec2 vAlphaMask;
 flat varying vec4 vTaskRect;
 
 #ifdef WR_VERTEX_SHADER
+
+in vec4 aDashOrDot0;
+in vec4 aDashOrDot1;
+
 // Matches BorderCorner enum in border.rs
 #define CORNER_TOP_LEFT     0
 #define CORNER_TOP_RIGHT    1
 #define CORNER_BOTTOM_LEFT  2
 #define CORNER_BOTTOM_RIGHT 3
 
 // Matches BorderCornerClipKind enum in border.rs
 #define CLIP_MODE_DASH      0
deleted file mode 100644
--- a/gfx/webrender/res/ps_image.glsl
+++ /dev/null
@@ -1,106 +0,0 @@
-/* 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
-
-// If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use non-normalized
-// texture coordinates. Otherwise, it uses normalized texture coordinates. Please
-// check GL_TEXTURE_RECTANGLE.
-flat varying vec2 vTextureOffset; // Offset of this image into the texture atlas.
-flat varying vec2 vTextureSize;   // Size of the image in the texture atlas.
-flat varying vec2 vTileSpacing;   // Amount of space between tiled instances of this image.
-flat varying vec4 vStRect;        // Rectangle of valid texture rect.
-flat varying float vLayer;
-
-#ifdef WR_FEATURE_TRANSFORM
-flat varying vec4 vLocalRect;
-#endif
-
-varying vec2 vLocalPos;
-flat varying vec2 vStretchSize;
-
-#ifdef WR_VERTEX_SHADER
-void main(void) {
-    Primitive prim = load_primitive();
-    Image image = fetch_image(prim.specific_prim_address);
-    ImageResource res = fetch_image_resource(prim.user_data0);
-
-#ifdef WR_FEATURE_TRANSFORM
-    VertexInfo vi = write_transform_vertex_primitive(prim);
-    vLocalPos = vi.local_pos;
-    vLocalRect = vec4(prim.local_rect.p0, prim.local_rect.p0 + prim.local_rect.size);
-#else
-    VertexInfo vi = write_vertex(prim.local_rect,
-                                 prim.local_clip_rect,
-                                 prim.z,
-                                 prim.scroll_node,
-                                 prim.task,
-                                 prim.local_rect);
-    vLocalPos = vi.local_pos - prim.local_rect.p0;
-#endif
-
-    write_clip(vi.screen_pos, prim.clip_area);
-
-    // 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_normalization_factor = vec2(1, 1);
-#else
-    vec2 texture_size_normalization_factor = vec2(textureSize(sColor0, 0));
-#endif
-
-    vec2 uv0 = res.uv_rect.p0;
-    vec2 uv1 = res.uv_rect.p1;
-
-    // vUv will contain how many times this image has wrapped around the image size.
-    vec2 st0 = uv0 / texture_size_normalization_factor;
-    vec2 st1 = uv1 / texture_size_normalization_factor;
-
-    vLayer = res.layer;
-    vTextureSize = st1 - st0;
-    vTextureOffset = st0;
-    vTileSpacing = image.stretch_size_and_tile_spacing.zw;
-    vStretchSize = image.stretch_size_and_tile_spacing.xy;
-
-    // We clamp the texture coordinates to the half-pixel offset from the borders
-    // in order to avoid sampling outside of the texture area.
-    vec2 half_texel = vec2(0.5) / texture_size_normalization_factor;
-    vStRect = vec4(min(st0, st1) + half_texel, max(st0, st1) - half_texel);
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-void main(void) {
-#ifdef WR_FEATURE_TRANSFORM
-    float alpha = init_transform_fs(vLocalPos);
-
-    // We clamp the texture coordinate calculation here to the local rectangle boundaries,
-    // which makes the edge of the texture stretch instead of repeat.
-    vec2 upper_bound_mask = step(vLocalRect.zw, vLocalPos);
-    vec2 relative_pos_in_rect = clamp(vLocalPos, vLocalRect.xy, vLocalRect.zw) - vLocalRect.xy;
-#else
-    float alpha = 1.0;
-    vec2 relative_pos_in_rect = vLocalPos;
-    vec2 upper_bound_mask = vec2(0.0);
-#endif
-
-    alpha *= do_clip();
-
-    // We calculate the particular tile this fragment belongs to, taking into
-    // account the spacing in between tiles. We only paint if our fragment does
-    // not fall into that spacing.
-    // If the pixel is at the local rectangle upper bound, we force the current
-    // tile upper bound in order to avoid wrapping.
-    vec2 position_in_tile = mix(
-        mod(relative_pos_in_rect, vStretchSize + vTileSpacing),
-        vStretchSize,
-        upper_bound_mask);
-    vec2 st = vTextureOffset + ((position_in_tile / vStretchSize) * vTextureSize);
-    st = clamp(st, vStRect.xy, vStRect.zw);
-
-    alpha = alpha * float(all(bvec2(step(position_in_tile, vStretchSize))));
-
-    oFragColor = vec4(alpha) * TEX_SAMPLE(sColor0, vec3(st, vLayer));
-}
-#endif
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -35,17 +35,16 @@ use util::{MatrixHelpers, TransformedRec
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff);
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum TransformBatchKind {
     TextRun(GlyphFormat),
-    Image(ImageBufferKind),
     BorderCorner,
     BorderEdge,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BrushBatchKind {
@@ -603,17 +602,36 @@ impl AlphaBatchBuilder {
         let task_relative_bounding_rect = DeviceIntRect::new(
             DeviceIntPoint::new(
                 screen_rect.unclipped.origin.x - content_origin.x,
                 screen_rect.unclipped.origin.y - content_origin.y,
             ),
             screen_rect.unclipped.size,
         );
 
-        let prim_cache_address = gpu_cache.get_address(&prim_metadata.gpu_location);
+        // If the primitive is internally decomposed into multiple sub-primitives we may not
+        // use some of the per-primitive data typically stored in PrimitiveMetadata and get
+        // it from each sub-primitive instead.
+        let is_multiple_primitives = match prim_metadata.prim_kind {
+            PrimitiveKind::Brush => {
+                let brush = &ctx.prim_store.cpu_brushes[prim_metadata.cpu_prim_index.0];
+                match brush.kind {
+                    BrushKind::Image { ref visible_tiles, .. } => !visible_tiles.is_empty(),
+                    _ => false,
+                }
+            }
+            _ => false,
+        };
+
+        let prim_cache_address = if is_multiple_primitives {
+            GpuCacheAddress::invalid()
+        } else {
+            gpu_cache.get_address(&prim_metadata.gpu_location)
+        };
+
         let no_textures = BatchTextures::no_texture();
         let clip_task_address = prim_metadata
             .clip_task_id
             .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
         let base_instance = SimplePrimitiveInstance::new(
             prim_cache_address,
             task_address,
             clip_task_address,
@@ -969,16 +987,42 @@ impl AlphaBatchBuilder {
                                 ctx,
                                 gpu_cache,
                                 render_tasks,
                                 deferred_resolves,
                                 z_generator,
                             );
                         }
                     }
+                    BrushKind::Image { request, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
+                        for tile in visible_tiles {
+                            if let Some((batch_kind, textures, user_data)) = get_image_tile_params(
+                                    ctx.resource_cache,
+                                    gpu_cache,
+                                    deferred_resolves,
+                                    request.with_tile(tile.tile_offset),
+                            ) {
+                                let prim_cache_address = gpu_cache.get_address(&tile.handle);
+                                self.add_image_tile_to_batch(
+                                    batch_kind,
+                                    specified_blend_mode,
+                                    textures,
+                                    clip_chain_rect_index,
+                                    clip_task_address,
+                                    &task_relative_bounding_rect,
+                                    prim_cache_address,
+                                    scroll_id,
+                                    task_address,
+                                    z,
+                                    user_data,
+                                    tile.edge_flags
+                                );
+                            }
+                        }
+                    }
                     _ => {
                         if let Some((batch_kind, textures, user_data)) = brush.get_batch_params(
                                 ctx.resource_cache,
                                 gpu_cache,
                                 deferred_resolves,
                                 ctx.cached_gradients,
                         ) {
                             self.add_brush_to_batch(
@@ -1059,60 +1103,16 @@ impl AlphaBatchBuilder {
                             BorderEdgeKind::Solid |
                             BorderEdgeKind::Clip => {
                                 batch.push(base_instance.build(border_segment as i32, 0, 0));
                             }
                         }
                     }
                 }
             }
-            PrimitiveKind::Image => {
-                let image_cpu = &ctx.prim_store.cpu_images[prim_metadata.cpu_prim_index.0];
-
-                let cache_item = match image_cpu.source {
-                    ImageSource::Default => {
-                        resolve_image(
-                            image_cpu.key.request,
-                            ctx.resource_cache,
-                            gpu_cache,
-                            deferred_resolves,
-                        )
-                    }
-                    ImageSource::Cache { ref handle, .. } => {
-                        let rt_handle = handle
-                            .as_ref()
-                            .expect("bug: render task handle not allocated");
-                        let rt_cache_entry = ctx
-                            .resource_cache
-                            .get_cached_render_task(rt_handle);
-                        ctx.resource_cache.get_texture_cache_item(&rt_cache_entry.handle)
-                    }
-                };
-
-                if cache_item.texture_id == SourceTexture::Invalid {
-                    warn!("Warnings: skip a PrimitiveKind::Image");
-                    debug!("at {:?}.", task_relative_bounding_rect);
-                    return;
-                }
-
-                let batch_kind = TransformBatchKind::Image(get_buffer_kind(cache_item.texture_id));
-                let key = BatchKey::new(
-                    BatchKind::Transformable(transform_kind, batch_kind),
-                    non_segmented_blend_mode,
-                    BatchTextures {
-                        colors: [
-                            cache_item.texture_id,
-                            SourceTexture::Invalid,
-                            SourceTexture::Invalid,
-                        ],
-                    },
-                );
-                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
-                batch.push(base_instance.build(cache_item.uv_rect_handle.as_int(gpu_cache), 0, 0));
-            }
             PrimitiveKind::TextRun => {
                 let text_cpu =
                     &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
 
                 let font = text_cpu.get_font(
                     ctx.device_pixel_scale,
                     Some(scroll_node.transform),
                 );
@@ -1205,16 +1205,55 @@ impl AlphaBatchBuilder {
                             ));
                         }
                     },
                 );
             }
         }
     }
 
+    fn add_image_tile_to_batch(
+        &mut self,
+        batch_kind: BrushBatchKind,
+        blend_mode: BlendMode,
+        textures: BatchTextures,
+        clip_chain_rect_index: ClipChainRectIndex,
+        clip_task_address: RenderTaskAddress,
+        task_relative_bounding_rect: &DeviceIntRect,
+        prim_cache_address: GpuCacheAddress,
+        scroll_id: ClipScrollNodeIndex,
+        task_address: RenderTaskAddress,
+        z: ZBufferId,
+        user_data: [i32; 3],
+        edge_flags: EdgeAaSegmentMask,
+    ) {
+        let base_instance = BrushInstance {
+            picture_address: task_address,
+            prim_address: prim_cache_address,
+            clip_chain_rect_index,
+            scroll_id,
+            clip_task_address,
+            z,
+            segment_index: 0,
+            edge_flags,
+            brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
+            user_data,
+        };
+
+        self.batch_list.add_bounding_rect(task_relative_bounding_rect);
+
+        let batch_key = BatchKey {
+            blend_mode,
+            kind: BatchKind::Brush(batch_kind),
+            textures,
+        };
+        let batch = self.batch_list.get_suitable_batch(batch_key, task_relative_bounding_rect);
+        batch.push(PrimitiveInstance::from(base_instance));
+    }
+
     fn add_brush_to_batch(
         &mut self,
         brush: &BrushPrimitive,
         prim_metadata: &PrimitiveMetadata,
         batch_kind: BrushBatchKind,
         alpha_blend_mode: BlendMode,
         non_segmented_blend_mode: BlendMode,
         textures: BatchTextures,
@@ -1304,16 +1343,47 @@ impl AlphaBatchBuilder {
                 };
                 let batch = self.batch_list.get_suitable_batch(batch_key, task_relative_bounding_rect);
                 batch.push(PrimitiveInstance::from(base_instance));
             }
         }
     }
 }
 
+fn get_image_tile_params(
+    resource_cache: &ResourceCache,
+    gpu_cache: &mut GpuCache,
+    deferred_resolves: &mut Vec<DeferredResolve>,
+    request: ImageRequest,
+) -> Option<(BrushBatchKind, BatchTextures, [i32; 3])> {
+
+    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,
+            ],
+        ))
+    }
+}
+
 impl BrushPrimitive {
     pub fn get_picture_index(&self) -> PictureIndex {
         match self.kind {
             BrushKind::Picture { pic_index, .. } => {
                 pic_index
             }
             _ => {
                 panic!("bug: not a picture brush!!");
@@ -1523,23 +1593,16 @@ impl AlphaBatchHelpers for PrimitiveStor
                     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 {
-                    AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
-                    AlphaType::Alpha => BlendMode::Alpha,
-                }
-            }
         }
     }
 }
 
 impl PictureSurface {
     // Retrieve the uv rect handle, and texture for a picture surface.
     fn resolve(
         &self,
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -267,17 +267,17 @@ impl ClipSource {
 #[derive(Debug)]
 pub struct ClipSources {
     pub clips: Vec<(ClipSource, GpuCacheHandle)>,
     pub local_inner_rect: LayoutRect,
     pub local_outer_rect: Option<LayoutRect>
 }
 
 impl ClipSources {
-    pub fn new(clips: Vec<ClipSource>) -> ClipSources {
+    pub fn new(clips: Vec<ClipSource>) -> Self {
         let (local_inner_rect, local_outer_rect) = Self::calculate_inner_and_outer_rects(&clips);
 
         let clips = clips
             .into_iter()
             .map(|clip| (clip, GpuCacheHandle::new()))
             .collect();
 
         ClipSources {
@@ -461,33 +461,36 @@ impl ClipSources {
             }
         }
     }
 
     pub fn get_screen_bounds(
         &self,
         transform: &LayoutToWorldFastTransform,
         device_pixel_scale: DevicePixelScale,
+        screen_rect: Option<&DeviceIntRect>,
     ) -> (DeviceIntRect, Option<DeviceIntRect>) {
         // If this translation isn't axis aligned or has a perspective component, don't try to
         // calculate the inner rectangle. The rectangle that we produce would include potentially
         // clipped screen area.
         // TODO(mrobinson): We should eventually try to calculate an inner region or some inner
         // rectangle so that we can do screen inner rectangle optimizations for these kind of
         // cilps.
         let can_calculate_inner_rect =
             transform.preserves_2d_axis_alignment() && !transform.has_perspective_component();
         let screen_inner_rect = if can_calculate_inner_rect {
-            calculate_screen_bounding_rect(transform, &self.local_inner_rect, device_pixel_scale)
+            calculate_screen_bounding_rect(transform, &self.local_inner_rect, device_pixel_scale, screen_rect)
+                .unwrap_or(DeviceIntRect::zero())
         } else {
             DeviceIntRect::zero()
         };
 
         let screen_outer_rect = self.local_outer_rect.map(|outer_rect|
-            calculate_screen_bounding_rect(transform, &outer_rect, device_pixel_scale)
+            calculate_screen_bounding_rect(transform, &outer_rect, device_pixel_scale, screen_rect)
+                .unwrap_or(DeviceIntRect::zero())
         );
 
         (screen_inner_rect, screen_outer_rect)
     }
 }
 
 /// Represents a local rect and a device space
 /// rectangles that are either outside or inside bounds.
@@ -560,27 +563,27 @@ pub struct ClipChain {
     pub parent_index: Option<ClipChainIndex>,
     pub combined_outer_screen_rect: DeviceIntRect,
     pub combined_inner_screen_rect: DeviceIntRect,
     pub nodes: ClipChainNodeRef,
     pub has_non_root_coord_system: bool,
 }
 
 impl ClipChain {
-    pub fn empty(screen_rect: &DeviceIntRect) -> ClipChain {
+    pub fn empty(screen_rect: &DeviceIntRect) -> Self {
         ClipChain {
             parent_index: None,
             combined_inner_screen_rect: *screen_rect,
             combined_outer_screen_rect: *screen_rect,
             nodes: None,
             has_non_root_coord_system: false,
         }
     }
 
-    pub fn new_with_added_node(&self, new_node: &ClipChainNode) -> ClipChain {
+    pub fn new_with_added_node(&self, new_node: &ClipChainNode) -> Self {
         // If the new node's inner rectangle completely surrounds our outer rectangle,
         // we can discard the new node entirely since it isn't going to affect anything.
         if new_node.screen_inner_rect.contains_rect(&self.combined_outer_screen_rect) {
             return self.clone();
         }
 
         let mut new_chain = self.clone();
         new_chain.add_node(new_node.clone());
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,45 +1,46 @@
 /* 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::{DevicePixelScale, ExternalScrollId, LayoutPixel, LayoutPoint, LayoutRect, LayoutSize};
 use api::{LayoutVector2D, LayoutTransform, PipelineId, PropertyBinding};
 use api::{ScrollClamping, ScrollLocation, ScrollSensitivity, StickyOffsetBounds};
-use api::WorldPoint;
 use clip::{ClipChain, ClipChainNode, ClipSourcesHandle, ClipStore, ClipWorkItem};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
 use clip_scroll_tree::TransformUpdateState;
 use euclid::SideOffsets2D;
-use geometry::ray_intersects_rect;
 use gpu_cache::GpuCache;
 use gpu_types::{ClipScrollNodeIndex as GPUClipScrollNodeIndex, ClipScrollNodeData};
 use resource_cache::ResourceCache;
 use scene::SceneProperties;
 use util::{LayoutToWorldFastTransform, LayoutFastTransform};
 use util::{TransformedRectKind};
 
 #[derive(Debug)]
 pub struct StickyFrameInfo {
+    pub frame_rect: LayoutRect,
     pub margins: SideOffsets2D<Option<f32>>,
     pub vertical_offset_bounds: StickyOffsetBounds,
     pub horizontal_offset_bounds: StickyOffsetBounds,
     pub previously_applied_offset: LayoutVector2D,
     pub current_offset: LayoutVector2D,
 }
 
 impl StickyFrameInfo {
     pub fn new(
+        frame_rect: LayoutRect,
         margins: SideOffsets2D<Option<f32>>,
         vertical_offset_bounds: StickyOffsetBounds,
         horizontal_offset_bounds: StickyOffsetBounds,
         previously_applied_offset: LayoutVector2D
     ) -> StickyFrameInfo {
         StickyFrameInfo {
+            frame_rect,
             margins,
             vertical_offset_bounds,
             horizontal_offset_bounds,
             previously_applied_offset,
             current_offset: LayoutVector2D::zero(),
         }
     }
 }
@@ -83,24 +84,20 @@ impl NodeType {
             _ => false,
         }
     }
 }
 
 /// Contains information common among all types of ClipScrollTree nodes.
 #[derive(Debug)]
 pub struct ClipScrollNode {
-    /// Viewing rectangle in the coordinate system of the parent reference frame.
-    pub local_viewport_rect: LayoutRect,
-
     /// The transformation for this viewport in world coordinates is the transformation for
     /// our parent reference frame, plus any accumulated scrolling offsets from nodes
     /// between our reference frame and this node. For reference frames, we also include
-    /// whatever local transformation this reference frame provides. This can be combined
-    /// with the local_viewport_rect to get its position in world space.
+    /// whatever local transformation this reference frame provides.
     pub world_viewport_transform: LayoutToWorldFastTransform,
 
     /// World transform for content transformed by this node.
     pub world_content_transform: LayoutToWorldFastTransform,
 
     /// The current transform kind of world_content_transform.
     pub transform_kind: TransformedRectKind,
 
@@ -133,88 +130,85 @@ pub struct ClipScrollNode {
     /// pass to shaders, to allow them to fetch a given clip-scroll node.
     pub node_data_index: GPUClipScrollNodeIndex,
 }
 
 impl ClipScrollNode {
     pub fn new(
         pipeline_id: PipelineId,
         parent_index: Option<ClipScrollNodeIndex>,
-        rect: &LayoutRect,
         node_type: NodeType
     ) -> Self {
         ClipScrollNode {
-            local_viewport_rect: *rect,
             world_viewport_transform: LayoutToWorldFastTransform::identity(),
             world_content_transform: LayoutToWorldFastTransform::identity(),
             transform_kind: TransformedRectKind::AxisAligned,
             parent: parent_index,
             children: Vec::new(),
             pipeline_id,
             node_type,
             invertible: true,
             coordinate_system_id: CoordinateSystemId(0),
             coordinate_system_relative_transform: LayoutFastTransform::identity(),
             node_data_index: GPUClipScrollNodeIndex(0),
         }
     }
 
     pub fn empty() -> ClipScrollNode {
-        ClipScrollNode::new(PipelineId::dummy(), None, &LayoutRect::zero(), NodeType::Empty)
+        Self::new(PipelineId::dummy(), None, NodeType::Empty)
     }
 
     pub fn new_scroll_frame(
         pipeline_id: PipelineId,
         parent_index: ClipScrollNodeIndex,
         external_id: Option<ExternalScrollId>,
         frame_rect: &LayoutRect,
         content_size: &LayoutSize,
         scroll_sensitivity: ScrollSensitivity,
     ) -> Self {
         let node_type = NodeType::ScrollFrame(ScrollFrameInfo::new(
+            *frame_rect,
             scroll_sensitivity,
             LayoutSize::new(
                 (content_size.width - frame_rect.size.width).max(0.0),
                 (content_size.height - frame_rect.size.height).max(0.0)
             ),
             external_id,
         ));
 
-        Self::new(pipeline_id, Some(parent_index), frame_rect, node_type)
+        Self::new(pipeline_id, Some(parent_index), node_type)
     }
 
     pub fn new_reference_frame(
         parent_index: Option<ClipScrollNodeIndex>,
-        frame_rect: &LayoutRect,
         source_transform: Option<PropertyBinding<LayoutTransform>>,
         source_perspective: Option<LayoutTransform>,
         origin_in_parent_reference_frame: LayoutVector2D,
         pipeline_id: PipelineId,
     ) -> Self {
         let identity = LayoutTransform::identity();
         let source_perspective = source_perspective.map_or_else(
             LayoutFastTransform::identity, |perspective| perspective.into());
         let info = ReferenceFrameInfo {
             resolved_transform: LayoutFastTransform::identity(),
             source_transform: source_transform.unwrap_or(PropertyBinding::Value(identity)),
             source_perspective,
             origin_in_parent_reference_frame,
             invertible: true,
         };
-        Self::new(pipeline_id, parent_index, frame_rect, NodeType::ReferenceFrame(info))
+        Self::new(pipeline_id, parent_index, NodeType::ReferenceFrame(info))
     }
 
     pub fn new_sticky_frame(
         parent_index: ClipScrollNodeIndex,
-        frame_rect: LayoutRect,
         sticky_frame_info: StickyFrameInfo,
         pipeline_id: PipelineId,
     ) -> Self {
         let node_type = NodeType::StickyFrame(sticky_frame_info);
-        Self::new(pipeline_id, Some(parent_index), &frame_rect, node_type)
+        Self::new(pipeline_id, Some(parent_index), node_type)
     }
 
 
     pub fn add_child(&mut self, child: ClipScrollNodeIndex) {
         self.children.push(child);
     }
 
     pub fn apply_old_scrolling_state(&mut self, old_scrolling_state: &ScrollFrameInfo) {
@@ -349,43 +343,47 @@ impl ClipScrollNode {
 
     pub fn update_clip_work_item(
         &mut self,
         state: &mut TransformUpdateState,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
-        clip_chains: &mut Vec<ClipChain>,
+        clip_chains: &mut [ClipChain],
     ) {
         let (clip_sources_handle, clip_chain_index, stored_clip_chain_node) = match self.node_type {
             NodeType::Clip { ref handle, clip_chain_index, ref mut clip_chain_node } =>
                 (handle, clip_chain_index, clip_chain_node),
             _ => {
                 self.invertible = true;
                 return;
             }
         };
 
         let clip_sources = clip_store.get_mut(clip_sources_handle);
         clip_sources.update(
             gpu_cache,
             resource_cache,
             device_pixel_scale,
         );
-        let (screen_inner_rect, screen_outer_rect) =
-            clip_sources.get_screen_bounds(&self.world_viewport_transform, device_pixel_scale);
+
+        let (screen_inner_rect, screen_outer_rect) = clip_sources.get_screen_bounds(
+            &self.world_viewport_transform,
+            device_pixel_scale,
+            None,
+        );
 
         // All clipping ClipScrollNodes should have outer rectangles, because they never
         // use the BorderCorner clip type and they always have at last one non-ClipOut
         // Rectangle ClipSource.
-        let screen_outer_rect = screen_outer_rect.expect("Clipping node didn't have outer rect.");
-        let local_outer_rect = clip_sources.local_outer_rect.expect(
-            "Clipping node didn't have outer rect."
-        );
+        let screen_outer_rect = screen_outer_rect
+            .expect("Clipping node didn't have outer rect.");
+        let local_outer_rect = clip_sources.local_outer_rect
+            .expect("Clipping node didn't have outer rect.");
 
         let new_node = ClipChainNode {
             work_item: ClipWorkItem {
                 scroll_node_data_index: self.node_data_index,
                 clip_sources: clip_sources_handle.weak(),
                 coordinate_system_id: state.current_coordinate_system_id,
             },
             local_clip_rect:
@@ -472,18 +470,17 @@ impl ClipScrollNode {
         info.resolved_transform =
             LayoutFastTransform::with_vector(info.origin_in_parent_reference_frame)
             .pre_mul(&source_transform.into())
             .pre_mul(&info.source_perspective);
 
         // The transformation for this viewport in world coordinates is the transformation for
         // our parent reference frame, plus any accumulated scrolling offsets from nodes
         // between our reference frame and this node. Finally, we also include
-        // whatever local transformation this reference frame provides. This can be combined
-        // with the local_viewport_rect to get its position in world space.
+        // whatever local transformation this reference frame provides.
         let relative_transform = info.resolved_transform
             .post_translate(state.parent_accumulated_scroll_offset)
             .to_transform()
             .with_destination::<LayoutPixel>();
         self.world_viewport_transform =
             state.parent_reference_frame_transform.pre_mul(&relative_transform.into());
         self.world_content_transform = self.world_viewport_transform;
 
@@ -521,17 +518,17 @@ impl ClipScrollNode {
             return LayoutVector2D::zero();
         }
 
         // The viewport and margins of the item establishes the maximum amount that it can
         // be offset in order to keep it on screen. Since we care about the relationship
         // between the scrolled content and unscrolled viewport we adjust the viewport's
         // position by the scroll offset in order to work with their relative positions on the
         // page.
-        let sticky_rect = self.local_viewport_rect.translate(viewport_scroll_offset);
+        let sticky_rect = info.frame_rect.translate(viewport_scroll_offset);
 
         let mut sticky_offset = LayoutVector2D::zero();
         if let Some(margin) = info.margins.top {
             let top_viewport_edge = viewport_rect.min_y() + margin;
             if sticky_rect.min_y() < top_viewport_edge {
                 // If the sticky rect is positioned above the top edge of the viewport (plus margin)
                 // we move it down so that it is fully inside the viewport.
                 sticky_offset.y = top_viewport_edge - sticky_rect.min_y();
@@ -636,17 +633,17 @@ impl ClipScrollNode {
                     state.nearest_scrolling_ancestor_viewport
                        .translate(&translation);
             }
             NodeType::Clip{ .. } => { }
             NodeType::ScrollFrame(ref scrolling) => {
                 state.parent_accumulated_scroll_offset =
                     scrolling.offset + state.parent_accumulated_scroll_offset;
                 state.nearest_scrolling_ancestor_offset = scrolling.offset;
-                state.nearest_scrolling_ancestor_viewport = self.local_viewport_rect;
+                state.nearest_scrolling_ancestor_viewport = scrolling.viewport_rect;
             }
             NodeType::StickyFrame(ref info) => {
                 // We don't translate the combined rect by the sticky offset, because sticky
                 // offsets actually adjust the node position itself, whereas scroll offsets
                 // only apply to contents inside the node.
                 state.parent_accumulated_scroll_offset =
                     info.current_offset + state.parent_accumulated_scroll_offset;
             }
@@ -707,39 +704,16 @@ impl ClipScrollNode {
                 .min(0.0)
                 .max(-scrollable_height)
                 .round();
         }
 
         scrolling.offset != original_layer_scroll_offset
     }
 
-    pub fn ray_intersects_node(&self, cursor: &WorldPoint) -> bool {
-        let inv = match self.world_viewport_transform.inverse() {
-            Some(inv) => inv,
-            None => return false,
-        };
-
-        let z0 = -10000.0;
-        let z1 = 10000.0;
-
-        let p0 = inv.transform_point3d(&cursor.extend(z0));
-        let p1 = inv.transform_point3d(&cursor.extend(z1));
-
-        if self.scrollable_size() == LayoutSize::zero() {
-            return false;
-        }
-
-        ray_intersects_rect(
-            p0.to_untyped(),
-            p1.to_untyped(),
-            self.local_viewport_rect.to_untyped(),
-        )
-    }
-
     pub fn scroll_offset(&self) -> LayoutVector2D {
         match self.node_type {
             NodeType::ScrollFrame(ref scrolling) => scrolling.offset,
             _ => LayoutVector2D::zero(),
         }
     }
 
     pub fn matches_external_id(&self, external_id: ExternalScrollId) -> bool {
@@ -747,37 +721,43 @@ impl ClipScrollNode {
             NodeType::ScrollFrame(info) if info.external_id == Some(external_id) => true,
             _ => false,
         }
     }
 }
 
 #[derive(Copy, Clone, Debug)]
 pub struct ScrollFrameInfo {
+    /// The rectangle of the viewport of this scroll frame. This is important for
+    /// positioning of items inside child StickyFrames.
+    pub viewport_rect: LayoutRect,
+
     pub offset: LayoutVector2D,
     pub scroll_sensitivity: ScrollSensitivity,
 
     /// Amount that this ScrollFrame can scroll in both directions.
     pub scrollable_size: LayoutSize,
 
     /// An external id to identify this scroll frame to API clients. This
     /// allows setting scroll positions via the API without relying on ClipsIds
     /// which may change between frames.
     pub external_id: Option<ExternalScrollId>,
 
 }
 
 /// Manages scrolling offset.
 impl ScrollFrameInfo {
     pub fn new(
+        viewport_rect: LayoutRect,
         scroll_sensitivity: ScrollSensitivity,
         scrollable_size: LayoutSize,
         external_id: Option<ExternalScrollId>,
     ) -> ScrollFrameInfo {
         ScrollFrameInfo {
+            viewport_rect,
             offset: LayoutVector2D::zero(),
             scroll_sensitivity,
             scrollable_size,
             external_id,
         }
     }
 
     pub fn sensitive_to_input_events(&self) -> bool {
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -128,46 +128,16 @@ impl ClipScrollTree {
     /// The root scroll node which is the first child of the root reference frame.
     /// Initially this ID is not valid, which is indicated by ```nodes``` being empty.
     pub fn topmost_scroll_node_index(&self) -> ClipScrollNodeIndex {
         // TODO(mrobinson): We should eventually make this impossible to misuse.
         debug_assert!(self.nodes.len() >= 1);
         TOPMOST_SCROLL_NODE_INDEX
     }
 
-    fn find_scrolling_node_at_point_in_node(
-        &self,
-        cursor: &WorldPoint,
-        index: ClipScrollNodeIndex,
-    ) -> Option<ClipScrollNodeIndex> {
-        let node = &self.nodes[index.0];
-        for child_index in node.children.iter().rev() {
-            let found_index = self.find_scrolling_node_at_point_in_node(cursor, *child_index);
-            if found_index.is_some() {
-                return found_index;
-            }
-        }
-
-        match node.node_type {
-            NodeType::ScrollFrame(state) if state.sensitive_to_input_events() => {}
-            _ => return None,
-        }
-
-        if node.ray_intersects_node(cursor) {
-            Some(index)
-        } else {
-            None
-        }
-    }
-
-    pub fn find_scrolling_node_at_point(&self, cursor: &WorldPoint) -> ClipScrollNodeIndex {
-        self.find_scrolling_node_at_point_in_node(cursor, self.root_reference_frame_index())
-            .unwrap_or(self.topmost_scroll_node_index())
-    }
-
     pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
         let mut result = vec![];
         for node in &self.nodes {
             if let NodeType::ScrollFrame(info) = node.node_type {
                 if let Some(id) = info.external_id {
                     result.push(ScrollNodeState { id, scroll_offset: info.offset })
                 }
             }
@@ -209,26 +179,41 @@ impl ClipScrollTree {
                 return node.set_scroll_origin(&origin, clamp);
             }
         }
 
         self.pending_scroll_offsets.insert(id, (origin, clamp));
         false
     }
 
-    pub fn scroll(
+    fn find_nearest_scrolling_ancestor(
+        &self,
+        index: Option<ClipScrollNodeIndex>
+    ) -> ClipScrollNodeIndex {
+        let index = match index {
+            Some(index) => index,
+            None => return self.topmost_scroll_node_index(),
+        };
+
+        let node = &self.nodes[index.0];
+        match node.node_type {
+            NodeType::ScrollFrame(state) if state.sensitive_to_input_events() => index,
+            _ => self.find_nearest_scrolling_ancestor(node.parent)
+        }
+    }
+
+    pub fn scroll_nearest_scrolling_ancestor(
         &mut self,
         scroll_location: ScrollLocation,
-        cursor: WorldPoint,
+        node_index: Option<ClipScrollNodeIndex>,
     ) -> bool {
         if self.nodes.is_empty() {
             return false;
         }
-
-        let node_index = self.find_scrolling_node_at_point(&cursor);
+        let node_index = self.find_nearest_scrolling_ancestor(node_index);
         self.nodes[node_index.0].scroll(scroll_location)
     }
 
     pub fn update_tree(
         &mut self,
         screen_rect: &DeviceIntRect,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
@@ -375,40 +360,33 @@ impl ClipScrollTree {
         }
     }
 
     pub fn add_clip_node(
         &mut self,
         index: ClipScrollNodeIndex,
         parent_index: ClipScrollNodeIndex,
         handle: ClipSourcesHandle,
-        clip_rect: LayoutRect,
         pipeline_id: PipelineId,
     )  -> ClipChainIndex {
         let clip_chain_index = self.allocate_clip_chain();
         let node_type = NodeType::Clip { handle, clip_chain_index, clip_chain_node: None };
-        let node = ClipScrollNode::new(pipeline_id, Some(parent_index), &clip_rect, node_type);
+        let node = ClipScrollNode::new(pipeline_id, Some(parent_index), node_type);
         self.add_node(node, index);
         clip_chain_index
     }
 
     pub fn add_sticky_frame(
         &mut self,
         index: ClipScrollNodeIndex,
         parent_index: ClipScrollNodeIndex,
-        frame_rect: LayoutRect,
         sticky_frame_info: StickyFrameInfo,
         pipeline_id: PipelineId,
     ) {
-        let node = ClipScrollNode::new_sticky_frame(
-            parent_index,
-            frame_rect,
-            sticky_frame_info,
-            pipeline_id,
-        );
+        let node = ClipScrollNode::new_sticky_frame(parent_index, sticky_frame_info, pipeline_id);
         self.add_node(node, index);
     }
 
     pub fn add_clip_chain_descriptor(
         &mut self,
         parent: Option<ClipChainIndex>,
         clips: Vec<ClipScrollNodeIndex>
     ) -> ClipChainIndex {
@@ -471,28 +449,28 @@ impl ClipScrollTree {
             }
             NodeType::ReferenceFrame(ref info) => {
                 pt.new_level(format!("ReferenceFrame {:?}", info.resolved_transform));
                 pt.add_item(format!("index: {:?}", index));
             }
             NodeType::ScrollFrame(scrolling_info) => {
                 pt.new_level(format!("ScrollFrame"));
                 pt.add_item(format!("index: {:?}", index));
+                pt.add_item(format!("viewport: {:?}", scrolling_info.viewport_rect));
                 pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
-                pt.add_item(format!("scroll.offset: {:?}", scrolling_info.offset));
+                pt.add_item(format!("scroll offset: {:?}", scrolling_info.offset));
             }
             NodeType::StickyFrame(ref sticky_frame_info) => {
                 pt.new_level(format!("StickyFrame"));
                 pt.add_item(format!("index: {:?}", index));
                 pt.add_item(format!("sticky info: {:?}", sticky_frame_info));
             }
             NodeType::Empty => unreachable!("Empty node remaining in ClipScrollTree."),
         }
 
-        pt.add_item(format!("local_viewport_rect: {:?}", node.local_viewport_rect));
         pt.add_item(format!("world_viewport_transform: {:?}", node.world_viewport_transform));
         pt.add_item(format!("world_content_transform: {:?}", node.world_content_transform));
         pt.add_item(format!("coordinate_system_id: {:?}", node.coordinate_system_id));
 
         for child_index in &node.children {
             self.print_node(*child_index, pt, clip_store);
         }
 
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -3,39 +3,39 @@
  * 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, LayoutSize, LayoutTransform, LayoutVector2D};
+use api::{LayoutPrimitiveInfo, LayoutRect, LayoutVector2D, LayoutSize, LayoutTransform};
 use api::{LineOrientation, LineStyle, LocalClip, NinePatchBorderSource, PipelineId};
-use api::{PropertyBinding, RepeatMode, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity};
-use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
-use api::{TileOffset, TransformStyle, YuvColorSpace, YuvData};
+use api::{PropertyBinding, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity, Shadow};
+use api::{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;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
-use image::{decompose_image, TiledImageInfo, simplify_repeated_primitive};
+use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::PictureCompositeMode;
 use prim_store::{BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient};
-use prim_store::{CachedGradientIndex, EdgeAaSegmentMask, ImageCacheKey, ImagePrimitiveCpu, ImageSource};
+use prim_store::{CachedGradientIndex, EdgeAaSegmentMask, 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 resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::{BuiltScene, SceneRequest};
 use std::{f32, mem, usize};
 use tiling::{CompositeOps, ScrollbarPrimitive};
 use util::{MaxRect, RectHelpers, recycle_vec};
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
     r: 0.3,
@@ -145,19 +145,16 @@ pub struct DisplayListFlattener<'a> {
     scene: &'a Scene,
 
     /// The ClipScrollTree that we are currently building during flattening.
     clip_scroll_tree: &'a mut ClipScrollTree,
 
     /// The map of all font instances.
     font_instances: FontInstanceMap,
 
-    /// The map of tiled images.
-    tiled_image_map: TiledImageMap,
-
     /// 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
@@ -202,17 +199,16 @@ pub struct DisplayListFlattener<'a> {
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn create_frame_builder(
         old_builder: FrameBuilder,
         scene: &Scene,
         clip_scroll_tree: &mut ClipScrollTree,
         font_instances: FontInstanceMap,
-        tiled_image_map: TiledImageMap,
         view: &DocumentView,
         output_pipelines: &FastHashSet<PipelineId>,
         frame_builder_config: &FrameBuilderConfig,
         new_scene: &mut Scene,
     ) -> FrameBuilder {
         // We checked that the root pipeline is available on the render backend.
         let root_pipeline_id = scene.root_pipeline_id.unwrap();
         let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap();
@@ -222,17 +218,16 @@ impl<'a> DisplayListFlattener<'a> {
         let background_color = root_pipeline
             .background_color
             .and_then(|color| if color.a > 0.0 { Some(color) } else { None });
 
         let mut flattener = DisplayListFlattener {
             scene,
             clip_scroll_tree,
             font_instances,
-            tiled_image_map,
             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),
@@ -408,27 +403,27 @@ impl<'a> DisplayListFlattener<'a> {
         item: &DisplayItemRef,
         info: &StickyFrameDisplayItem,
         clip_and_scroll: &ScrollNodeAndClipChain,
         parent_id: &ClipId,
         reference_frame_relative_offset: &LayoutVector2D,
     ) {
         let frame_rect = item.rect().translate(reference_frame_relative_offset);
         let sticky_frame_info = StickyFrameInfo::new(
+            frame_rect,
             info.margins,
             info.vertical_offset_bounds,
             info.horizontal_offset_bounds,
             info.previously_applied_offset,
         );
 
         let index = self.id_to_index_mapper.get_node_index(info.id);
         self.clip_scroll_tree.add_sticky_frame(
             index,
             clip_and_scroll.scroll_node_id, /* parent id */
-            frame_rect,
             sticky_frame_info,
             info.id.pipeline_id(),
         );
         self.id_to_index_mapper.map_to_parent_clip_chain(info.id, parent_id);
     }
 
     fn flatten_scroll_frame(
         &mut self,
@@ -469,17 +464,17 @@ impl<'a> DisplayListFlattener<'a> {
 
     fn flatten_stacking_context(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
         item: &DisplayItemRef,
         stacking_context: &StackingContext,
         unreplaced_scroll_id: ClipId,
-        mut scroll_node_id: ClipId,
+        scroll_node_id: ClipId,
         mut 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;
         }
@@ -493,39 +488,32 @@ impl<'a> DisplayListFlattener<'a> {
                 .expect("No display list?!")
                 .display_list;
             CompositeOps::new(
                 stacking_context.filter_ops_for_compositing(display_list, item.filters()),
                 stacking_context.mix_blend_mode_for_compositing(),
             )
         };
 
-        if stacking_context.scroll_policy == ScrollPolicy::Fixed {
-            scroll_node_id = self.current_reference_frame_id();
-            self.replacements.push((unreplaced_scroll_id, scroll_node_id));
-        }
-
         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()
             );
 
-            let reference_frame_bounds = LayoutRect::new(LayoutPoint::zero(), bounds.size);
             self.push_reference_frame(
                 reference_frame_id,
                 Some(scroll_node_id),
                 pipeline_id,
-                &reference_frame_bounds,
                 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();
         }
 
@@ -544,20 +532,16 @@ impl<'a> DisplayListFlattener<'a> {
         );
 
         self.flatten_items(
             traversal,
             pipeline_id,
             reference_frame_relative_offset,
         );
 
-        if stacking_context.scroll_policy == ScrollPolicy::Fixed {
-            self.replacements.pop();
-        }
-
         if stacking_context.reference_frame_id.is_some() {
             self.replacements.pop();
             self.pop_reference_frame();
         }
 
         self.pop_stacking_context();
     }
 
@@ -588,28 +572,27 @@ impl<'a> DisplayListFlattener<'a> {
                 reference_frame_relative_offset
             ),
         );
 
         let epoch = self.scene.pipeline_epochs[&iframe_pipeline_id];
         self.pipeline_epochs.push((iframe_pipeline_id, epoch));
 
         let bounds = item.rect();
-        let iframe_rect = LayoutRect::new(LayoutPoint::zero(), bounds.size);
         let origin = *reference_frame_relative_offset + bounds.origin.to_vector();
         self.push_reference_frame(
             ClipId::root_reference_frame(iframe_pipeline_id),
             Some(info.clip_id),
             iframe_pipeline_id,
-            &iframe_rect,
             None,
             None,
             origin,
         );
 
+        let iframe_rect = LayoutRect::new(LayoutPoint::zero(), bounds.size);
         self.add_scroll_frame(
             ClipId::root_scroll_node(iframe_pipeline_id),
             ClipId::root_reference_frame(iframe_pipeline_id),
             Some(ExternalScrollId(0, iframe_pipeline_id)),
             iframe_pipeline_id,
             &iframe_rect,
             &pipeline.content_size,
             ScrollSensitivity::ScriptAndInputEvents,
@@ -630,59 +613,26 @@ impl<'a> DisplayListFlattener<'a> {
         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 = 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) => {
-                match self.tiled_image_map.get(&info.image_key).cloned() {
-                    Some(tiling) => {
-                        // The image resource is tiled. We have to generate an image primitive
-                        // for each tile.
-                        decompose_image(
-                            &TiledImageInfo {
-                                rect: prim_info.rect,
-                                tile_spacing: info.tile_spacing,
-                                stretch_size: info.stretch_size,
-                                device_image_size: tiling.image_size,
-                                device_tile_size: tiling.tile_size as u32,
-                            },
-                            &mut|tile| {
-                                let mut prim_info = prim_info.clone();
-                                prim_info.rect = tile.rect;
-                                self.add_image(
-                                    clip_and_scroll,
-                                    &prim_info,
-                                    tile.stretch_size,
-                                    info.tile_spacing,
-                                    None,
-                                    info.image_key,
-                                    info.image_rendering,
-                                    info.alpha_type,
-                                    Some(tile.tile_offset),
-                                );
-                            }
-                        );
-                    }
-                    None => {
-                        self.add_image(
-                            clip_and_scroll,
-                            &prim_info,
-                            info.stretch_size,
-                            info.tile_spacing,
-                            None,
-                            info.image_key,
-                            info.image_rendering,
-                            info.alpha_type,
-                            None,
-                        );
-                    }
-                }
+                self.add_image(
+                    clip_and_scroll,
+                    &prim_info,
+                    info.stretch_size,
+                    info.tile_spacing,
+                    None,
+                    info.image_key,
+                    info.image_rendering,
+                    info.alpha_type,
+                );
             }
             SpecificDisplayItem::YuvImage(ref info) => {
                 self.add_yuv_image(
                     clip_and_scroll,
                     &prim_info,
                     info.yuv_data,
                     info.color_space,
                     info.image_rendering,
@@ -1274,25 +1224,23 @@ impl<'a> DisplayListFlattener<'a> {
         );
     }
 
     pub fn push_reference_frame(
         &mut self,
         reference_frame_id: ClipId,
         parent_id: Option<ClipId>,
         pipeline_id: PipelineId,
-        rect: &LayoutRect,
         source_transform: Option<PropertyBinding<LayoutTransform>>,
         source_perspective: Option<LayoutTransform>,
         origin_in_parent_reference_frame: LayoutVector2D,
     ) -> ClipScrollNodeIndex {
         let index = self.id_to_index_mapper.get_node_index(reference_frame_id);
         let node = ClipScrollNode::new_reference_frame(
             parent_id.map(|id| self.id_to_index_mapper.get_node_index(id)),
-            rect,
             source_transform,
             source_perspective,
             origin_in_parent_reference_frame,
             pipeline_id,
         );
         self.clip_scroll_tree.add_node(node, index);
         self.reference_frame_stack.push((reference_frame_id, index));
 
@@ -1303,20 +1251,16 @@ impl<'a> DisplayListFlattener<'a> {
         }
         index
     }
 
     pub fn current_reference_frame_index(&self) -> ClipScrollNodeIndex {
         self.reference_frame_stack.last().unwrap().1
     }
 
-    pub fn current_reference_frame_id(&self) -> ClipId{
-        self.reference_frame_stack.last().unwrap().0
-    }
-
     pub fn setup_viewport_offset(
         &mut self,
         inner_rect: DeviceUintRect,
         device_pixel_scale: DevicePixelScale,
     ) {
         let viewport_offset = (inner_rect.origin.to_vector().to_f32() / device_pixel_scale).round();
         let root_id = self.clip_scroll_tree.root_reference_frame_index();
         let root_node = &mut self.clip_scroll_tree.nodes[root_id.0];
@@ -1327,55 +1271,50 @@ impl<'a> DisplayListFlattener<'a> {
     }
 
     pub fn push_root(
         &mut self,
         pipeline_id: PipelineId,
         viewport_size: &LayoutSize,
         content_size: &LayoutSize,
     ) {
-        let viewport_rect = LayoutRect::new(LayoutPoint::zero(), *viewport_size);
-
         self.push_reference_frame(
             ClipId::root_reference_frame(pipeline_id),
             None,
             pipeline_id,
-            &viewport_rect,
             None,
             None,
             LayoutVector2D::zero(),
         );
 
         self.add_scroll_frame(
             ClipId::root_scroll_node(pipeline_id),
             ClipId::root_reference_frame(pipeline_id),
             Some(ExternalScrollId(0, pipeline_id)),
             pipeline_id,
-            &viewport_rect,
+            &LayoutRect::new(LayoutPoint::zero(), *viewport_size),
             content_size,
             ScrollSensitivity::ScriptAndInputEvents,
         );
     }
 
     pub fn add_clip_node(
         &mut self,
         new_node_id: ClipId,
         parent_id: ClipId,
         clip_region: ClipRegion,
     ) -> ClipScrollNodeIndex {
-        let clip_rect = clip_region.main;
         let clip_sources = ClipSources::from(clip_region);
         let handle = self.clip_store.insert(clip_sources);
 
         let node_index = self.id_to_index_mapper.get_node_index(new_node_id);
         let clip_chain_index = self.clip_scroll_tree.add_clip_node(
             node_index,
             self.id_to_index_mapper.get_node_index(parent_id),
             handle,
-            clip_rect,
             new_node_id.pipeline_id(),
         );
         self.id_to_index_mapper.add_clip_chain(new_node_id, clip_chain_index);
         node_index
     }
 
     pub fn add_scroll_frame(
         &mut self,
@@ -1791,17 +1730,16 @@ impl<'a> DisplayListFlattener<'a> {
                 // Right
                 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,
                 );
-
                 let descriptor = BrushSegmentDescriptor {
                     segments,
                     clip_mask_kind: BrushClipMaskKind::Unknown,
                 };
 
                 let prim = PrimitiveContainer::Brush(match border.source {
                     NinePatchBorderSource::Image(image_key) => {
                         BrushPrimitive::new(
@@ -2159,87 +2097,62 @@ impl<'a> DisplayListFlattener<'a> {
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         stretch_size: LayoutSize,
         mut tile_spacing: LayoutSize,
         sub_rect: Option<TexelRect>,
         image_key: ImageKey,
         image_rendering: ImageRendering,
         alpha_type: AlphaType,
-        tile_offset: Option<TileOffset>,
     ) {
         let mut prim_rect = info.rect;
         simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
         let info = LayoutPrimitiveInfo {
             rect: prim_rect,
             .. *info
         };
 
-        let request = ImageRequest {
-            key: image_key,
-            rendering: image_rendering,
-            tile: tile_offset,
-        };
-
         let sub_rect = sub_rect.map(|texel_rect| {
             DeviceIntRect::new(
                 DeviceIntPoint::new(
                     texel_rect.uv0.x as i32,
                     texel_rect.uv0.y as i32,
                 ),
                 DeviceIntSize::new(
                     (texel_rect.uv1.x - texel_rect.uv0.x) as i32,
                     (texel_rect.uv1.y - texel_rect.uv0.y) as i32,
                 ),
             )
         });
 
-        // See if conditions are met to run through the new
-        // image brush shader, which supports segments.
-        if tile_offset.is_none() {
-            let prim = BrushPrimitive::new(
-                BrushKind::Image {
-                    request,
-                    current_epoch: Epoch::invalid(),
-                    alpha_type,
-                    stretch_size,
-                    tile_spacing,
-                    source: ImageSource::Default,
-                    sub_rect,
-                    opacity_binding: OpacityBinding::new(),
+        let prim = BrushPrimitive::new(
+            BrushKind::Image {
+                request: ImageRequest {
+                    key: image_key,
+                    rendering: image_rendering,
+                    tile: None,
                 },
-                None,
-            );
-
-            self.add_primitive(
-                clip_and_scroll,
-                &info,
-                Vec::new(),
-                PrimitiveContainer::Brush(prim),
-            );
-        } else {
-            let prim_cpu = ImagePrimitiveCpu {
-                tile_spacing,
+                current_epoch: Epoch::invalid(),
                 alpha_type,
                 stretch_size,
-                current_epoch: Epoch::invalid(),
+                tile_spacing,
                 source: ImageSource::Default,
-                key: ImageCacheKey {
-                    request,
-                    texel_rect: sub_rect,
-                },
-            };
+                sub_rect,
+                visible_tiles: Vec::new(),
+                opacity_binding: OpacityBinding::new(),
+            },
+            None,
+        );
 
-            self.add_primitive(
-                clip_and_scroll,
-                &info,
-                Vec::new(),
-                PrimitiveContainer::Image(prim_cpu),
-            );
-        }
+        self.add_primitive(
+            clip_and_scroll,
+            &info,
+            Vec::new(),
+            PrimitiveContainer::Brush(prim),
+        );
     }
 
     pub fn add_yuv_image(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         yuv_data: YuvData,
         color_space: YuvColorSpace,
@@ -2276,17 +2189,16 @@ pub fn build_scene(config: &FrameBuilder
     let mut clip_scroll_tree = ClipScrollTree::new();
     let mut new_scene = Scene::new();
 
     let frame_builder = DisplayListFlattener::create_frame_builder(
         FrameBuilder::empty(), // WIP, we're not really recycling anything here, clean this up.
         &request.scene,
         &mut clip_scroll_tree,
         request.font_instances,
-        request.tiled_image_map,
         &request.view,
         &request.output_pipelines,
         config,
         &mut new_scene
     );
 
     BuiltScene {
         scene: new_scene,
--- a/gfx/webrender/src/freelist.rs
+++ b/gfx/webrender/src/freelist.rs
@@ -47,24 +47,26 @@ impl<M> Clone for WeakFreeListHandle<M> 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct WeakFreeListHandle<M> {
     index: u32,
     epoch: Epoch,
     _marker: PhantomData<M>,
 }
 
+#[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct Slot<T> {
     next: Option<u32>,
     epoch: Epoch,
     value: Option<T>,
 }
 
+#[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FreeList<T, M> {
     slots: Vec<Slot<T>>,
     free_list_head: Option<u32>,
     active_count: usize,
     _marker: PhantomData<M>,
 }
deleted file mode 100644
--- a/gfx/webrender/src/geometry.rs
+++ /dev/null
@@ -1,113 +0,0 @@
-/* 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 euclid::{Point3D, Rect};
-
-/*
- A naive port of "An Efficient and Robust Ray–Box Intersection Algorithm"
- from https://www.cs.utah.edu/~awilliam/box/box.pdf
-
- This should be cleaned up and extracted into more useful types!
- */
-
-// Assumes rect is in the z=0 plane!
-pub fn ray_intersects_rect(
-    ray_origin: Point3D<f32>,
-    ray_end: Point3D<f32>,
-    rect: Rect<f32>,
-) -> bool {
-    let mut dir = ray_end - ray_origin;
-    let len = ((dir.x * dir.x) + (dir.y * dir.y) + (dir.z * dir.z)).sqrt();
-    dir.x = dir.x / len;
-    dir.y = dir.y / len;
-    dir.z = dir.z / len;
-    let inv_direction = Point3D::new(1.0 / dir.x, 1.0 / dir.y, 1.0 / dir.z);
-
-    let sign = [
-        if inv_direction.x < 0.0 { 1 } else { 0 },
-        if inv_direction.y < 0.0 { 1 } else { 0 },
-        if inv_direction.z < 0.0 { 1 } else { 0 },
-    ];
-
-    let parameters = [rect.origin.to_3d(), rect.bottom_right().to_3d()];
-
-    let mut tmin = (parameters[sign[0]].x - ray_origin.x) * inv_direction.x;
-    let mut tmax = (parameters[1 - sign[0]].x - ray_origin.x) * inv_direction.x;
-    let tymin = (parameters[sign[1]].y - ray_origin.y) * inv_direction.y;
-    let tymax = (parameters[1 - sign[1]].y - ray_origin.y) * inv_direction.y;
-    if (tmin > tymax) || (tymin > tmax) {
-        return false;
-    }
-    if tymin > tmin {
-        tmin = tymin;
-    }
-    if tymax < tmax {
-        tmax = tymax;
-    }
-    let tzmin = (parameters[sign[2]].z - ray_origin.z) * inv_direction.z;
-    let tzmax = (parameters[1 - sign[2]].z - ray_origin.z) * inv_direction.z;
-    if (tmin > tzmax) || (tzmin > tmax) {
-        return false;
-    }
-
-    // Don't care about where on the ray it hits...
-    true
-
-    /*
-    if tzmin > tmin {
-        tmin = tzmin;
-    }
-    if tzmax < tmax {
-        tmax = tzmax;
-    }
-
-    let t0 = 0.0;
-    let t1 = len;
-
-    (tmin < t1) && (tmax > t0)
-    */
-}
-
-/*
-pub fn circle_contains_rect(circle_center: &Point2D<f32>,
-                        radius: f32,
-                        rect: &Rect<f32>) -> bool {
-    let dx = (circle_center.x - rect.origin.x).max(
-        rect.origin.x + rect.size.width - circle_center.x
-    );
-    let dy = (circle_center.y - rect.origin.y).max(
-        rect.origin.y + rect.size.height - circle_center.y
-    );
-    radius * radius >= dx * dx + dy * dy
-}
-
-pub fn rect_intersects_circle(circle_center: &Point2D<f32>,
-                          radius: f32,
-                          rect: &Rect<f32>) -> bool {
-    let circle_distance_x = (circle_center.x - (rect.origin.x + rect.size.width * 0.5)).abs();
-    let circle_distance_y = (circle_center.y - (rect.origin.y + rect.size.height * 0.5)).abs();
-
-    if circle_distance_x > rect.size.width * 0.5 + radius {
-        return false
-    }
-    if circle_distance_y > rect.size.height * 0.5 + radius {
-        return false
-    }
-
-    if circle_distance_x <= rect.size.width * 0.5 {
-        return true;
-    }
-    if circle_distance_y <= rect.size.height * 0.5 {
-        return true;
-    }
-
-    let corner_distance_sq =
-        (circle_distance_x - rect.size.width * 0.5) *
-        (circle_distance_x - rect.size.width * 0.5) +
-        (circle_distance_y - rect.size.height * 0.5) *
-        (circle_distance_y - rect.size.height * 0.5);
-
-    corner_distance_sq <= radius * radius
-}
-*/
deleted file mode 100644
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ /dev/null
@@ -1,1083 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#[cfg(test)]
-use api::{IdNamespace, LayoutPoint};
-use api::{ColorF, ColorU};
-use api::{FontInstanceFlags, FontInstancePlatformOptions};
-use api::{FontKey, FontRenderMode, FontTemplate, FontVariation};
-use api::{GlyphDimensions, GlyphKey, LayoutToWorldTransform, SubpixelDirection};
-#[cfg(feature = "pathfinder")]
-use api::NativeFontHandle;
-#[cfg(any(test, feature = "pathfinder"))]
-use api::DeviceIntSize;
-#[cfg(not(feature = "pathfinder"))]
-use api::{ImageData, ImageDescriptor, ImageFormat};
-use app_units::Au;
-#[cfg(not(feature = "pathfinder"))]
-use device::TextureFilter;
-#[cfg(feature = "pathfinder")]
-use euclid::{TypedPoint2D, TypedSize2D, TypedVector2D};
-use glyph_cache::{CachedGlyphInfo, GlyphCache, GlyphCacheEntry};
-use gpu_cache::GpuCache;
-use gpu_types::UvRectKind;
-use internal_types::ResourceCacheError;
-#[cfg(feature = "pathfinder")]
-use pathfinder_font_renderer;
-#[cfg(feature = "pathfinder")]
-use pathfinder_partitioner::mesh::Mesh as PathfinderMesh;
-#[cfg(feature = "pathfinder")]
-use pathfinder_path_utils::cubic_to_quadratic::CubicToQuadraticTransformer;
-use platform::font::FontContext;
-use profiler::TextureCacheProfileCounters;
-use rayon::ThreadPool;
-#[cfg(not(feature = "pathfinder"))]
-use rayon::prelude::*;
-#[cfg(test)]
-use render_backend::FrameId;
-use render_task::{RenderTaskCache, RenderTaskTree};
-#[cfg(feature = "pathfinder")]
-use render_task::{RenderTask, RenderTaskCacheKey, RenderTaskCacheEntryHandle};
-#[cfg(feature = "pathfinder")]
-use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
-#[cfg(feature = "pathfinder")]
-use resource_cache::CacheItem;
-use std::cmp;
-use std::collections::hash_map::Entry;
-use std::f32;
-use std::hash::{Hash, Hasher};
-use std::mem;
-use std::sync::{Arc, Mutex, MutexGuard};
-use std::sync::mpsc::{channel, Receiver, Sender};
-use texture_cache::TextureCache;
-#[cfg(not(feature = "pathfinder"))]
-use texture_cache::TextureCacheHandle;
-#[cfg(test)]
-use thread_profiler::register_thread_with_profiler;
-#[cfg(feature = "pathfinder")]
-use tiling::RenderTargetKind;
-use tiling::SpecialRenderPasses;
-#[cfg(feature = "pathfinder")]
-use webrender_api::{DeviceIntPoint, DevicePixel};
-
-/// Should match macOS 10.13 High Sierra.
-///
-/// We multiply by sqrt(2) to compensate for the fact that dilation amounts are relative to the
-/// pixel square on macOS and relative to the vertex normal in Pathfinder.
-#[cfg(feature = "pathfinder")]
-const STEM_DARKENING_FACTOR_X: f32 = 0.0121 * f32::consts::SQRT_2;
-#[cfg(feature = "pathfinder")]
-const STEM_DARKENING_FACTOR_Y: f32 = 0.0121 * 1.25 * f32::consts::SQRT_2;
-
-/// Likewise, should match macOS 10.13 High Sierra.
-#[cfg(feature = "pathfinder")]
-const MAX_STEM_DARKENING_AMOUNT: f32 = 0.3 * f32::consts::SQRT_2;
-
-#[cfg(feature = "pathfinder")]
-const CUBIC_TO_QUADRATIC_APPROX_TOLERANCE: f32 = 0.01;
-
-#[cfg(feature = "pathfinder")]
-type PathfinderFontContext = pathfinder_font_renderer::FontContext<FontKey>;
-
-#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct FontTransform {
-    pub scale_x: f32,
-    pub skew_x: f32,
-    pub skew_y: f32,
-    pub scale_y: f32,
-}
-
-// Floats don't impl Hash/Eq/Ord...
-impl Eq for FontTransform {}
-impl Ord for FontTransform {
-    fn cmp(&self, other: &Self) -> cmp::Ordering {
-        self.partial_cmp(other).unwrap_or(cmp::Ordering::Equal)
-    }
-}
-impl Hash for FontTransform {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        // Note: this is inconsistent with the Eq impl for -0.0 (don't care).
-        self.scale_x.to_bits().hash(state);
-        self.skew_x.to_bits().hash(state);
-        self.skew_y.to_bits().hash(state);
-        self.scale_y.to_bits().hash(state);
-    }
-}
-
-impl FontTransform {
-    const QUANTIZE_SCALE: f32 = 1024.0;
-
-    pub fn new(scale_x: f32, skew_x: f32, skew_y: f32, scale_y: f32) -> Self {
-        FontTransform { scale_x, skew_x, skew_y, scale_y }
-    }
-
-    pub fn identity() -> Self {
-        FontTransform::new(1.0, 0.0, 0.0, 1.0)
-    }
-
-    pub fn is_identity(&self) -> bool {
-        *self == FontTransform::identity()
-    }
-
-    pub fn quantize(&self) -> Self {
-        FontTransform::new(
-            (self.scale_x * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
-            (self.skew_x * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
-            (self.skew_y * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
-            (self.scale_y * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
-        )
-    }
-
-    #[allow(dead_code)]
-    pub fn determinant(&self) -> f64 {
-        self.scale_x as f64 * self.scale_y as f64 - self.skew_y as f64 * self.skew_x as f64
-    }
-
-    #[allow(dead_code)]
-    pub fn compute_scale(&self) -> Option<(f64, f64)> {
-        let det = self.determinant();
-        if det != 0.0 {
-            let x_scale = (self.scale_x as f64).hypot(self.skew_y as f64);
-            let y_scale = det.abs() / x_scale;
-            Some((x_scale, y_scale))
-        } else {
-            None
-        }
-    }
-
-    #[allow(dead_code)]
-    pub fn pre_scale(&self, scale_x: f32, scale_y: f32) -> Self {
-        FontTransform::new(
-            self.scale_x * scale_x,
-            self.skew_x * scale_y,
-            self.skew_y * scale_x,
-            self.scale_y * scale_y,
-        )
-    }
-
-    #[allow(dead_code)]
-    pub fn invert_scale(&self, x_scale: f64, y_scale: f64) -> Self {
-        self.pre_scale(x_scale.recip() as f32, y_scale.recip() as f32)
-    }
-
-    pub fn synthesize_italics(&self, skew_factor: f32) -> Self {
-        FontTransform::new(
-            self.scale_x,
-            self.skew_x - self.scale_x * skew_factor,
-            self.skew_y,
-            self.scale_y - self.skew_y * skew_factor,
-        )
-    }
-
-    pub fn swap_xy(&self) -> Self {
-        FontTransform::new(self.skew_x, self.scale_x, self.scale_y, self.skew_y)
-    }
-
-    pub fn flip_x(&self) -> Self {
-        FontTransform::new(-self.scale_x, self.skew_x, -self.skew_y, self.scale_y)
-    }
-
-    pub fn flip_y(&self) -> Self {
-        FontTransform::new(self.scale_x, -self.skew_y, self.skew_y, -self.scale_y)
-    }
-}
-
-impl<'a> From<&'a LayoutToWorldTransform> for FontTransform {
-    fn from(xform: &'a LayoutToWorldTransform) -> Self {
-        FontTransform::new(xform.m11, xform.m21, xform.m12, xform.m22)
-    }
-}
-
-#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct FontInstance {
-    pub font_key: FontKey,
-    // The font size is in *device* pixels, not logical pixels.
-    // It is stored as an Au since we need sub-pixel sizes, but
-    // can't store as a f32 due to use of this type as a hash key.
-    // TODO(gw): Perhaps consider having LogicalAu and DeviceAu
-    //           or something similar to that.
-    pub size: Au,
-    pub color: ColorU,
-    pub bg_color: ColorU,
-    pub render_mode: FontRenderMode,
-    pub subpx_dir: SubpixelDirection,
-    pub flags: FontInstanceFlags,
-    pub platform_options: Option<FontInstancePlatformOptions>,
-    pub variations: Vec<FontVariation>,
-    pub transform: FontTransform,
-}
-
-impl FontInstance {
-    pub fn new(
-        font_key: FontKey,
-        size: Au,
-        color: ColorF,
-        bg_color: ColorU,
-        render_mode: FontRenderMode,
-        subpx_dir: SubpixelDirection,
-        flags: FontInstanceFlags,
-        platform_options: Option<FontInstancePlatformOptions>,
-        variations: Vec<FontVariation>,
-    ) -> Self {
-        // If a background color is enabled, it only makes sense
-        // for it to be completely opaque.
-        debug_assert!(bg_color.a == 0 || bg_color.a == 255);
-
-        FontInstance {
-            font_key,
-            size,
-            color: color.into(),
-            bg_color,
-            render_mode,
-            subpx_dir,
-            flags,
-            platform_options,
-            variations,
-            transform: FontTransform::identity(),
-        }
-    }
-
-    pub fn get_subpx_offset(&self, glyph: &GlyphKey) -> (f64, f64) {
-        match self.subpx_dir {
-            SubpixelDirection::None => (0.0, 0.0),
-            SubpixelDirection::Horizontal => (glyph.subpixel_offset.into(), 0.0),
-            SubpixelDirection::Vertical => (0.0, glyph.subpixel_offset.into()),
-        }
-    }
-
-    pub fn get_alpha_glyph_format(&self) -> GlyphFormat {
-        if self.transform.is_identity() { GlyphFormat::Alpha } else { GlyphFormat::TransformedAlpha }
-    }
-
-    pub fn get_subpixel_glyph_format(&self) -> GlyphFormat {
-        if self.transform.is_identity() { GlyphFormat::Subpixel } else { GlyphFormat::TransformedSubpixel }
-    }
-
-    #[allow(dead_code)]
-    pub fn get_glyph_format(&self) -> GlyphFormat {
-        match self.render_mode {
-            FontRenderMode::Mono | FontRenderMode::Alpha => self.get_alpha_glyph_format(),
-            FontRenderMode::Subpixel => self.get_subpixel_glyph_format(),
-        }
-    }
-
-    #[allow(dead_code)]
-    pub fn get_extra_strikes(&self, x_scale: f64) -> usize {
-        if self.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) {
-            let mut bold_offset = self.size.to_f64_px() / 48.0;
-            if bold_offset < 1.0 {
-                bold_offset = 0.25 + 0.75 * bold_offset;
-            }
-            (bold_offset * x_scale).max(1.0).round() as usize
-        } else {
-            0
-        }
-    }
-}
-
-#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-#[allow(dead_code)]
-pub enum GlyphFormat {
-    Alpha,
-    TransformedAlpha,
-    Subpixel,
-    TransformedSubpixel,
-    Bitmap,
-    ColorBitmap,
-}
-
-impl GlyphFormat {
-    pub fn ignore_color(self) -> Self {
-        match self {
-            GlyphFormat::ColorBitmap => GlyphFormat::Bitmap,
-            _ => self,
-        }
-    }
-}
-
-pub struct RasterizedGlyph {
-    pub top: f32,
-    pub left: f32,
-    pub width: u32,
-    pub height: u32,
-    pub scale: f32,
-    pub format: GlyphFormat,
-    pub bytes: Vec<u8>,
-}
-
-pub struct FontContexts {
-    // These worker are mostly accessed from their corresponding worker threads.
-    // The goal is that there should be no noticeable contention on the mutexes.
-    worker_contexts: Vec<Mutex<FontContext>>,
-
-    // This worker should be accessed by threads that don't belong to the thread pool
-    // (in theory that's only the render backend thread so no contention expected either).
-    shared_context: Mutex<FontContext>,
-
-    #[cfg(feature = "pathfinder")]
-    pathfinder_context: Box<Mutex<PathfinderFontContext>>,
-    #[cfg(not(feature = "pathfinder"))]
-    #[allow(dead_code)]
-    pathfinder_context: (),
-
-    // Stored here as a convenience to get the current thread index.
-    #[allow(dead_code)]
-    workers: Arc<ThreadPool>,
-}
-
-impl FontContexts {
-    /// Get access to the font context associated to the current thread.
-    #[cfg(not(feature = "pathfinder"))]
-    pub fn lock_current_context(&self) -> MutexGuard<FontContext> {
-        let id = self.current_worker_id();
-        self.lock_context(id)
-    }
-
-    /// Get access to any particular font context.
-    ///
-    /// The id is ```Some(i)``` where i is an index between 0 and num_worker_contexts
-    /// for font contexts associated to the thread pool, and None for the shared
-    /// global font context for use outside of the thread pool.
-    pub fn lock_context(&self, id: Option<usize>) -> MutexGuard<FontContext> {
-        match id {
-            Some(index) => self.worker_contexts[index].lock().unwrap(),
-            None => self.shared_context.lock().unwrap(),
-        }
-    }
-
-    /// Get access to the font context usable outside of the thread pool.
-    pub fn lock_shared_context(&self) -> MutexGuard<FontContext> {
-        self.shared_context.lock().unwrap()
-    }
-
-    #[cfg(feature = "pathfinder")]
-    pub fn lock_pathfinder_context(&self) -> MutexGuard<PathfinderFontContext> {
-        self.pathfinder_context.lock().unwrap()
-    }
-
-    // number of contexts associated to workers
-    pub fn num_worker_contexts(&self) -> usize {
-        self.worker_contexts.len()
-    }
-
-    #[cfg(not(feature = "pathfinder"))]
-    fn current_worker_id(&self) -> Option<usize> {
-        self.workers.current_thread_index()
-    }
-}
-
-pub struct GlyphRasterizer {
-    #[allow(dead_code)]
-    workers: Arc<ThreadPool>,
-    font_contexts: Arc<FontContexts>,
-
-    // Maintain a set of glyphs that have been requested this
-    // frame. This ensures the glyph thread won't rasterize
-    // the same glyph more than once in a frame. This is required
-    // because the glyph cache hash table is not updated
-    // until the end of the frame when we wait for glyph requests
-    // to be resolved.
-    #[allow(dead_code)]
-    pending_glyphs: usize,
-
-    // Receives the rendered glyphs.
-    #[allow(dead_code)]
-    glyph_rx: Receiver<GlyphRasterJobs>,
-    #[allow(dead_code)]
-    glyph_tx: Sender<GlyphRasterJobs>,
-
-    // We defer removing fonts to the end of the frame so that:
-    // - this work is done outside of the critical path,
-    // - we don't have to worry about the ordering of events if a font is used on
-    //   a frame where it is used (although it seems unlikely).
-    fonts_to_remove: Vec<FontKey>,
-
-    #[allow(dead_code)]
-    next_gpu_glyph_cache_key: GpuGlyphCacheKey,
-}
-
-impl GlyphRasterizer {
-    pub fn new(workers: Arc<ThreadPool>) -> Result<Self, ResourceCacheError> {
-        let (glyph_tx, glyph_rx) = channel();
-
-        let num_workers = workers.current_num_threads();
-        let mut contexts = Vec::with_capacity(num_workers);
-
-        let shared_context = FontContext::new()?;
-
-        for _ in 0 .. num_workers {
-            contexts.push(Mutex::new(FontContext::new()?));
-        }
-
-        let pathfinder_context = create_pathfinder_font_context()?;
-
-        Ok(GlyphRasterizer {
-            font_contexts: Arc::new(FontContexts {
-                worker_contexts: contexts,
-                shared_context: Mutex::new(shared_context),
-                pathfinder_context,
-                workers: Arc::clone(&workers),
-            }),
-            pending_glyphs: 0,
-            glyph_rx,
-            glyph_tx,
-            workers,
-            fonts_to_remove: Vec::new(),
-            next_gpu_glyph_cache_key: GpuGlyphCacheKey(0),
-        })
-    }
-
-    pub fn add_font(&mut self, font_key: FontKey, template: FontTemplate) {
-        let font_contexts = Arc::clone(&self.font_contexts);
-        // It's important to synchronously add the font for the shared context because
-        // we use it to check that fonts have been properly added when requesting glyphs.
-        font_contexts
-            .lock_shared_context()
-            .add_font(&font_key, &template);
-
-        // TODO: this locks each font context while adding the font data, probably not a big deal,
-        // but if there is contention on this lock we could easily have a queue of per-context
-        // operations to add and delete fonts, and have these queues lazily processed by each worker
-        // before rendering a glyph.
-        // We can also move this into a worker to free up some cycles in the calling (render backend)
-        // thread.
-        for i in 0 .. font_contexts.num_worker_contexts() {
-            font_contexts
-                .lock_context(Some(i))
-                .add_font(&font_key, &template);
-        }
-
-        self.add_font_to_pathfinder(&font_key, &template);
-    }
-
-    #[cfg(feature = "pathfinder")]
-    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);
-    }
-
-    #[cfg(not(feature = "pathfinder"))]
-    fn add_font_to_pathfinder(&mut self, _: &FontKey, _: &FontTemplate) {}
-
-    pub fn delete_font(&mut self, font_key: FontKey) {
-        self.fonts_to_remove.push(font_key);
-    }
-
-    pub fn prepare_font(&self, font: &mut FontInstance) {
-        FontContext::prepare_font(font);
-    }
-
-    #[cfg(feature = "pathfinder")]
-    pub fn get_cache_item_for_glyph(&self,
-                                    glyph_key: &GlyphKey,
-                                    font: &FontInstance,
-                                    glyph_cache: &GlyphCache,
-                                    texture_cache: &TextureCache,
-                                    render_task_cache: &RenderTaskCache)
-                                    -> Option<(CacheItem, GlyphFormat)> {
-        let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font(font);
-        let render_task_cache_key = match *glyph_key_cache.get(glyph_key) {
-            GlyphCacheEntry::Cached(ref cached_glyph) => {
-                (*cached_glyph).render_task_cache_key.clone()
-            }
-            GlyphCacheEntry::Blank => return None,
-            GlyphCacheEntry::Pending => {
-                panic!("GlyphRasterizer::get_cache_item_for_glyph(): Glyph should have been \
-                        cached by now!")
-            }
-        };
-        let cache_item = render_task_cache.get_cache_item_for_render_task(texture_cache,
-                                                                          &render_task_cache_key);
-        Some((cache_item, font.get_glyph_format()))
-    }
-
-    #[cfg(feature = "pathfinder")]
-    fn request_glyph_from_pathfinder_if_necessary(&mut self,
-                                                  glyph_key: &GlyphKey,
-                                                  font: &FontInstance,
-                                                  cached_glyph_info: CachedGlyphInfo,
-                                                  texture_cache: &mut TextureCache,
-                                                  gpu_cache: &mut GpuCache,
-                                                  render_task_cache: &mut RenderTaskCache,
-                                                  render_task_tree: &mut RenderTaskTree,
-                                                  render_passes: &mut SpecialRenderPasses)
-                                                  -> Result<(RenderTaskCacheEntryHandle,
-                                                             GlyphFormat), ()> {
-        let mut pathfinder_font_context = self.font_contexts.lock_pathfinder_context();
-        let render_task_cache_key = cached_glyph_info.render_task_cache_key;
-        let (glyph_origin, glyph_size) = (cached_glyph_info.origin, render_task_cache_key.size);
-        let user_data = [glyph_origin.x as f32, (glyph_origin.y - glyph_size.height) as f32, 1.0];
-        let handle = try!(render_task_cache.request_render_task(render_task_cache_key,
-                                                                texture_cache,
-                                                                gpu_cache,
-                                                                render_task_tree,
-                                                                Some(user_data),
-                                                                false,
-                                                                |render_tasks| {
-            // TODO(pcwalton): Non-subpixel font render mode.
-            request_render_task_from_pathfinder(glyph_key,
-                                                font,
-                                                &glyph_origin,
-                                                &glyph_size,
-                                                &mut *pathfinder_font_context,
-                                                font.render_mode,
-                                                render_tasks,
-                                                render_passes)
-        }));
-        Ok((handle, font.get_glyph_format()))
-    }
-
-    #[cfg(feature = "pathfinder")]
-    pub fn request_glyphs(
-        &mut self,
-        glyph_cache: &mut GlyphCache,
-        font: FontInstance,
-        glyph_keys: &[GlyphKey],
-        texture_cache: &mut TextureCache,
-        gpu_cache: &mut GpuCache,
-        render_task_cache: &mut RenderTaskCache,
-        render_task_tree: &mut RenderTaskTree,
-        render_passes: &mut SpecialRenderPasses,
-    ) {
-        debug_assert!(self.font_contexts.lock_shared_context().has_font(&font.font_key));
-
-        let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font.clone());
-
-        // select glyphs that have not been requested yet.
-        for glyph_key in glyph_keys {
-            let mut cached_glyph_info = None;
-            match glyph_key_cache.entry(glyph_key.clone()) {
-                Entry::Occupied(mut entry) => {
-                    let value = entry.into_mut();
-                    match *value {
-                        GlyphCacheEntry::Cached(ref glyph_info) => {
-                            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();
-
-                    let pathfinder_font_instance = pathfinder_font_renderer::FontInstance {
-                        font_key: font.font_key.clone(),
-                        size: font.size,
-                    };
-
-                    let pathfinder_subpixel_offset =
-                        pathfinder_font_renderer::SubpixelOffset(glyph_key.subpixel_offset 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,
-                        };
-
-                    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),
-                        },
-                        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,
-                };
-
-            glyph_key_cache.insert(glyph_key.clone(), handle);
-        }
-    }
-
-    #[cfg(not(feature = "pathfinder"))]
-    pub fn request_glyphs(
-        &mut self,
-        glyph_cache: &mut GlyphCache,
-        font: FontInstance,
-        glyph_keys: &[GlyphKey],
-        texture_cache: &mut TextureCache,
-        gpu_cache: &mut GpuCache,
-        _: &mut RenderTaskCache,
-        _: &mut RenderTaskTree,
-        _: &mut SpecialRenderPasses,
-    ) {
-        assert!(
-            self.font_contexts
-                .lock_shared_context()
-                .has_font(&font.font_key)
-        );
-        let mut new_glyphs = Vec::new();
-
-        let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font.clone());
-
-        // select glyphs that have not been requested yet.
-        for key in glyph_keys {
-            match glyph_key_cache.entry(key.clone()) {
-                Entry::Occupied(mut entry) => {
-                    let value = entry.into_mut();
-                    match *value {
-                        GlyphCacheEntry::Cached(ref glyph) => {
-                            // Skip the glyph if it is already has a valid texture cache handle.
-                            if !texture_cache.request(&glyph.texture_cache_handle, gpu_cache) {
-                                continue;
-                            }
-                        }
-                        // Otherwise, skip the entry if it is blank or pending.
-                        GlyphCacheEntry::Blank | GlyphCacheEntry::Pending => continue,
-                    }
-
-                    // This case gets hit when we already rasterized the glyph, but the
-                    // glyph has been evicted from the texture cache. Just force it to
-                    // pending so it gets rematerialized.
-                    *value = GlyphCacheEntry::Pending;
-                    new_glyphs.push((*key).clone());
-                }
-                Entry::Vacant(entry) => {
-                    // This is the first time we've seen the glyph, so mark it as pending.
-                    entry.insert(GlyphCacheEntry::Pending);
-                    new_glyphs.push((*key).clone());
-                }
-            }
-        }
-
-        if new_glyphs.is_empty() {
-            return;
-        }
-
-        self.pending_glyphs += 1;
-
-        self.request_glyphs_from_backend(font, new_glyphs);
-    }
-
-    #[cfg(not(feature = "pathfinder"))]
-    fn request_glyphs_from_backend(&mut self, font: FontInstance, glyphs: Vec<GlyphKey>) {
-        let font_contexts = Arc::clone(&self.font_contexts);
-        let glyph_tx = self.glyph_tx.clone();
-
-        // spawn an async task to get off of the render backend thread as early as
-        // possible and in that task use rayon's fork join dispatch to rasterize the
-        // glyphs in the thread pool.
-        self.workers.spawn(move || {
-            let jobs = glyphs
-                .par_iter()
-                .map(|key: &GlyphKey| {
-                    profile_scope!("glyph-raster");
-                    let mut context = font_contexts.lock_current_context();
-                    let job = GlyphRasterJob {
-                        key: key.clone(),
-                        result: context.rasterize_glyph(&font, key),
-                    };
-
-                    // Sanity check.
-                    if let GlyphRasterResult::Bitmap(ref glyph) = job.result {
-                        let bpp = 4; // We always render glyphs in 32 bits RGBA format.
-                        assert_eq!(
-                            glyph.bytes.len(),
-                            bpp * (glyph.width * glyph.height) as usize
-                        );
-                    }
-
-                    job
-                })
-                .collect();
-
-            glyph_tx.send(GlyphRasterJobs { font, jobs }).unwrap();
-        });
-    }
-
-    pub fn get_glyph_dimensions(
-        &mut self,
-        font: &FontInstance,
-        glyph_key: &GlyphKey,
-    ) -> Option<GlyphDimensions> {
-        self.font_contexts
-            .lock_shared_context()
-            .get_glyph_dimensions(font, glyph_key)
-    }
-
-    pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
-        self.font_contexts
-            .lock_shared_context()
-            .get_glyph_index(font_key, ch)
-    }
-
-    #[cfg(feature = "pathfinder")]
-    pub fn resolve_glyphs(
-        &mut self,
-        _: &mut GlyphCache,
-        _: &mut TextureCache,
-        _: &mut GpuCache,
-        _: &mut RenderTaskCache,
-        _: &mut RenderTaskTree,
-        _: &mut TextureCacheProfileCounters,
-    ) {
-        self.remove_dead_fonts();
-    }
-
-    #[cfg(not(feature = "pathfinder"))]
-    pub fn resolve_glyphs(
-        &mut self,
-        glyph_cache: &mut GlyphCache,
-        texture_cache: &mut TextureCache,
-        gpu_cache: &mut GpuCache,
-        _: &mut RenderTaskCache,
-        _: &mut RenderTaskTree,
-        _: &mut TextureCacheProfileCounters,
-    ) {
-        // Pull rasterized glyphs from the queue and update the caches.
-        while self.pending_glyphs > 0 {
-            self.pending_glyphs -= 1;
-
-            // TODO: rather than blocking until all pending glyphs are available
-            // we could try_recv and steal work from the thread pool to take advantage
-            // of the fact that this thread is alive and we avoid the added latency
-            // of blocking it.
-            let GlyphRasterJobs { font, mut jobs } = self.glyph_rx
-                .recv()
-                .expect("BUG: Should be glyphs pending!");
-
-            // Ensure that the glyphs are always processed in the same
-            // order for a given text run (since iterating a hash set doesn't
-            // guarantee order). This can show up as very small float inaccuracy
-            // differences in rasterizers due to the different coordinates
-            // that text runs get associated with by the texture cache allocator.
-            jobs.sort_by(|a, b| a.key.cmp(&b.key));
-
-            let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font);
-
-            for GlyphRasterJob { key, result } in jobs {
-                let glyph_info = match result {
-                    GlyphRasterResult::LoadFailed => GlyphCacheEntry::Blank,
-                    GlyphRasterResult::Bitmap(ref glyph) if glyph.width == 0 ||
-                                                            glyph.height == 0 => {
-                        GlyphCacheEntry::Blank
-                    }
-                    GlyphRasterResult::Bitmap(glyph) => {
-                        assert_eq!((glyph.left.fract(), glyph.top.fract()), (0.0, 0.0));
-                        let mut texture_cache_handle = TextureCacheHandle::new();
-                        texture_cache.request(&texture_cache_handle, gpu_cache);
-                        texture_cache.update(
-                            &mut texture_cache_handle,
-                            ImageDescriptor {
-                                width: glyph.width,
-                                height: glyph.height,
-                                stride: None,
-                                format: ImageFormat::BGRA8,
-                                is_opaque: false,
-                                allow_mipmaps: false,
-                                offset: 0,
-                            },
-                            TextureFilter::Linear,
-                            Some(ImageData::Raw(Arc::new(glyph.bytes))),
-                            [glyph.left, -glyph.top, glyph.scale],
-                            None,
-                            gpu_cache,
-                            Some(glyph_key_cache.eviction_notice()),
-                            UvRectKind::Rect,
-                        );
-                        GlyphCacheEntry::Cached(CachedGlyphInfo {
-                            texture_cache_handle,
-                            format: glyph.format,
-                        })
-                    }
-                };
-                glyph_key_cache.insert(key, glyph_info);
-            }
-        }
-
-        // Now that we are done with the critical path (rendering the glyphs),
-        // we can schedule removing the fonts if needed.
-        self.remove_dead_fonts();
-    }
-
-    fn remove_dead_fonts(&mut self) {
-        if self.fonts_to_remove.is_empty() {
-            return
-        }
-
-        let font_contexts = Arc::clone(&self.font_contexts);
-        let fonts_to_remove = mem::replace(&mut self.fonts_to_remove, Vec::new());
-        self.workers.spawn(move || {
-            for font_key in &fonts_to_remove {
-                font_contexts.lock_shared_context().delete_font(font_key);
-            }
-            for i in 0 .. font_contexts.num_worker_contexts() {
-                let mut context = font_contexts.lock_context(Some(i));
-                for font_key in &fonts_to_remove {
-                    context.delete_font(font_key);
-                }
-            }
-        });
-    }
-
-    #[cfg(feature = "replay")]
-    pub fn reset(&mut self) {
-        //TODO: any signals need to be sent to the workers?
-        self.pending_glyphs = 0;
-        self.fonts_to_remove.clear();
-    }
-}
-
-trait AddFont {
-    fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate);
-}
-
-impl AddFont for FontContext {
-    fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate) {
-        match *template {
-            FontTemplate::Raw(ref bytes, index) => {
-                self.add_raw_font(font_key, bytes.clone(), index);
-            }
-            FontTemplate::Native(ref native_font_handle) => {
-                self.add_native_font(font_key, (*native_font_handle).clone());
-            }
-        }
-    }
-}
-
-#[cfg(feature = "pathfinder")]
-impl AddFont for PathfinderFontContext {
-    fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate) {
-        match *template {
-            FontTemplate::Raw(ref bytes, index) => {
-                drop(self.add_font_from_memory(&font_key, bytes.clone(), index))
-            }
-            FontTemplate::Native(ref native_font_handle) => {
-                drop(self.add_native_font(&font_key, NativeFontHandleWrapper(native_font_handle)))
-            }
-        }
-    }
-}
-
-#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct GlyphRequest {
-    pub key: GlyphKey,
-    pub font: FontInstance,
-}
-
-impl GlyphRequest {
-    pub fn new(font: &FontInstance, key: &GlyphKey) -> Self {
-        GlyphRequest {
-            key: key.clone(),
-            font: font.clone(),
-        }
-    }
-}
-
-#[allow(dead_code)]
-struct GlyphRasterJob {
-    key: GlyphKey,
-    result: GlyphRasterResult,
-}
-
-#[allow(dead_code)]
-pub enum GlyphRasterResult {
-    LoadFailed,
-    Bitmap(RasterizedGlyph),
-}
-
-#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct GpuGlyphCacheKey(pub u32);
-
-#[cfg(feature = "pathfinder")]
-fn create_pathfinder_font_context() -> Result<Box<Mutex<PathfinderFontContext>>,
-                                              ResourceCacheError> {
-    match PathfinderFontContext::new() {
-        Ok(context) => Ok(Box::new(Mutex::new(context))),
-        Err(_) => {
-            let msg = "Failed to create the Pathfinder font context!".to_owned();
-            Err(ResourceCacheError::new(msg))
-        }
-    }
-}
-
-#[cfg(not(feature = "pathfinder"))]
-fn create_pathfinder_font_context() -> Result<(), ResourceCacheError> {
-    Ok(())
-}
-
-#[allow(dead_code)]
-struct GlyphRasterJobs {
-    font: FontInstance,
-    jobs: Vec<GlyphRasterJob>,
-}
-
-#[test]
-fn rasterize_200_glyphs() {
-    // This test loads a font from disc, the renders 4 requests containing
-    // 50 glyphs each, deletes the font and waits for the result.
-
-    use rayon::ThreadPoolBuilder;
-    use std::fs::File;
-    use std::io::Read;
-
-    let worker = ThreadPoolBuilder::new()
-        .thread_name(|idx|{ format!("WRWorker#{}", idx) })
-        .start_handler(move |idx| {
-            register_thread_with_profiler(format!("WRWorker#{}", idx));
-        })
-        .build();
-    let workers = Arc::new(worker.unwrap());
-    let mut glyph_rasterizer = GlyphRasterizer::new(workers).unwrap();
-    let mut glyph_cache = GlyphCache::new();
-    let mut gpu_cache = GpuCache::new();
-    let mut texture_cache = TextureCache::new(2048);
-    let mut render_task_cache = RenderTaskCache::new();
-    let mut render_task_tree = RenderTaskTree::new(FrameId(0));
-    let mut special_render_passes = SpecialRenderPasses::new(&DeviceIntSize::new(1366, 768));
-
-    let mut font_file =
-        File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file");
-    let mut font_data = vec![];
-    font_file
-        .read_to_end(&mut font_data)
-        .expect("failed to read font file");
-
-    let font_key = FontKey::new(IdNamespace(0), 0);
-    glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0));
-
-    let font = FontInstance::new(
-        font_key,
-        Au::from_px(32),
-        ColorF::new(0.0, 0.0, 0.0, 1.0),
-        ColorU::new(0, 0, 0, 0),
-        FontRenderMode::Subpixel,
-        SubpixelDirection::Horizontal,
-        Default::default(),
-        None,
-        Vec::new(),
-    );
-
-    let mut glyph_keys = Vec::with_capacity(200);
-    for i in 0 .. 200 {
-        glyph_keys.push(GlyphKey::new(
-            i,
-            LayoutPoint::zero(),
-            font.render_mode,
-            font.subpx_dir,
-        ));
-    }
-
-    for i in 0 .. 4 {
-        glyph_rasterizer.request_glyphs(
-            &mut glyph_cache,
-            font.clone(),
-            &glyph_keys[(50 * i) .. (50 * (i + 1))],
-            &mut texture_cache,
-            &mut gpu_cache,
-            &mut render_task_cache,
-            &mut render_task_tree,
-            &mut special_render_passes,
-        );
-    }
-
-    glyph_rasterizer.delete_font(font_key);
-
-    glyph_rasterizer.resolve_glyphs(
-        &mut glyph_cache,
-        &mut TextureCache::new(4096),
-        &mut gpu_cache,
-        &mut render_task_cache,
-        &mut render_task_tree,
-        &mut TextureCacheProfileCounters::new(),
-    );
-}
-
-#[cfg(feature = "pathfinder")]
-fn compute_embolden_amount(ppem: f32) -> TypedVector2D<f32, DevicePixel> {
-    TypedVector2D::new(f32::min(ppem * STEM_DARKENING_FACTOR_X, MAX_STEM_DARKENING_AMOUNT),
-                       f32::min(ppem * STEM_DARKENING_FACTOR_Y, MAX_STEM_DARKENING_AMOUNT))
-}
-
-#[cfg(feature = "pathfinder")]
-fn request_render_task_from_pathfinder(glyph_key: &GlyphKey,
-                                       font: &FontInstance,
-                                       glyph_origin: &DeviceIntPoint,
-                                       glyph_size: &DeviceIntSize,
-                                       font_context: &mut PathfinderFontContext,
-                                       render_mode: FontRenderMode,
-                                       render_tasks: &mut RenderTaskTree,
-                                       render_passes: &mut SpecialRenderPasses)
-                                       -> Result<RenderTaskId, ()> {
-    let pathfinder_font_instance = pathfinder_font_renderer::FontInstance {
-        font_key: font.font_key.clone(),
-        size: font.size,
-    };
-
-    let pathfinder_subpixel_offset =
-        pathfinder_font_renderer::SubpixelOffset(glyph_key.subpixel_offset as u8);
-    let glyph_subpixel_offset: f64 = glyph_key.subpixel_offset.into();
-    let pathfinder_glyph_key = pathfinder_font_renderer::GlyphKey::new(glyph_key.index,
-                                                                       pathfinder_subpixel_offset);
-
-    // TODO(pcwalton): Fall back to CPU rendering if Pathfinder fails to collect the outline.
-    let mut mesh = PathfinderMesh::new();
-    let outline = try!(font_context.glyph_outline(&pathfinder_font_instance,
-                                                  &pathfinder_glyph_key));
-    let tolerance = CUBIC_TO_QUADRATIC_APPROX_TOLERANCE;
-    mesh.push_stencil_segments(CubicToQuadraticTransformer::new(outline.iter(), tolerance));
-    mesh.push_stencil_normals(CubicToQuadraticTransformer::new(outline.iter(), tolerance));
-
-    // FIXME(pcwalton): Support vertical subpixel offsets.
-    // FIXME(pcwalton): Embolden amount should be 0 on macOS if "Use LCD font
-    // smoothing" is unchecked in System Preferences.
-
-    let subpixel_offset = TypedPoint2D::new(glyph_subpixel_offset as f32, 0.0);
-    let embolden_amount = compute_embolden_amount(font.size.to_f32_px());
-
-    let location = RenderTaskLocation::Dynamic(None, Some(*glyph_size));
-    let glyph_render_task = RenderTask::new_glyph(location,
-                                                  mesh,
-                                                  &glyph_origin,
-                                                  &subpixel_offset,
-                                                  font.render_mode,
-                                                  &embolden_amount);
-
-    let root_task_id = render_tasks.add(glyph_render_task);
-    let render_pass = match render_mode {
-        FontRenderMode::Mono | FontRenderMode::Alpha => &mut render_passes.alpha_glyph_pass,
-        FontRenderMode::Subpixel => &mut render_passes.color_glyph_pass,
-    };
-    render_pass.add_render_task(root_task_id, *glyph_size, RenderTargetKind::Color);
-
-    Ok(root_task_id)
-}
-
-#[cfg(feature = "pathfinder")]
-pub struct NativeFontHandleWrapper<'a>(pub &'a NativeFontHandle);
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/glyph_rasterizer/mod.rs
@@ -0,0 +1,593 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use api::{ColorF, ColorU};
+use api::{FontInstanceFlags, FontInstancePlatformOptions};
+use api::{FontKey, FontRenderMode, FontTemplate, FontVariation};
+use api::{GlyphDimensions, GlyphKey, LayoutToWorldTransform, SubpixelDirection};
+use app_units::Au;
+use internal_types::ResourceCacheError;
+use platform::font::FontContext;
+use rayon::ThreadPool;
+use std::cmp;
+use std::hash::{Hash, Hasher};
+use std::mem;
+use std::sync::{Arc, Mutex, MutexGuard};
+use std::sync::mpsc::{channel, Receiver, Sender};
+
+#[cfg(feature = "pathfinder")]
+mod pathfinder;
+#[cfg(feature = "pathfinder")]
+use self::pathfinder::create_pathfinder_font_context;
+#[cfg(feature = "pathfinder")]
+pub use self::pathfinder::{ThreadSafePathfinderFontContext, NativeFontHandleWrapper};
+
+#[cfg(not(feature = "pathfinder"))]
+mod no_pathfinder;
+
+#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct FontTransform {
+    pub scale_x: f32,
+    pub skew_x: f32,
+    pub skew_y: f32,
+    pub scale_y: f32,
+}
+
+// Floats don't impl Hash/Eq/Ord...
+impl Eq for FontTransform {}
+impl Ord for FontTransform {
+    fn cmp(&self, other: &Self) -> cmp::Ordering {
+        self.partial_cmp(other).unwrap_or(cmp::Ordering::Equal)
+    }
+}
+impl Hash for FontTransform {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        // Note: this is inconsistent with the Eq impl for -0.0 (don't care).
+        self.scale_x.to_bits().hash(state);
+        self.skew_x.to_bits().hash(state);
+        self.skew_y.to_bits().hash(state);
+        self.scale_y.to_bits().hash(state);
+    }
+}
+
+impl FontTransform {
+    const QUANTIZE_SCALE: f32 = 1024.0;
+
+    pub fn new(scale_x: f32, skew_x: f32, skew_y: f32, scale_y: f32) -> Self {
+        FontTransform { scale_x, skew_x, skew_y, scale_y }
+    }
+
+    pub fn identity() -> Self {
+        FontTransform::new(1.0, 0.0, 0.0, 1.0)
+    }
+
+    pub fn is_identity(&self) -> bool {
+        *self == FontTransform::identity()
+    }
+
+    pub fn quantize(&self) -> Self {
+        FontTransform::new(
+            (self.scale_x * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
+            (self.skew_x * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
+            (self.skew_y * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
+            (self.scale_y * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
+        )
+    }
+
+    #[allow(dead_code)]
+    pub fn determinant(&self) -> f64 {
+        self.scale_x as f64 * self.scale_y as f64 - self.skew_y as f64 * self.skew_x as f64
+    }
+
+    #[allow(dead_code)]
+    pub fn compute_scale(&self) -> Option<(f64, f64)> {
+        let det = self.determinant();
+        if det != 0.0 {
+            let x_scale = (self.scale_x as f64).hypot(self.skew_y as f64);
+            let y_scale = det.abs() / x_scale;
+            Some((x_scale, y_scale))
+        } else {
+            None
+        }
+    }
+
+    #[allow(dead_code)]
+    pub fn pre_scale(&self, scale_x: f32, scale_y: f32) -> Self {
+        FontTransform::new(
+            self.scale_x * scale_x,
+            self.skew_x * scale_y,
+            self.skew_y * scale_x,
+            self.scale_y * scale_y,
+        )
+    }
+
+    #[allow(dead_code)]
+    pub fn invert_scale(&self, x_scale: f64, y_scale: f64) -> Self {
+        self.pre_scale(x_scale.recip() as f32, y_scale.recip() as f32)
+    }
+
+    pub fn synthesize_italics(&self, skew_factor: f32) -> Self {
+        FontTransform::new(
+            self.scale_x,
+            self.skew_x - self.scale_x * skew_factor,
+            self.skew_y,
+            self.scale_y - self.skew_y * skew_factor,
+        )
+    }
+
+    pub fn swap_xy(&self) -> Self {
+        FontTransform::new(self.skew_x, self.scale_x, self.scale_y, self.skew_y)
+    }
+
+    pub fn flip_x(&self) -> Self {
+        FontTransform::new(-self.scale_x, self.skew_x, -self.skew_y, self.scale_y)
+    }
+
+    pub fn flip_y(&self) -> Self {
+        FontTransform::new(self.scale_x, -self.skew_y, self.skew_y, -self.scale_y)
+    }
+}
+
+impl<'a> From<&'a LayoutToWorldTransform> for FontTransform {
+    fn from(xform: &'a LayoutToWorldTransform) -> Self {
+        FontTransform::new(xform.m11, xform.m21, xform.m12, xform.m22)
+    }
+}
+
+#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct FontInstance {
+    pub font_key: FontKey,
+    // The font size is in *device* pixels, not logical pixels.
+    // It is stored as an Au since we need sub-pixel sizes, but
+    // can't store as a f32 due to use of this type as a hash key.
+    // TODO(gw): Perhaps consider having LogicalAu and DeviceAu
+    //           or something similar to that.
+    pub size: Au,
+    pub color: ColorU,
+    pub bg_color: ColorU,
+    pub render_mode: FontRenderMode,
+    pub subpx_dir: SubpixelDirection,
+    pub flags: FontInstanceFlags,
+    pub platform_options: Option<FontInstancePlatformOptions>,
+    pub variations: Vec<FontVariation>,
+    pub transform: FontTransform,
+}
+
+impl FontInstance {
+    pub fn new(
+        font_key: FontKey,
+        size: Au,
+        color: ColorF,
+        bg_color: ColorU,
+        render_mode: FontRenderMode,
+        subpx_dir: SubpixelDirection,
+        flags: FontInstanceFlags,
+        platform_options: Option<FontInstancePlatformOptions>,
+        variations: Vec<FontVariation>,
+    ) -> Self {
+        // If a background color is enabled, it only makes sense
+        // for it to be completely opaque.
+        debug_assert!(bg_color.a == 0 || bg_color.a == 255);
+
+        FontInstance {
+            font_key,
+            size,
+            color: color.into(),
+            bg_color,
+            render_mode,
+            subpx_dir,
+            flags,
+            platform_options,
+            variations,
+            transform: FontTransform::identity(),
+        }
+    }
+
+    pub fn get_alpha_glyph_format(&self) -> GlyphFormat {
+        if self.transform.is_identity() { GlyphFormat::Alpha } else { GlyphFormat::TransformedAlpha }
+    }
+
+    pub fn get_subpixel_glyph_format(&self) -> GlyphFormat {
+        if self.transform.is_identity() { GlyphFormat::Subpixel } else { GlyphFormat::TransformedSubpixel }
+    }
+
+    #[allow(dead_code)]
+    pub fn get_subpx_offset(&self, glyph: &GlyphKey) -> (f64, f64) {
+        match self.subpx_dir {
+            SubpixelDirection::None => (0.0, 0.0),
+            SubpixelDirection::Horizontal => (glyph.subpixel_offset.into(), 0.0),
+            SubpixelDirection::Vertical => (0.0, glyph.subpixel_offset.into()),
+        }
+    }
+
+    #[allow(dead_code)]
+    pub fn get_glyph_format(&self) -> GlyphFormat {
+        match self.render_mode {
+            FontRenderMode::Mono | FontRenderMode::Alpha => self.get_alpha_glyph_format(),
+            FontRenderMode::Subpixel => self.get_subpixel_glyph_format(),
+        }
+    }
+
+    #[allow(dead_code)]
+    pub fn get_extra_strikes(&self, x_scale: f64) -> usize {
+        if self.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) {
+            let mut bold_offset = self.size.to_f64_px() / 48.0;
+            if bold_offset < 1.0 {
+                bold_offset = 0.25 + 0.75 * bold_offset;
+            }
+            (bold_offset * x_scale).max(1.0).round() as usize
+        } else {
+            0
+        }
+    }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[allow(dead_code)]
+pub enum GlyphFormat {
+    Alpha,
+    TransformedAlpha,
+    Subpixel,
+    TransformedSubpixel,
+    Bitmap,
+    ColorBitmap,
+}
+
+impl GlyphFormat {
+    pub fn ignore_color(self) -> Self {
+        match self {
+            GlyphFormat::ColorBitmap => GlyphFormat::Bitmap,
+            _ => self,
+        }
+    }
+}
+
+pub struct RasterizedGlyph {
+    pub top: f32,
+    pub left: f32,
+    pub width: u32,
+    pub height: u32,
+    pub scale: f32,
+    pub format: GlyphFormat,
+    pub bytes: Vec<u8>,
+}
+
+pub struct FontContexts {
+    // These worker are mostly accessed from their corresponding worker threads.
+    // The goal is that there should be no noticeable contention on the mutexes.
+    worker_contexts: Vec<Mutex<FontContext>>,
+    // This worker should be accessed by threads that don't belong to the thread pool
+    // (in theory that's only the render backend thread so no contention expected either).
+    shared_context: Mutex<FontContext>,
+    #[cfg(feature = "pathfinder")]
+    pathfinder_context: Box<ThreadSafePathfinderFontContext>,
+    // Stored here as a convenience to get the current thread index.
+    #[allow(dead_code)]
+    workers: Arc<ThreadPool>,
+}
+
+impl FontContexts {
+
+    /// Get access to any particular font context.
+    ///
+    /// The id is ```Some(i)``` where i is an index between 0 and num_worker_contexts
+    /// for font contexts associated to the thread pool, and None for the shared
+    /// global font context for use outside of the thread pool.
+    pub fn lock_context(&self, id: Option<usize>) -> MutexGuard<FontContext> {
+        match id {
+            Some(index) => self.worker_contexts[index].lock().unwrap(),
+            None => self.shared_context.lock().unwrap(),
+        }
+    }
+
+    /// Get access to the font context usable outside of the thread pool.
+    pub fn lock_shared_context(&self) -> MutexGuard<FontContext> {
+        self.shared_context.lock().unwrap()
+    }
+
+    // number of contexts associated to workers
+    pub fn num_worker_contexts(&self) -> usize {
+        self.worker_contexts.len()
+    }
+}
+
+pub struct GlyphRasterizer {
+    #[allow(dead_code)]
+    workers: Arc<ThreadPool>,
+    font_contexts: Arc<FontContexts>,
+
+    // Maintain a set of glyphs that have been requested this
+    // frame. This ensures the glyph thread won't rasterize
+    // the same glyph more than once in a frame. This is required
+    // because the glyph cache hash table is not updated
+    // until the end of the frame when we wait for glyph requests
+    // to be resolved.
+    #[allow(dead_code)]
+    pending_glyphs: usize,
+
+    // Receives the rendered glyphs.
+    #[allow(dead_code)]
+    glyph_rx: Receiver<GlyphRasterJobs>,
+    #[allow(dead_code)]
+    glyph_tx: Sender<GlyphRasterJobs>,
+
+    // We defer removing fonts to the end of the frame so that:
+    // - this work is done outside of the critical path,
+    // - we don't have to worry about the ordering of events if a font is used on
+    //   a frame where it is used (although it seems unlikely).
+    fonts_to_remove: Vec<FontKey>,
+
+    #[allow(dead_code)]
+    next_gpu_glyph_cache_key: GpuGlyphCacheKey,
+}
+
+impl GlyphRasterizer {
+    pub fn new(workers: Arc<ThreadPool>) -> Result<Self, ResourceCacheError> {
+        let (glyph_tx, glyph_rx) = channel();
+
+        let num_workers = workers.current_num_threads();
+        let mut contexts = Vec::with_capacity(num_workers);
+
+        let shared_context = FontContext::new()?;
+
+        for _ in 0 .. num_workers {
+            contexts.push(Mutex::new(FontContext::new()?));
+        }
+
+        let font_context = FontContexts {
+                worker_contexts: contexts,
+                shared_context: Mutex::new(shared_context),
+                #[cfg(feature = "pathfinder")]
+                pathfinder_context: create_pathfinder_font_context()?,
+                workers: Arc::clone(&workers),
+        };
+
+        Ok(GlyphRasterizer {
+            font_contexts: Arc::new(font_context),
+            pending_glyphs: 0,
+            glyph_rx,
+            glyph_tx,
+            workers,
+            fonts_to_remove: Vec::new(),
+            next_gpu_glyph_cache_key: GpuGlyphCacheKey(0),
+        })
+    }
+
+    pub fn add_font(&mut self, font_key: FontKey, template: FontTemplate) {
+        let font_contexts = Arc::clone(&self.font_contexts);
+        // It's important to synchronously add the font for the shared context because
+        // we use it to check that fonts have been properly added when requesting glyphs.
+        font_contexts
+            .lock_shared_context()
+            .add_font(&font_key, &template);
+
+        // TODO: this locks each font context while adding the font data, probably not a big deal,
+        // but if there is contention on this lock we could easily have a queue of per-context
+        // operations to add and delete fonts, and have these queues lazily processed by each worker
+        // before rendering a glyph.
+        // We can also move this into a worker to free up some cycles in the calling (render backend)
+        // thread.
+        for i in 0 .. font_contexts.num_worker_contexts() {
+            font_contexts
+                .lock_context(Some(i))
+                .add_font(&font_key, &template);
+        }
+
+        #[cfg(feature = "pathfinder")]
+        self.add_font_to_pathfinder(&font_key, &template);
+    }
+
+    pub fn delete_font(&mut self, font_key: FontKey) {
+        self.fonts_to_remove.push(font_key);
+    }
+
+    pub fn prepare_font(&self, font: &mut FontInstance) {
+        FontContext::prepare_font(font);
+    }
+
+    pub fn get_glyph_dimensions(
+        &mut self,
+        font: &FontInstance,
+        glyph_key: &GlyphKey,
+    ) -> Option<GlyphDimensions> {
+        self.font_contexts
+            .lock_shared_context()
+            .get_glyph_dimensions(font, glyph_key)
+    }
+
+    pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+        self.font_contexts
+            .lock_shared_context()
+            .get_glyph_index(font_key, ch)
+    }
+
+    fn remove_dead_fonts(&mut self) {
+        if self.fonts_to_remove.is_empty() {
+            return
+        }
+
+        let font_contexts = Arc::clone(&self.font_contexts);
+        let fonts_to_remove = mem::replace(&mut self.fonts_to_remove, Vec::new());
+
+        self.workers.spawn(move || {
+            for font_key in &fonts_to_remove {
+                font_contexts.lock_shared_context().delete_font(font_key);
+            }
+            for i in 0 .. font_contexts.num_worker_contexts() {
+                let mut context = font_contexts.lock_context(Some(i));
+                for font_key in &fonts_to_remove {
+                    context.delete_font(font_key);
+                }
+            }
+        });
+    }
+
+    #[cfg(feature = "replay")]
+    pub fn reset(&mut self) {
+        //TODO: any signals need to be sent to the workers?
+        self.pending_glyphs = 0;
+        self.fonts_to_remove.clear();
+    }
+}
+
+trait AddFont {
+    fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate);
+}
+
+impl AddFont for FontContext {
+    fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate) {
+        match *template {
+            FontTemplate::Raw(ref bytes, index) => {
+                self.add_raw_font(font_key, bytes.clone(), index);
+            }
+            FontTemplate::Native(ref native_font_handle) => {
+                self.add_native_font(font_key, (*native_font_handle).clone());
+            }
+        }
+    }
+}
+
+#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct GlyphRequest {
+    pub key: GlyphKey,
+    pub font: FontInstance,
+}
+
+impl GlyphRequest {
+    pub fn new(font: &FontInstance, key: &GlyphKey) -> Self {
+        GlyphRequest {
+            key: key.clone(),
+            font: font.clone(),
+        }
+    }
+}
+
+#[allow(dead_code)]
+pub(in glyph_rasterizer) struct GlyphRasterJob {
+    key: GlyphKey,
+    result: GlyphRasterResult,
+}
+
+#[allow(dead_code)]
+pub enum GlyphRasterResult {
+    LoadFailed,
+    Bitmap(RasterizedGlyph),
+}
+
+#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct GpuGlyphCacheKey(pub u32);
+
+#[allow(dead_code)]
+struct GlyphRasterJobs {
+    font: FontInstance,
+    jobs: Vec<GlyphRasterJob>,
+}
+
+#[cfg(test)]
+mod test_glyph_rasterizer {
+    #[test]
+    fn rasterize_200_glyphs() {
+        // This test loads a font from disc, the renders 4 requests containing
+        // 50 glyphs each, deletes the font and waits for the result.
+
+        use rayon::ThreadPoolBuilder;
+        use std::fs::File;
+        use std::io::Read;
+        use texture_cache::TextureCache;
+        use glyph_cache::GlyphCache;
+        use gpu_cache::GpuCache;
+        use tiling::SpecialRenderPasses;
+        use api::DeviceIntSize;
+        use render_task::{RenderTaskCache, RenderTaskTree};
+        use profiler::TextureCacheProfileCounters;
+        use api::{FontKey, FontTemplate, FontRenderMode, GlyphKey,
+                  IdNamespace, LayoutPoint, ColorF, ColorU, SubpixelDirection};
+        use render_backend::FrameId;
+        use app_units::Au;
+        use thread_profiler::register_thread_with_profiler;
+        use std::sync::Arc;
+        use glyph_rasterizer::{GlyphRasterizer, FontInstance};
+
+        let worker = ThreadPoolBuilder::new()
+            .thread_name(|idx|{ format!("WRWorker#{}", idx) })
+            .start_handler(move |idx| {
+                register_thread_with_profiler(format!("WRWorker#{}", idx));
+            })
+            .build();
+        let workers = Arc::new(worker.unwrap());
+        let mut glyph_rasterizer = GlyphRasterizer::new(workers).unwrap();
+        let mut glyph_cache = GlyphCache::new();
+        let mut gpu_cache = GpuCache::new();
+        let mut texture_cache = TextureCache::new(2048);
+        let mut render_task_cache = RenderTaskCache::new();
+        let mut render_task_tree = RenderTaskTree::new(FrameId(0));
+        let mut special_render_passes = SpecialRenderPasses::new(&DeviceIntSize::new(1366, 768));
+
+        let mut font_file =
+            File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file");
+        let mut font_data = vec![];
+        font_file
+            .read_to_end(&mut font_data)
+            .expect("failed to read font file");
+
+        let font_key = FontKey::new(IdNamespace(0), 0);
+        glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0));
+
+        let font = FontInstance::new(
+            font_key,
+            Au::from_px(32),
+            ColorF::new(0.0, 0.0, 0.0, 1.0),
+            ColorU::new(0, 0, 0, 0),
+            FontRenderMode::Subpixel,
+            SubpixelDirection::Horizontal,
+            Default::default(),
+            None,
+            Vec::new(),
+        );
+
+        let mut glyph_keys = Vec::with_capacity(200);
+        for i in 0 .. 200 {
+            glyph_keys.push(GlyphKey::new(
+                i,
+                LayoutPoint::zero(),
+                font.render_mode,
+                font.subpx_dir,
+            ));
+        }
+
+        for i in 0 .. 4 {
+            glyph_rasterizer.request_glyphs(
+                &mut glyph_cache,
+                font.clone(),
+                &glyph_keys[(50 * i) .. (50 * (i + 1))],
+                &mut texture_cache,
+                &mut gpu_cache,
+                &mut render_task_cache,
+                &mut render_task_tree,
+                &mut special_render_passes,
+            );
+        }
+
+        glyph_rasterizer.delete_font(font_key);
+
+        glyph_rasterizer.resolve_glyphs(
+            &mut glyph_cache,
+            &mut TextureCache::new(4096),
+            &mut gpu_cache,
+            &mut render_task_cache,
+            &mut render_task_tree,
+            &mut TextureCacheProfileCounters::new(),
+        );
+    }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/glyph_rasterizer/no_pathfinder.rs
@@ -0,0 +1,205 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Module only available when pathfinder is deactivated when webrender is
+//! compiled regularly (i.e. any configuration without feature = "pathfinder")
+
+use api::{GlyphKey, ImageData, ImageDescriptor, ImageFormat};
+use device::TextureFilter;
+use gpu_types::UvRectKind;
+use rayon::prelude::*;
+use std::sync::{Arc, MutexGuard};
+use platform::font::FontContext;
+use glyph_rasterizer::{FontInstance, FontContexts, GlyphRasterizer, GlyphRasterJob, GlyphRasterJobs, GlyphRasterResult};
+use glyph_cache::{GlyphCache, CachedGlyphInfo, GlyphCacheEntry};
+use texture_cache::{TextureCache, TextureCacheHandle};
+use gpu_cache::GpuCache;
+use render_task::{RenderTaskTree, RenderTaskCache};
+use tiling::SpecialRenderPasses;
+use profiler::TextureCacheProfileCounters;
+use std::collections::hash_map::Entry;
+
+impl FontContexts {
+    /// Get access to the font context associated to the current thread.
+    pub fn lock_current_context(&self) -> MutexGuard<FontContext> {
+        let id = self.current_worker_id();
+        self.lock_context(id)
+    }
+
+    pub(in super) fn current_worker_id(&self) -> Option<usize> {
+        self.workers.current_thread_index()
+    }
+}
+
+impl GlyphRasterizer {
+
+    pub fn request_glyphs(
+        &mut self,
+        glyph_cache: &mut GlyphCache,
+        font: FontInstance,
+        glyph_keys: &[GlyphKey],
+        texture_cache: &mut TextureCache,
+        gpu_cache: &mut GpuCache,
+        _: &mut RenderTaskCache,
+        _: &mut RenderTaskTree,
+        _: &mut SpecialRenderPasses,
+    ) {
+        assert!(
+            self.font_contexts
+                .lock_shared_context()
+                .has_font(&font.font_key)
+        );
+        let mut new_glyphs = Vec::new();
+
+        let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font.clone());
+
+        // select glyphs that have not been requested yet.
+        for key in glyph_keys {
+            match glyph_key_cache.entry(key.clone()) {
+                Entry::Occupied(mut entry) => {
+                    let value = entry.into_mut();
+                    match *value {
+                        GlyphCacheEntry::Cached(ref glyph) => {
+                            // Skip the glyph if it is already has a valid texture cache handle.
+                            if !texture_cache.request(&glyph.texture_cache_handle, gpu_cache) {
+                                continue;
+                            }
+                        }
+                        // Otherwise, skip the entry if it is blank or pending.
+                        GlyphCacheEntry::Blank | GlyphCacheEntry::Pending => continue,
+                    }
+
+                    // This case gets hit when we already rasterized the glyph, but the
+                    // glyph has been evicted from the texture cache. Just force it to
+                    // pending so it gets rematerialized.
+                    *value = GlyphCacheEntry::Pending;
+                    new_glyphs.push((*key).clone());
+                }
+                Entry::Vacant(entry) => {
+                    // This is the first time we've seen the glyph, so mark it as pending.
+                    entry.insert(GlyphCacheEntry::Pending);
+                    new_glyphs.push((*key).clone());
+                }
+            }
+        }
+
+        if new_glyphs.is_empty() {
+            return;
+        }
+
+        self.pending_glyphs += 1;
+
+        self.request_glyphs_from_backend(font, new_glyphs);
+    }
+
+    pub(in super) fn request_glyphs_from_backend(&mut self, font: FontInstance, glyphs: Vec<GlyphKey>) {
+        let font_contexts = Arc::clone(&self.font_contexts);
+        let glyph_tx = self.glyph_tx.clone();
+
+        // spawn an async task to get off of the render backend thread as early as
+        // possible and in that task use rayon's fork join dispatch to rasterize the
+        // glyphs in the thread pool.
+        self.workers.spawn(move || {
+            let jobs = glyphs
+                .par_iter()
+                .map(|key: &GlyphKey| {
+                    profile_scope!("glyph-raster");
+                    let mut context = font_contexts.lock_current_context();
+                    let job = GlyphRasterJob {
+                        key: key.clone(),
+                        result: context.rasterize_glyph(&font, key),
+                    };
+
+                    // Sanity check.
+                    if let GlyphRasterResult::Bitmap(ref glyph) = job.result {
+                        let bpp = 4; // We always render glyphs in 32 bits RGBA format.
+                        assert_eq!(
+                            glyph.bytes.len(),
+                            bpp * (glyph.width * glyph.height) as usize
+                        );
+                    }
+
+                    job
+                })
+                .collect();
+
+            glyph_tx.send(GlyphRasterJobs { font, jobs }).unwrap();
+        });
+    }
+
+    pub fn resolve_glyphs(
+        &mut self,
+        glyph_cache: &mut GlyphCache,
+        texture_cache: &mut TextureCache,
+        gpu_cache: &mut GpuCache,
+        _: &mut RenderTaskCache,
+        _: &mut RenderTaskTree,
+        _: &mut TextureCacheProfileCounters,
+    ) {
+        // Pull rasterized glyphs from the queue and update the caches.
+        while self.pending_glyphs > 0 {
+            self.pending_glyphs -= 1;
+
+            // TODO: rather than blocking until all pending glyphs are available
+            // we could try_recv and steal work from the thread pool to take advantage
+            // of the fact that this thread is alive and we avoid the added latency
+            // of blocking it.
+            let GlyphRasterJobs { font, mut jobs } = self.glyph_rx
+                .recv()
+                .expect("BUG: Should be glyphs pending!");
+
+            // Ensure that the glyphs are always processed in the same
+            // order for a given text run (since iterating a hash set doesn't
+            // guarantee order). This can show up as very small float inaccuracy
+            // differences in rasterizers due to the different coordinates
+            // that text runs get associated with by the texture cache allocator.
+            jobs.sort_by(|a, b| a.key.cmp(&b.key));
+
+            let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font);
+
+            for GlyphRasterJob { key, result } in jobs {
+                let glyph_info = match result {
+                    GlyphRasterResult::LoadFailed => GlyphCacheEntry::Blank,
+                    GlyphRasterResult::Bitmap(ref glyph) if glyph.width == 0 ||
+                                                            glyph.height == 0 => {
+                        GlyphCacheEntry::Blank
+                    }
+                    GlyphRasterResult::Bitmap(glyph) => {
+                        assert_eq!((glyph.left.fract(), glyph.top.fract()), (0.0, 0.0));
+                        let mut texture_cache_handle = TextureCacheHandle::new();
+                        texture_cache.request(&texture_cache_handle, gpu_cache);
+                        texture_cache.update(
+                            &mut texture_cache_handle,
+                            ImageDescriptor {
+                                width: glyph.width,
+                                height: glyph.height,
+                                stride: None,
+                                format: ImageFormat::BGRA8,
+                                is_opaque: false,
+                                allow_mipmaps: false,
+                                offset: 0,
+                            },
+                            TextureFilter::Linear,
+                            Some(ImageData::Raw(Arc::new(glyph.bytes))),
+                            [glyph.left, -glyph.top, glyph.scale],
+                            None,
+                            gpu_cache,
+                            Some(glyph_key_cache.eviction_notice()),
+                            UvRectKind::Rect,
+                        );
+                        GlyphCacheEntry::Cached(CachedGlyphInfo {
+                            texture_cache_handle,
+                            format: glyph.format,
+                        })
+                    }
+                };
+                glyph_key_cache.insert(key, glyph_info);
+            }
+        }
+
+        // Now that we are done with the critical path (rendering the glyphs),
+        // we can schedule removing the fonts if needed.
+        self.remove_dead_fonts();
+    }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/glyph_rasterizer/pathfinder.rs
@@ -0,0 +1,314 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Module only available when pathfinder is activated
+
+use api::{DeviceIntPoint, DeviceIntSize, DevicePixel, FontRenderMode, FontKey, FontTemplate, GlyphKey, NativeFontHandle};
+use euclid::{TypedPoint2D, TypedSize2D, TypedVector2D};
+use pathfinder_font_renderer;
+use pathfinder_partitioner::mesh::Mesh as PathfinderMesh;
+use pathfinder_path_utils::cubic_to_quadratic::CubicToQuadraticTransformer;
+use render_task::{RenderTask, RenderTaskTree, RenderTaskCache, RenderTaskCacheKey, RenderTaskCacheEntryHandle,
+                  RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
+use resource_cache::CacheItem;
+use std::ops::Deref;
+use std::sync::{Arc, Mutex, MutexGuard};
+use tiling::{RenderTargetKind, SpecialRenderPasses};
+use glyph_rasterizer::AddFont;
+use internal_types::ResourceCacheError;
+use glyph_cache::{GlyphCache, GlyphCacheEntry, CachedGlyphInfo};
+use std::collections::hash_map::Entry;
+use std::f32;
+use glyph_rasterizer::{FontInstance, GlyphRasterizer, GlyphFormat, FontContexts};
+use texture_cache::TextureCache;
+use gpu_cache::GpuCache;
+use profiler::TextureCacheProfileCounters;
+
+/// Should match macOS 10.13 High Sierra.
+///
+/// We multiply by sqrt(2) to compensate for the fact that dilation amounts are relative to the
+/// pixel square on macOS and relative to the vertex normal in Pathfinder.
+const STEM_DARKENING_FACTOR_X: f32 = 0.0121 * f32::consts::SQRT_2;
+const STEM_DARKENING_FACTOR_Y: f32 = 0.0121 * 1.25 * f32::consts::SQRT_2;
+
+/// Likewise, should match macOS 10.13 High Sierra.
+const MAX_STEM_DARKENING_AMOUNT: f32 = 0.3 * f32::consts::SQRT_2;
+
+const CUBIC_TO_QUADRATIC_APPROX_TOLERANCE: f32 = 0.01;
+
+type PathfinderFontContext = pathfinder_font_renderer::FontContext<FontKey>;
+
+impl AddFont for PathfinderFontContext {
+    fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate) {
+        match *template {
+            FontTemplate::Raw(ref bytes, index) => {
+                drop(self.add_font_from_memory(&font_key, bytes.clone(), index))
+            }
+            FontTemplate::Native(ref native_font_handle) => {
+                drop(self.add_native_font(&font_key, NativeFontHandleWrapper(native_font_handle)))
+            }
+        }
+    }
+}
+
+pub(in super) fn create_pathfinder_font_context()
+-> Result<Box<ThreadSafePathfinderFontContext>, ResourceCacheError>
+{
+    match PathfinderFontContext::new() {
+        Ok(context) => Ok(Box::new(ThreadSafePathfinderFontContext(Mutex::new(context)))),
+        Err(_) => {
+            let msg = "Failed to create the Pathfinder font context!".to_owned();
+            Err(ResourceCacheError::new(msg))
+        }
+    }
+}
+
+pub struct ThreadSafePathfinderFontContext(Mutex<PathfinderFontContext>);
+
+impl Deref for ThreadSafePathfinderFontContext {
+    type Target = Mutex<PathfinderFontContext>;
+
+    fn deref(&self) -> &Mutex<PathfinderFontContext> {
+        &self.0
+    }
+}
+
+/// 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,
+        glyph_key: &GlyphKey,
+        font: &FontInstance,
+        glyph_cache: &GlyphCache,
+        texture_cache: &TextureCache,
+        render_task_cache: &RenderTaskCache)
+    -> Option<(CacheItem, GlyphFormat)>
+    {
+        let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font(font);
+        let render_task_cache_key = match *glyph_key_cache.get(glyph_key) {
+            GlyphCacheEntry::Cached(ref cached_glyph) => {
+                (*cached_glyph).render_task_cache_key.clone()
+            }
+            GlyphCacheEntry::Blank => return None,
+            GlyphCacheEntry::Pending => {
+                panic!("GlyphRasterizer::get_cache_item_for_glyph(): Glyph should have been \
+                        cached by now!")
+            }
+        };
+        let cache_item = render_task_cache.get_cache_item_for_render_task(texture_cache,
+                                                                          &render_task_cache_key);
+        Some((cache_item, font.get_glyph_format()))
+    }
+
+    pub(in super) fn request_glyph_from_pathfinder_if_necessary(
+        &mut self,
+        glyph_key: &GlyphKey,
+        font: &FontInstance,
+        cached_glyph_info: CachedGlyphInfo,
+        texture_cache: &mut TextureCache,
+        gpu_cache: &mut GpuCache,
+        render_task_cache: &mut RenderTaskCache,
+        render_task_tree: &mut RenderTaskTree,
+        render_passes: &mut SpecialRenderPasses)
+    -> Result<(RenderTaskCacheEntryHandle,GlyphFormat), ()>
+    {
+        let mut pathfinder_font_context = self.font_contexts.lock_pathfinder_context();
+        let render_task_cache_key = cached_glyph_info.render_task_cache_key;
+        let (glyph_origin, glyph_size) = (cached_glyph_info.origin, render_task_cache_key.size);
+        let user_data = [glyph_origin.x as f32, (glyph_origin.y - glyph_size.height) as f32, 1.0];
+        let handle = try!(render_task_cache.request_render_task(render_task_cache_key,
+                                                                texture_cache,
+                                                                gpu_cache,
+                                                                render_task_tree,
+                                                                Some(user_data),
+                                                                false,
+                                                                |render_tasks| {
+            // TODO(pcwalton): Non-subpixel font render mode.
+            request_render_task_from_pathfinder(glyph_key,
+                                                font,
+                                                &glyph_origin,
+                                                &glyph_size,
+                                                &mut *pathfinder_font_context,
+                                                font.render_mode,
+                                                render_tasks,
+                                                render_passes)
+        }));
+        Ok((handle, font.get_glyph_format()))
+    }
+
+    pub fn request_glyphs(
+        &mut self,
+        glyph_cache: &mut GlyphCache,
+        font: FontInstance,
+        glyph_keys: &[GlyphKey],
+        texture_cache: &mut TextureCache,
+        gpu_cache: &mut GpuCache,
+        render_task_cache: &mut RenderTaskCache,
+        render_task_tree: &mut RenderTaskTree,
+        render_passes: &mut SpecialRenderPasses,
+    ) {
+        debug_assert!(self.font_contexts.lock_shared_context().has_font(&font.font_key));
+
+        let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font.clone());
+
+        // select glyphs that have not been requested yet.
+        for glyph_key in glyph_keys {
+            let mut cached_glyph_info = None;
+            match glyph_key_cache.entry(glyph_key.clone()) {
+                Entry::Occupied(mut entry) => {
+                    let value = entry.into_mut();
+                    match *value {
+                        GlyphCacheEntry::Cached(ref glyph_info) => {
+                            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();
+
+                    let pathfinder_font_instance = pathfinder_font_renderer::FontInstance {
+                        font_key: font.font_key.clone(),
+                        size: font.size,
+                    };
+
+                    let pathfinder_subpixel_offset =
+                        pathfinder_font_renderer::SubpixelOffset(glyph_key.subpixel_offset 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,
+                        };
+
+                    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),
+                        },
+                        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,
+                };
+
+            glyph_key_cache.insert(glyph_key.clone(), handle);
+        }
+    }
+
+    pub fn resolve_glyphs(
+        &mut self,
+        _: &mut GlyphCache,
+        _: &mut TextureCache,
+        _: &mut GpuCache,
+        _: &mut RenderTaskCache,
+        _: &mut RenderTaskTree,
+        _: &mut TextureCacheProfileCounters,
+    ) {
+        self.remove_dead_fonts();
+    }
+}
+
+impl FontContexts {
+    pub fn lock_pathfinder_context(&self) -> MutexGuard<PathfinderFontContext> {
+        self.pathfinder_context.lock().unwrap()
+    }
+}
+
+fn compute_embolden_amount(ppem: f32) -> TypedVector2D<f32, DevicePixel> {
+    TypedVector2D::new(f32::min(ppem * STEM_DARKENING_FACTOR_X, MAX_STEM_DARKENING_AMOUNT),
+                       f32::min(ppem * STEM_DARKENING_FACTOR_Y, MAX_STEM_DARKENING_AMOUNT))
+}
+
+fn request_render_task_from_pathfinder(glyph_key: &GlyphKey,
+                                       font: &FontInstance,
+                                       glyph_origin: &DeviceIntPoint,
+                                       glyph_size: &DeviceIntSize,
+                                       font_context: &mut PathfinderFontContext,
+                                       render_mode: FontRenderMode,
+                                       render_tasks: &mut RenderTaskTree,
+                                       render_passes: &mut SpecialRenderPasses)
+                                       -> Result<RenderTaskId, ()> {
+    let pathfinder_font_instance = pathfinder_font_renderer::FontInstance {
+        font_key: font.font_key.clone(),
+        size: font.size,
+    };
+
+    let pathfinder_subpixel_offset =
+        pathfinder_font_renderer::SubpixelOffset(glyph_key.subpixel_offset as u8);
+    let glyph_subpixel_offset: f64 = glyph_key.subpixel_offset.into();
+    let pathfinder_glyph_key = pathfinder_font_renderer::GlyphKey::new(glyph_key.index,
+                                                                       pathfinder_subpixel_offset);
+
+    // TODO(pcwalton): Fall back to CPU rendering if Pathfinder fails to collect the outline.
+    let mut mesh = PathfinderMesh::new();
+    let outline = try!(font_context.glyph_outline(&pathfinder_font_instance,
+                                                  &pathfinder_glyph_key));
+    let tolerance = CUBIC_TO_QUADRATIC_APPROX_TOLERANCE;
+    mesh.push_stencil_segments(CubicToQuadraticTransformer::new(outline.iter(), tolerance));
+    mesh.push_stencil_normals(CubicToQuadraticTransformer::new(outline.iter(), tolerance));
+
+    // FIXME(pcwalton): Support vertical subpixel offsets.
+    // FIXME(pcwalton): Embolden amount should be 0 on macOS if "Use LCD font
+    // smoothing" is unchecked in System Preferences.
+
+    let subpixel_offset = TypedPoint2D::new(glyph_subpixel_offset as f32, 0.0);
+    let embolden_amount = compute_embolden_amount(font.size.to_f32_px());
+
+    let location = RenderTaskLocation::Dynamic(None, Some(*glyph_size));
+    let glyph_render_task = RenderTask::new_glyph(location,
+                                                  mesh,
+                                                  &glyph_origin,
+                                                  &subpixel_offset,
+                                                  font.render_mode,
+                                                  &embolden_amount);
+
+    let root_task_id = render_tasks.add(glyph_render_task);
+    let render_pass = match render_mode {
+        FontRenderMode::Mono | FontRenderMode::Alpha => &mut render_passes.alpha_glyph_pass,
+        FontRenderMode::Subpixel => &mut render_passes.color_glyph_pass,
+    };
+    render_pass.add_render_task(root_task_id, *glyph_size, RenderTargetKind::Color);
+
+    Ok(root_task_id)
+}
+
+pub struct NativeFontHandleWrapper<'a>(pub &'a NativeFontHandle);
\ No newline at end of file
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -22,19 +22,16 @@ pub struct HitTestClipScrollNode {
     /// for the purposes of a hit test.
     regions: Vec<HitTestRegion>,
 
     /// World transform for content transformed by this node.
     world_content_transform: LayoutToWorldFastTransform,
 
     /// World viewport transform for content transformed by this node.
     world_viewport_transform: LayoutToWorldFastTransform,
-
-    /// Origin of the viewport of the node, used to calculate node-relative positions.
-    node_origin: LayoutPoint,
 }
 
 /// A description of a clip chain in the HitTester. This is used to describe
 /// hierarchical clip scroll nodes as well as ClipChains, so that they can be
 /// handled the same way during hit testing. Once we represent all ClipChains
 /// using ClipChainDescriptors, we can get rid of this and just use the
 /// ClipChainDescriptor here.
 #[derive(Clone)]
@@ -136,17 +133,16 @@ impl HitTester {
             // node.
             self.pipeline_root_nodes.entry(node.pipeline_id).or_insert(index);
 
             self.nodes.push(HitTestClipScrollNode {
                 pipeline_id: node.pipeline_id,
                 regions: get_regions_for_clip_scroll_node(node, clip_store),
                 world_content_transform: node.world_content_transform,
                 world_viewport_transform: node.world_viewport_transform,
-                node_origin: node.local_viewport_rect.origin,
             });
 
             if let NodeType::Clip { clip_chain_index, .. } = node.node_type {
               let clip_chain = self.clip_chains.get_mut(clip_chain_index.0).unwrap();
               clip_chain.parent =
                   clip_scroll_tree.get_clip_chain(clip_chain_index).parent_index;
               clip_chain.clips = vec![index];
             }
@@ -161,73 +157,105 @@ impl HitTester {
 
     fn is_point_clipped_in_for_clip_chain(
         &self,
         point: WorldPoint,
         clip_chain_index: ClipChainIndex,
         test: &mut HitTest
     ) -> bool {
         if let Some(result) = test.get_from_clip_chain_cache(clip_chain_index) {
-            return result;
+            return result == ClippedIn::ClippedIn;
         }
 
         let descriptor = &self.clip_chains[clip_chain_index.0];
         let parent_clipped_in = match descriptor.parent {
             None => true,
             Some(parent) => self.is_point_clipped_in_for_clip_chain(point, parent, test),
         };
 
         if !parent_clipped_in {
-            test.set_in_clip_chain_cache(clip_chain_index, false);
+            test.set_in_clip_chain_cache(clip_chain_index, ClippedIn::NotClippedIn);
             return false;
         }
 
         for clip_node_index in &descriptor.clips {
             if !self.is_point_clipped_in_for_node(point, *clip_node_index, test) {
-                test.set_in_clip_chain_cache(clip_chain_index, false);
+                test.set_in_clip_chain_cache(clip_chain_index, ClippedIn::NotClippedIn);
                 return false;
             }
         }
 
-        test.set_in_clip_chain_cache(clip_chain_index, true);
+        test.set_in_clip_chain_cache(clip_chain_index, ClippedIn::ClippedIn);
         true
     }
 
     fn is_point_clipped_in_for_node(
         &self,
         point: WorldPoint,
         node_index: ClipScrollNodeIndex,
         test: &mut HitTest
     ) -> bool {
-        if let Some(point) = test.node_cache.get(&node_index) {
-            return point.is_some();
+        if let Some(clipped_in) = test.node_cache.get(&node_index) {
+            return *clipped_in == ClippedIn::ClippedIn;
         }
 
         let node = &self.nodes[node_index.0];
         let transform = node.world_viewport_transform;
         let transformed_point = match transform.inverse() {
             Some(inverted) => inverted.transform_point2d(&point),
             None => {
-                test.node_cache.insert(node_index, None);
+                test.node_cache.insert(node_index, ClippedIn::NotClippedIn);
                 return false;
             }
         };
 
-        let point_in_layer = transformed_point - node.node_origin.to_vector();
         for region in &node.regions {
             if !region.contains(&transformed_point) {
-                test.node_cache.insert(node_index, None);
+                test.node_cache.insert(node_index, ClippedIn::NotClippedIn);
                 return false;
             }
         }
 
-        test.node_cache.insert(node_index, Some(point_in_layer));
+        test.node_cache.insert(node_index, ClippedIn::ClippedIn);
         true
     }
 
+    pub fn find_node_under_point(&self, mut test: HitTest) -> Option<ClipScrollNodeIndex> {
+        let point = test.get_absolute_point(self);
+
+        for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() {
+            let scroll_node_id = clip_and_scroll.scroll_node_id;
+            let scroll_node = &self.nodes[scroll_node_id.0];
+            let transform = scroll_node.world_content_transform;
+            let point_in_layer = match transform.inverse() {
+                Some(inverted) => inverted.transform_point2d(&point),
+                None => continue,
+            };
+
+            let mut clipped_in = false;
+            for item in items.iter().rev() {
+                if !item.rect.contains(&point_in_layer) ||
+                    !item.clip_rect.contains(&point_in_layer) {
+                    continue;
+                }
+
+                let clip_chain_index = clip_and_scroll.clip_chain_index;
+                clipped_in |=
+                    self.is_point_clipped_in_for_clip_chain(point, clip_chain_index, &mut test);
+                if !clipped_in {
+                    break;
+                }
+
+                return Some(scroll_node_id);
+            }
+        }
+
+        None
+    }
+
     pub fn hit_test(&self, mut test: HitTest) -> HitTestResult {
         let point = test.get_absolute_point(self);
 
         let mut result = HitTestResult::default();
         for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() {
             let scroll_node_id = clip_and_scroll.scroll_node_id;
             let scroll_node = &self.nodes[scroll_node_id.0];
             let pipeline_id = scroll_node.pipeline_id;
@@ -252,37 +280,33 @@ impl HitTester {
 
                 let clip_chain_index = clip_and_scroll.clip_chain_index;
                 clipped_in = clipped_in ||
                     self.is_point_clipped_in_for_clip_chain(point, clip_chain_index, &mut test);
                 if !clipped_in {
                     break;
                 }
 
-                // We need to trigger a lookup against the root reference frame here, because
-                // items that are clipped by clip chains won't test against that part of the
-                // hierarchy. If we don't have a valid point for this test, we are likely
-                // in a situation where the reference frame has an univertible transform, but the
-                // item's clip does not.
-                let root_node_index = self.pipeline_root_nodes[&pipeline_id];
-                if !self.is_point_clipped_in_for_node(point, root_node_index, &mut test) {
-                    continue;
-                }
-                let point_in_viewport = match test.node_cache[&root_node_index] {
-                    Some(point) => point,
-                    None => continue,
-                };
-
                 // Don't hit items with backface-visibility:hidden if they are facing the back.
                 if !item.is_backface_visible {
                     if *facing_backwards.get_or_insert_with(|| transform.is_backface_visible()) {
                         continue;
                     }
                 }
 
+                // We need to calculate the position of the test point relative to the origin of
+                // the pipeline of the hit item. If we cannot get a transformed point, we are
+                // in a situation with an uninvertible transformation so we should just skip this
+                // result.
+                let root_node = &self.nodes[self.pipeline_root_nodes[&pipeline_id].0];
+                let point_in_viewport = match root_node.world_viewport_transform.inverse() {
+                    Some(inverted) => inverted.transform_point2d(&point),
+                    None => continue,
+                };
+
                 result.items.push(HitTestItem {
                     pipeline: pipeline_id,
                     tag: item.tag,
                     point_in_viewport,
                     point_relative_to_item: point_in_layer - item.rect.origin.to_vector(),
                 });
                 if !test.flags.contains(HitTestFlags::FIND_ALL) {
                     return result;
@@ -318,22 +342,28 @@ fn get_regions_for_clip_scroll_node(
             ClipSource::LineDecoration(_) |
             ClipSource::BoxShadow(_) => {
                 unreachable!("Didn't expect to hit test against BorderCorner / BoxShadow / LineDecoration");
             }
         }
     }).collect()
 }
 
+#[derive(Clone, Copy, PartialEq)]
+enum ClippedIn {
+    ClippedIn,
+    NotClippedIn,
+}
+
 pub struct HitTest {
     pipeline_id: Option<PipelineId>,
     point: WorldPoint,
     flags: HitTestFlags,
-    node_cache: FastHashMap<ClipScrollNodeIndex, Option<LayoutPoint>>,
-    clip_chain_cache: Vec<Option<bool>>,
+    node_cache: FastHashMap<ClipScrollNodeIndex, ClippedIn>,
+    clip_chain_cache: Vec<Option<ClippedIn>>,
 }
 
 impl HitTest {
     pub fn new(
         pipeline_id: Option<PipelineId>,
         point: WorldPoint,
         flags: HitTestFlags,
     ) -> HitTest {
@@ -341,32 +371,32 @@ impl HitTest {
             pipeline_id,
             point,
             flags,
             node_cache: FastHashMap::default(),
             clip_chain_cache: Vec::new(),
         }
     }
 
-    pub fn get_from_clip_chain_cache(&mut self, index: ClipChainIndex) -> Option<bool> {
+    fn get_from_clip_chain_cache(&mut self, index: ClipChainIndex) -> Option<ClippedIn> {
         if index.0 >= self.clip_chain_cache.len() {
             None
         } else {
             self.clip_chain_cache[index.0]
         }
     }
 
-    pub fn set_in_clip_chain_cache(&mut self, index: ClipChainIndex, value: bool) {
+    fn set_in_clip_chain_cache(&mut self, index: ClipChainIndex, value: ClippedIn) {
         if index.0 >= self.clip_chain_cache.len() {
             self.clip_chain_cache.resize(index.0 + 1, None);
         }
         self.clip_chain_cache[index.0] = Some(value);
     }
 
-    pub fn get_absolute_point(&self, hit_tester: &HitTester) -> WorldPoint {
+    fn get_absolute_point(&self, hit_tester: &HitTester) -> WorldPoint {
         if !self.flags.contains(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT) {
             return self.point;
         }
 
         let point =  &LayoutPoint::new(self.point.x, self.point.y);
         self.pipeline_id.map(|id|
             hit_tester.get_pipeline_root(id).world_viewport_transform.transform_point2d(point)
         ).unwrap_or_else(|| WorldPoint::new(self.point.x, self.point.y))
--- a/gfx/webrender/src/image.rs
+++ b/gfx/webrender/src/image.rs
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{TileOffset, LayoutRect, LayoutSize, LayoutVector2D, DeviceUintSize};
-use euclid::rect;
+use api::{TileOffset, LayoutRect, LayoutSize, LayoutPoint, DeviceUintSize};
+use euclid::vec2;
+use prim_store::EdgeAaSegmentMask;
 
 /// If repetitions are far enough apart that only one is within
 /// the primitive rect, then we can simplify the parameters and
 /// treat the primitive as not repeated.
 /// This can let us avoid unnecessary work later to handle some
 /// of the parameters.
 pub fn simplify_repeated_primitive(
     stretch_size: &LayoutSize,
@@ -22,112 +23,87 @@ pub fn simplify_repeated_primitive(
         prim_rect.size.width = f32::min(prim_rect.size.width, stretch_size.width);
     }
     if stride.height >= prim_rect.size.height {
         tile_spacing.height = 0.0;
         prim_rect.size.height = f32::min(prim_rect.size.height, stretch_size.height);
     }
 }
 
-pub struct DecomposedTile {
-    pub rect: LayoutRect,
-    pub stretch_size: LayoutSize,
-    pub tile_offset: TileOffset,
-}
+pub fn for_each_repetition(
+    prim_rect: &LayoutRect,
+    visible_rect: &LayoutRect,
+    stride: &LayoutSize,
+    callback: &mut FnMut(&LayoutPoint, EdgeAaSegmentMask),
+) {
+    assert!(stride.width > 0.0);
+    assert!(stride.height > 0.0);
+
+    let visible_rect = match prim_rect.intersection(&visible_rect) {
+       Some(rect) => rect,
+       None => return,
+    };
 
-pub struct TiledImageInfo {
-    /// The bounds of the item in layout space.
-    pub rect: LayoutRect,
-    /// The space between each repeated pattern in layout space.
-    pub tile_spacing: LayoutSize,
-    /// The size in layout space of each repetition of the image.
-    pub stretch_size: LayoutSize,
+    let nx = if visible_rect.origin.x > prim_rect.origin.x {
+        f32::floor((visible_rect.origin.x - prim_rect.origin.x) / stride.width)
+    } else {
+        0.0
+    };
 
-    /// The size the image occupies in the cache in device space.
-    pub device_image_size: DeviceUintSize,
-    /// The size of the tiles in the cache in device pixels.
-    pub device_tile_size: u32,
-}
+    let ny = if visible_rect.origin.y > prim_rect.origin.y {
+        f32::floor((visible_rect.origin.y - prim_rect.origin.y) / stride.height)
+    } else {
+        0.0
+    };
+
+    let x0 = prim_rect.origin.x + nx * stride.width;
+    let y0 = prim_rect.origin.y + ny * stride.height;
+
+    let mut p = LayoutPoint::new(x0, y0);
 
-/// Decomposes an image that is repeated into an image per individual repetition.
-/// We need to do this when we are unable to perform the repetition in the shader,
-/// for example if the image is tiled.
-///
-/// In all of the "decompose" methods below, we independently handle horizontal and vertical
-/// decomposition. This lets us generate the minimum amount of primitives by, for example,
-/// decomposing the repetition horizontally while repeating vertically in the shader (for
-/// an image where the width is too bug but the height is not).
-///
-/// decompose_image and decompose_row handle image repetitions while decompose_cache_tiles
-/// takes care of the decomposition required by the internal tiling of the image in the cache.
-///
-/// Note that the term tiling is overloaded: There is the tiling we get from repeating images
-/// in layout space, and the tiling that we do in the texture cache (to avoid hitting texture
-/// size limits). The latter is referred to as "device" tiling here to disambiguate.
-pub fn decompose_image(info: &TiledImageInfo, callback: &mut FnMut(&DecomposedTile)) {
+    let x_most = visible_rect.max_x();
+    let y_most = visible_rect.max_y();
+
+    let x_count = f32::ceil((x_most - x0) / stride.width) as i32;
+    let y_count = f32::ceil((y_most - y0) / stride.height) as i32;
 
-    let no_vertical_tiling = info.device_image_size.height <= info.device_tile_size;
-    let no_vertical_spacing = info.tile_spacing.height == 0.0;
+    for y in 0..y_count {
+        let mut row_flags = EdgeAaSegmentMask::empty();
+        if y == 0 {
+            row_flags |= EdgeAaSegmentMask::TOP;
+        }
+        if y == y_count - 1 {
+            row_flags |= EdgeAaSegmentMask::BOTTOM;
+        }
 
-    if no_vertical_tiling && no_vertical_spacing {
-        decompose_row(&info.rect, info, callback);
-        return;
-    }
-
-    // Decompose each vertical repetition into rows.
-    let layout_stride = info.stretch_size.height + info.tile_spacing.height;
-    let num_repetitions = (info.rect.size.height / layout_stride).ceil() as u32;
+        for x in 0..x_count {
+            let mut edge_flags = row_flags;
+            if x == 0 {
+                edge_flags |= EdgeAaSegmentMask::LEFT;
+            }
+            if x == x_count - 1 {
+                edge_flags |= EdgeAaSegmentMask::RIGHT;
+            }
 
-    for i in 0 .. num_repetitions {
-        let row_rect = rect(
-            info.rect.origin.x,
-            info.rect.origin.y + (i as f32) * layout_stride,
-            info.rect.size.width,
-            info.stretch_size.height,
-        ).intersection(&info.rect);
+            callback(&p, edge_flags);
 
-        if let Some(row_rect) = row_rect {
-            decompose_row(&row_rect, info, callback);
+            p.x += stride.width;
         }
+
+        p.x = x0;
+        p.y += stride.height;
     }
 }
 
-
-fn decompose_row(item_rect: &LayoutRect, info: &TiledImageInfo, callback: &mut FnMut(&DecomposedTile)) {
-
-    let no_horizontal_tiling = info.device_image_size.width <= info.device_tile_size;
-    let no_horizontal_spacing = info.tile_spacing.width == 0.0;
-
-    if no_horizontal_tiling && no_horizontal_spacing {
-        decompose_cache_tiles(item_rect, info, callback);
-        return;
-    }
-
-    // Decompose each horizontal repetition.
-    let layout_stride = info.stretch_size.width + info.tile_spacing.width;
-    let num_repetitions = (item_rect.size.width / layout_stride).ceil() as u32;
-
-    for i in 0 .. num_repetitions {
-        let decomposed_rect = rect(
-            item_rect.origin.x + (i as f32) * layout_stride,
-            item_rect.origin.y,
-            info.stretch_size.width,
-            item_rect.size.height,
-        ).intersection(item_rect);
-
-        if let Some(decomposed_rect) = decomposed_rect {
-            decompose_cache_tiles(&decomposed_rect, info, callback);
-        }
-    }
-}
-
-fn decompose_cache_tiles(
-    item_rect: &LayoutRect,
-    info: &TiledImageInfo,
-    callback: &mut FnMut(&DecomposedTile),
+pub fn for_each_tile(
+    prim_rect: &LayoutRect,
+    visible_rect: &LayoutRect,
+    device_image_size: &DeviceUintSize,
+    device_tile_size: u32,
+    callback: &mut FnMut(&LayoutRect, TileOffset, EdgeAaSegmentMask),
 ) {
     // The image resource is tiled. We have to generate an image primitive
     // for each tile.
     // We need to do this because the image is broken up into smaller tiles in the texture
     // cache and the image shader is not able to work with this type of sparse representation.
 
     // The tiling logic works as follows:
     //
@@ -140,163 +116,174 @@ fn decompose_cache_tiles(
     //  #----+----+----+--#-+   |  -+-+
     //  #////|////|////|//# |   |     | "leftover" height
     //  ################### |  -+  ---+
     //  #----+----+----+----+
     //
     // In the ascii diagram above, a large image is split into tiles of almost regular size.
     // The tiles on the right and bottom edges (hatched in the diagram) are smaller than
     // the regular tiles and are handled separately in the code see leftover_width/height.
-    // each generated image primitive corresponds to a tile in the texture cache, with the
+    // each generated segment corresponds to a tile in the texture cache, with the
     // assumption that the smaller tiles with leftover sizes are sized to fit their own
     // irregular size in the texture cache.
-    //
-    // For the case where we don't tile along an axis, we can still perform the repetition in
-    // the shader (for this particular axis), and it is worth special-casing for this to avoid
-    // generating many primitives.
-    // This can happen with very tall and thin images used as a repeating background.
-    // Apparently web authors do that...
+
+    // Because we can have very large virtual images we iterate over the visible portion of
+    // the image in layer space intead of iterating over device tiles.
 
-    let needs_repeat_x = info.stretch_size.width < item_rect.size.width;
-    let needs_repeat_y = info.stretch_size.height < item_rect.size.height;
-
-    let tiled_in_x = info.device_image_size.width > info.device_tile_size;
-    let tiled_in_y = info.device_image_size.height > info.device_tile_size;
+    let visible_rect = match prim_rect.intersection(&visible_rect) {
+       Some(rect) => rect,
+       None => return,
+    };
 
-    // If we don't actually tile in this dimension, repeating can be done in the shader.
-    let shader_repeat_x = needs_repeat_x && !tiled_in_x;
-    let shader_repeat_y = needs_repeat_y && !tiled_in_y;
-
-    let tile_size_f32 = info.device_tile_size as f32;
+    let device_tile_size_f32 = device_tile_size as f32;
 
-    // Note: this rounds down so it excludes the partially filled tiles on the right and
-    // bottom edges (we handle them separately below).
-    let num_tiles_x = (info.device_image_size.width / info.device_tile_size) as u16;
-    let num_tiles_y = (info.device_image_size.height / info.device_tile_size) as u16;
+    // Ratio between (image space) tile size and image size .
+    let tile_dw = device_tile_size_f32 / (device_image_size.width as f32);
+    let tile_dh = device_tile_size_f32 / (device_image_size.height as f32);
 
-    // Ratio between (image space) tile size and image size.
-    let img_dw = tile_size_f32 / (info.device_image_size.width as f32);
-    let img_dh = tile_size_f32 / (info.device_image_size.height as f32);
-
-    // Stretched size of the tile in layout space.
-    let stretched_tile_size = LayoutSize::new(
-        img_dw * info.stretch_size.width,
-        img_dh * info.stretch_size.height,
+    // size of regular tiles in layout space.
+    let layer_tile_size = LayoutSize::new(
+        tile_dw * prim_rect.size.width,
+        tile_dh * prim_rect.size.height,
     );
 
     // The size in pixels of the tiles on the right and bottom edges, smaller
     // than the regular tile size if the image is not a multiple of the tile size.
     // Zero means the image size is a multiple of the tile size.
-    let leftover = DeviceUintSize::new(
-        info.device_image_size.width % info.device_tile_size,
-        info.device_image_size.height % info.device_tile_size
+    let leftover_device_size = DeviceUintSize::new(
+        device_image_size.width % device_tile_size,
+        device_image_size.height % device_tile_size
+    );
+
+    // The size in layer space of the tiles on the right and bottom edges.
+    let leftover_layer_size = LayoutSize::new(
+        layer_tile_size.width * leftover_device_size.width as f32 / device_tile_size_f32,
+        layer_tile_size.height * leftover_device_size.height as f32 / device_tile_size_f32,
+    );
+
+    // Offset of the row and column of tiles with leftover size.
+    let leftover_offset = TileOffset::new(
+        (device_image_size.width / device_tile_size) as u16,
+        (device_image_size.height / device_tile_size) as u16,
+    );
+
+    // Number of culled out tiles to skip before the first visible tile.
+    let t0 = TileOffset::new(
+        if visible_rect.origin.x > prim_rect.origin.x {
+            f32::floor((visible_rect.origin.x - prim_rect.origin.x) / layer_tile_size.width) as u16
+        } else {
+            0
+        },
+        if visible_rect.origin.y > prim_rect.origin.y {
+            f32::floor((visible_rect.origin.y - prim_rect.origin.y) / layer_tile_size.height) as u16
+        } else {
+            0
+        },
     );
 
-    for ty in 0 .. num_tiles_y {
-        for tx in 0 .. num_tiles_x {
-            add_device_tile(
-                item_rect,
-                stretched_tile_size,
-                TileOffset::new(tx, ty),
-                1.0,
-                1.0,
-                shader_repeat_x,
-                shader_repeat_y,
-                callback,
-            );
+    let x_count = f32::ceil((visible_rect.max_x() - prim_rect.origin.x) / layer_tile_size.width) as u16 - t0.x;
+    let y_count = f32::ceil((visible_rect.max_y() - prim_rect.origin.y) / layer_tile_size.height) as u16 - t0.y;
+
+    for y in 0..y_count {
+
+        let mut row_flags = EdgeAaSegmentMask::empty();
+        if y == 0 {
+            row_flags |= EdgeAaSegmentMask::TOP;
         }
-        if leftover.width != 0 {
-            // Tiles on the right edge that are smaller than the tile size.
-            add_device_tile(
-                item_rect,
-                stretched_tile_size,
-                TileOffset::new(num_tiles_x, ty),
-                (leftover.width as f32) / tile_size_f32,
-                1.0,
-                shader_repeat_x,
-                shader_repeat_y,
-                callback,
-            );
-        }
-    }
-
-    if leftover.height != 0 {
-        for tx in 0 .. num_tiles_x {
-            // Tiles on the bottom edge that are smaller than the tile size.
-            add_device_tile(
-                item_rect,
-                stretched_tile_size,
-                TileOffset::new(tx, num_tiles_y),
-                1.0,
-                (leftover.height as f32) / tile_size_f32,
-                shader_repeat_x,
-                shader_repeat_y,
-                callback,
-            );
+        if y == y_count - 1 {
+            row_flags |= EdgeAaSegmentMask::BOTTOM;
         }
 
-        if leftover.width != 0 {
-            // Finally, the bottom-right tile with a "leftover" size.
-            add_device_tile(
-                item_rect,
-                stretched_tile_size,
-                TileOffset::new(num_tiles_x, num_tiles_y),
-                (leftover.width as f32) / tile_size_f32,
-                (leftover.height as f32) / tile_size_f32,
-                shader_repeat_x,
-                shader_repeat_y,
-                callback,
-            );
+        for x in 0..x_count {
+            let tile_offset = t0 + vec2(x, y);
+
+
+            let mut segment_rect = LayoutRect {
+                origin: LayoutPoint::new(
+                    prim_rect.origin.x + tile_offset.x as f32 * layer_tile_size.width,
+                    prim_rect.origin.y + tile_offset.y as f32 * layer_tile_size.height,
+                ),
+                size: layer_tile_size,
+            };
+
+            if tile_offset.x == leftover_offset.x {
+                segment_rect.size.width = leftover_layer_size.width;
+            }
+
+            if tile_offset.y == leftover_offset.y {
+                segment_rect.size.height = leftover_layer_size.height;
+            }
+
+            let mut edge_flags = row_flags;
+            if x == 0 {
+                edge_flags |= EdgeAaSegmentMask::LEFT;
+            }
+            if x == x_count - 1 {
+                edge_flags |= EdgeAaSegmentMask::RIGHT;
+            }
+
+            callback(&segment_rect, tile_offset, edge_flags);
         }
     }
 }
 
-fn add_device_tile(
-    item_rect: &LayoutRect,
-    stretched_tile_size: LayoutSize,
-    tile_offset: TileOffset,
-    tile_ratio_width: f32,
-    tile_ratio_height: f32,
-    shader_repeat_x: bool,
-    shader_repeat_y: bool,
-    callback: &mut FnMut(&DecomposedTile),
-) {
-    // If the image is tiled along a given axis, we can't have the shader compute
-    // the image repetition pattern. In this case we base the primitive's rectangle size
-    // on the stretched tile size which effectively cancels the repetition (and repetition
-    // has to be emulated by generating more primitives).
-    // If the image is not tiled along this axis, we can perform the repetition in the
-    // shader. In this case we use the item's size in the primitive (on that particular
-    // axis).
-    // See the shader_repeat_x/y code below.
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::collections::HashSet;
+    use api::{LayoutRect, DeviceUintSize};
+    use euclid::{rect, size2};
 
-    let stretch_size = LayoutSize::new(
-        stretched_tile_size.width * tile_ratio_width,
-        stretched_tile_size.height * tile_ratio_height,
-    );
-
-    let mut prim_rect = LayoutRect::new(
-        item_rect.origin + LayoutVector2D::new(
-            tile_offset.x as f32 * stretched_tile_size.width,
-            tile_offset.y as f32 * stretched_tile_size.height,
-        ),
-        stretch_size,
-    );
-
-    if shader_repeat_x {
-        assert_eq!(tile_offset.x, 0);
-        prim_rect.size.width = item_rect.size.width;
+    // this checks some additional invariants
+    fn checked_for_each_tile(
+        prim_rect: &LayoutRect,
+        visible_rect: &LayoutRect,
+        device_image_size: &DeviceUintSize,
+        device_tile_size: u32,
+        callback: &mut FnMut(&LayoutRect, TileOffset, EdgeAaSegmentMask),
+    ) {
+        let mut coverage = LayoutRect::zero();
+        let mut tiles = HashSet::new();
+        for_each_tile(prim_rect,
+                      visible_rect,
+                      device_image_size,
+                      device_tile_size,
+                      &mut |tile_rect, tile_offset, tile_flags| {
+                          // make sure we don't get sent duplicate tiles
+                          assert!(!tiles.contains(&tile_offset));
+                          tiles.insert(tile_offset);
+                          coverage = coverage.union(tile_rect);
+                          assert!(prim_rect.contains_rect(&tile_rect));
+                          callback(tile_rect, tile_offset, tile_flags);
+                      },
+        );
+        assert!(prim_rect.contains_rect(&coverage));
+        assert!(coverage.contains_rect(&visible_rect.intersection(&prim_rect).unwrap_or(LayoutRect::zero())));
     }
 
-    if shader_repeat_y {
-        assert_eq!(tile_offset.y, 0);
-        prim_rect.size.height = item_rect.size.height;
+    #[test]
+    fn basic() {
+        let mut count = 0;
+        checked_for_each_tile(&rect(0., 0., 1000., 1000.),
+            &rect(75., 75., 400., 400.),
+            &size2(400, 400),
+            36,
+            &mut |_tile_rect, _tile_offset, _tile_flags| {
+                count += 1;
+            },
+        );
+        assert_eq!(count, 36);
     }
 
-    // Fix up the primitive's rect if it overflows the original item rect.
-    if let Some(rect) = prim_rect.intersection(item_rect) {
-        callback(&DecomposedTile {
-            tile_offset,
-            rect,
-            stretch_size,
-        });
+    #[test]
+    fn empty() {
+        let mut count = 0;
+        checked_for_each_tile(&rect(0., 0., 74., 74.),
+              &rect(75., 75., 400., 400.),
+              &size2(400, 400),
+              36,
+              &mut |_tile_rect, _tile_offset, _tile_flags| {
+                count += 1;
+              },
+        );
+        assert_eq!(count, 0);
     }
-}
+}
\ No newline at end of file
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -71,17 +71,16 @@ mod debug_render;
 mod debug_server;
 mod device;
 mod display_list_flattener;
 mod ellipse;
 mod frame_builder;
 mod freelist;
 #[cfg(any(target_os = "macos", target_os = "windows"))]
 mod gamma_lut;
-mod geometry;
 mod glyph_cache;
 mod glyph_rasterizer;
 mod gpu_cache;
 #[cfg(feature = "pathfinder")]
 mod gpu_glyph_renderer;
 mod gpu_types;
 mod hit_test;
 mod image;
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -1,39 +1,45 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{FontInstanceFlags, FontKey, FontRenderMode};
 use api::{ColorU, GlyphDimensions, GlyphKey, SubpixelDirection};
 use dwrote;
-use gamma_lut::{ColorLut, GammaLut};
-use glyph_rasterizer::{FontInstance, FontTransform, GlyphFormat};
-use glyph_rasterizer::{GlyphRasterResult, RasterizedGlyph};
+use gamma_lut::ColorLut;
+use glyph_rasterizer::{FontInstance, FontTransform};
 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;
+cfg_if! {
+    if #[cfg(feature = "pathfinder")] {
+        use pathfinder_font_renderer::{PathfinderComPtr, IDWriteFontFace};
+        use glyph_rasterizer::NativeFontHandleWrapper;
+    } else if #[cfg(not(feature = "pathfinder"))] {
+        use glyph_rasterizer::{GlyphFormat, GlyphRasterResult, RasterizedGlyph};
+        use gamma_lut::GammaLut;
+    }
+}
 
 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,
     };
 }
 
 pub struct FontContext {
     fonts: FastHashMap<FontKey, dwrote::FontFace>,
     simulations: FastHashMap<(FontKey, dwrote::DWRITE_FONT_SIMULATIONS), dwrote::FontFace>,
+    #[cfg(not(feature = "pathfinder"))]
     gamma_lut: GammaLut,
+    #[cfg(not(feature = "pathfinder"))]
     gdi_gamma_lut: GammaLut,
 }
 
 // DirectWrite is safe to use on multiple threads and non-shareable resources are
 // all hidden inside their font context.
 unsafe impl Send for FontContext {}
 
 fn dwrite_texture_type(render_mode: FontRenderMode) -> dwrote::DWRITE_TEXTURE_TYPE {
@@ -94,24 +100,31 @@ fn is_bitmap_font(font: &FontInstance) -
 // Skew factor matching Gecko/DWrite.
 const OBLIQUE_SKEW_FACTOR: f32 = 0.3;
 
 impl FontContext {
     pub fn new() -> Result<FontContext, ResourceCacheError> {
         // These are the default values we use in Gecko.
         // We use a gamma value of 2.3 for gdi fonts
         // TODO: Fetch this data from Gecko itself.
-        let contrast = 1.0;
-        let gamma = 1.8;
-        let gdi_gamma = 2.3;
+        cfg_if! {
+            if #[cfg(not(feature = "pathfinder"))] {
+                const CONTRAST: f32 = 1.0;
+                const GAMMA: f32 = 1.8;
+                const GDI_GAMMA: f32 = 2.3;
+            }
+        }
+
         Ok(FontContext {
             fonts: FastHashMap::default(),
             simulations: FastHashMap::default(),
-            gamma_lut: GammaLut::new(contrast, gamma, gamma),
-            gdi_gamma_lut: GammaLut::new(contrast, gdi_gamma, gdi_gamma),
+            #[cfg(not(feature = "pathfinder"))]
+            gamma_lut: GammaLut::new(CONTRAST, GAMMA, GAMMA),
+            #[cfg(not(feature = "pathfinder"))]
+            gdi_gamma_lut: GammaLut::new(CONTRAST, GDI_GAMMA, GDI_GAMMA),
         })
     }
 
     pub fn has_font(&self, font_key: &FontKey) -> bool {
         self.fonts.contains_key(font_key)
     }
 
     pub fn add_raw_font(&mut self, font_key: &FontKey, data: Arc<Vec<u8>>, index: u32) {
@@ -302,16 +315,17 @@ impl FontContext {
                     width,
                     height,
                     advance: advance,
                 }
             })
     }
 
     // DWrite ClearType gives us values in RGB, but WR expects BGRA.
+    #[cfg(not(feature = "pathfinder"))]
     fn convert_to_bgra(
         &self,
         pixels: &[u8],
         render_mode: FontRenderMode,
         bitmaps: bool,
     ) -> Vec<u8> {
         match (render_mode, bitmaps) {
             (FontRenderMode::Mono, _) => {
@@ -448,11 +462,11 @@ impl FontContext {
 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())
+        unsafe { PathfinderComPtr::new(face.as_ptr()) }
     }
 }
\ No newline at end of file
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,29 +1,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, BorderRadius, BoxShadowClipMode, BuiltDisplayList, ClipMode, ColorF, ComplexClipRegion};
-use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch, ExtendMode, FontRenderMode};
-use api::{FilterOp, GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag};
+use api::{DeviceIntRect, DeviceIntSize, DeviceUintSize, DevicePixelScale, Epoch, ExtendMode, FontRenderMode};
+use api::{FilterOp, GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, TileOffset};
 use api::{GlyphRasterSpace, LayoutPoint, LayoutRect, LayoutSize, LayoutToWorldTransform, LayoutVector2D};
 use api::{PipelineId, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat, DeviceIntSideOffsets};
 use border::{BorderCornerInstance, BorderEdgeKind};
 use box_shadow::BLUR_SAMPLE_SCALE;
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
 use clip::{ClipSourcesHandle, ClipWorkItem};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveRunContext;
 use glyph_rasterizer::{FontInstance, FontTransform};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::{BrushFlags, ClipChainRectIndex};
+use image::{for_each_tile, for_each_repetition};
 use picture::{PictureCompositeMode, PictureId, PicturePrimitive};
 #[cfg(debug_assertions)]
 use render_backend::FrameId;
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest};
 use scene::SceneProperties;
@@ -141,17 +142,16 @@ pub struct PrimitiveIndex(pub usize);
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PictureIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 pub enum PrimitiveKind {
     TextRun,
-    Image,
     Border,
     Brush,
 }
 
 impl GpuCacheHandle {
     pub fn as_int(&self, gpu_cache: &GpuCache) -> i32 {
         gpu_cache.get_address(self).as_int()
     }
@@ -238,16 +238,23 @@ impl OpacityBinding {
         let changed = new_opacity != self.current;
         self.current = new_opacity;
 
         changed
     }
 }
 
 #[derive(Debug)]
+pub struct VisibleImageTile {
+    pub tile_offset: TileOffset,
+    pub handle: GpuCacheHandle,
+    pub edge_flags: EdgeAaSegmentMask,
+}
+
+#[derive(Debug)]
 pub enum BrushKind {
     Solid {
         color: ColorF,
         opacity_binding: OpacityBinding,
     },
     Clear,
     Picture {
         pic_index: PictureIndex,
@@ -256,16 +263,17 @@ pub enum BrushKind {
         request: ImageRequest,
         current_epoch: Epoch,
         alpha_type: AlphaType,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         source: ImageSource,
         sub_rect: Option<DeviceIntRect>,
         opacity_binding: OpacityBinding,
+        visible_tiles: Vec<VisibleImageTile>,
     },
     YuvImage {
         yuv_key: [ImageKey; 3],
         format: YuvFormat,
         color_space: YuvColorSpace,
         image_rendering: ImageRendering,
     },
     RadialGradient {
@@ -1041,17 +1049,16 @@ impl ClipData {
             corner.write(request);
         }
     }
 }
 
 #[derive(Debug)]
 pub enum PrimitiveContainer {
     TextRun(TextRunPrimitiveCpu),
-    Image(ImagePrimitiveCpu),
     Border(BorderPrimitiveCpu),
     Brush(BrushPrimitive),
 }
 
 impl PrimitiveContainer {
     // Return true if the primary primitive is visible.
     // Used to trivially reject non-visible primitives.
     // TODO(gw): Currently, primitives other than those
@@ -1075,17 +1082,16 @@ impl PrimitiveContainer {
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::Border { .. } |
                     BrushKind::LinearGradient { .. } => {
                         true
                     }
                 }
             }
-            PrimitiveContainer::Image(..) |
             PrimitiveContainer::Border(..) => {
                 true
             }
         }
     }
 
     // Create a clone of this PrimitiveContainer, applying whatever
     // changes are necessary to the primitive to support rendering
@@ -1127,17 +1133,16 @@ impl PrimitiveContainer {
                     BrushKind::YuvImage { .. } |
                     BrushKind::Border { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::LinearGradient { .. } => {
                         panic!("bug: other brush kinds not expected here yet");
                     }
                 }
             }
-            PrimitiveContainer::Image(..) |
             PrimitiveContainer::Border(..) => {
                 panic!("bug: other primitive containers not expected here");
             }
         }
     }
 }
 
 pub struct PrimitiveStore {
@@ -1262,27 +1267,16 @@ impl PrimitiveStore {
                     prim_kind: PrimitiveKind::TextRun,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_runs.len()),
                     ..base_metadata
                 };
 
                 self.cpu_text_runs.push(text_cpu);
                 metadata
             }
-            PrimitiveContainer::Image(image_cpu) => {
-                let metadata = PrimitiveMetadata {
-                    opacity: PrimitiveOpacity::translucent(),
-                    prim_kind: PrimitiveKind::Image,
-                    cpu_prim_index: SpecificPrimitiveIndex(self.cpu_images.len()),
-                    ..base_metadata
-                };
-
-                self.cpu_images.push(image_cpu);
-                metadata
-            }
             PrimitiveContainer::Border(border_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
                     prim_kind: PrimitiveKind::Border,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_borders.len()),
                     ..base_metadata
                 };
 
@@ -1342,17 +1336,16 @@ impl PrimitiveStore {
                     BrushKind::Border { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::LinearGradient { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::Clear => {}
                 }
             }
             PrimitiveKind::TextRun |
-            PrimitiveKind::Image |
             PrimitiveKind::Border => {}
         }
 
         None
     }
 
     // Apply any optimizations to drawing this picture. Currently,
     // we just support collapsing pictures with an opacity filter
@@ -1393,17 +1386,16 @@ impl PrimitiveStore {
                         BrushKind::Border { .. } |
                         BrushKind::LinearGradient { .. } |
                         BrushKind::RadialGradient { .. } => {
                             unreachable!("bug: invalid prim type for opacity collapse");
                         }
                     };
                 }
                 PrimitiveKind::TextRun |
-                PrimitiveKind::Image |
                 PrimitiveKind::Border => {
                     unreachable!("bug: invalid prim type for opacity collapse");
                 }
             }
 
             // The opacity filter has been collapsed, so mark this picture
             // as a pass though. This means it will no longer allocate an
             // intermediate surface or incur an extra blend / blit. Instead,
@@ -1426,16 +1418,17 @@ impl PrimitiveStore {
         prim_index: PrimitiveIndex,
         prim_run_context: &PrimitiveRunContext,
         pic_state_for_children: PictureState,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) {
+        let mut is_tiled_image = false;
         let metadata = &mut self.cpu_metadata[prim_index.0];
         #[cfg(debug_assertions)]
         {
             metadata.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
         match metadata.prim_kind {
             PrimitiveKind::Border => {}
@@ -1446,175 +1439,69 @@ impl PrimitiveStore {
                 text.prepare_for_render(
                     frame_context.device_pixel_scale,
                     transform,
                     pic_context.allow_subpixel_aa,
                     pic_context.display_list,
                     frame_state,
                 );
             }
-            PrimitiveKind::Image => {
-                let image_cpu = &mut self.cpu_images[metadata.cpu_prim_index.0];
-                let image_properties = frame_state
-                    .resource_cache
-                    .get_image_properties(image_cpu.key.request.key);
-
-                // TODO(gw): Add image.rs and move this code out to a separate
-                //           source file as it gets more complicated, and we
-                //           start pre-rendering images for other reasons.
-
-                if let Some(image_properties) = image_properties {
-                    // See if this image has been updated since we last hit this code path.
-                    // If so, we need to (at least) update the opacity, and also rebuild
-                    // and render task cached portions of this image.
-                    if image_properties.epoch != image_cpu.current_epoch {
-                        image_cpu.current_epoch = image_properties.epoch;
-
-                        // Update the opacity.
-                        metadata.opacity.is_opaque = image_properties.descriptor.is_opaque &&
-                            image_cpu.tile_spacing.width == 0.0 &&
-                            image_cpu.tile_spacing.height == 0.0;
-
-                        // Work out whether this image is a normal / simple type, or if
-                        // we need to pre-render it to the render task cache.
-                        image_cpu.source = match image_cpu.key.texel_rect {
-                            Some(texel_rect) => {
-                                ImageSource::Cache {
-                                    // Size in device-pixels we need to allocate in render task cache.
-                                    size: texel_rect.size,
-                                    handle: None,
-                                }
-                            }
-                            None => {
-                                // Simple image - just use a normal texture cache entry.
-                                ImageSource::Default
-                            }
-                        };
-                    }
-
-                    // Set if we need to request the source image from the cache this frame.
-                    let mut request_source_image = false;
-
-                    // Every frame, for cached items, we need to request the render
-                    // task cache item. The closure will be invoked on the first
-                    // time through, and any time the render task output has been
-                    // evicted from the texture cache.
-                    match image_cpu.source {
-                        ImageSource::Cache { size, ref mut handle } => {
-                            let key = image_cpu.key;
-
-                            // Request a pre-rendered image task.
-                            *handle = Some(frame_state.resource_cache.request_render_task(
-                                RenderTaskCacheKey {
-                                    size,
-                                    kind: RenderTaskCacheKeyKind::Image(key),
-                                },
-                                frame_state.gpu_cache,
-                                frame_state.render_tasks,
-                                None,
-                                image_properties.descriptor.is_opaque,
-                                |render_tasks| {
-                                    // We need to render the image cache this frame,
-                                    // so will need access to the source texture.
-                                    request_source_image = true;
-
-                                    // Create a task to blit from the texture cache to
-                                    // a normal transient render task surface. This will
-                                    // copy only the sub-rect, if specified.
-                                    let cache_to_target_task = RenderTask::new_blit(
-                                        size,
-                                        BlitSource::Image {
-                                            key,
-                                        },
-                                    );
-                                    let cache_to_target_task_id = render_tasks.add(cache_to_target_task);
-
-                                    // Create a task to blit the rect from the child render
-                                    // task above back into the right spot in the persistent
-                                    // render target cache.
-                                    let target_to_cache_task = RenderTask::new_blit(
-                                        size,
-                                        BlitSource::RenderTask {
-                                            task_id: cache_to_target_task_id,
-                                        },
-                                    );
-                                    let target_to_cache_task_id = render_tasks.add(target_to_cache_task);
-
-                                    // Hook this into the render task tree at the right spot.
-                                    pic_state.tasks.push(target_to_cache_task_id);
-
-                                    // Pass the image opacity, so that the cached render task
-                                    // item inherits the same opacity properties.
-                                    target_to_cache_task_id
-                                }
-                            ));
-                        }
-                        ImageSource::Default => {
-                            // Normal images just reference the source texture each frame.
-                            request_source_image = true;
-                        }
-                    }
-
-                    // Request source image from the texture cache, if required.
-                    if request_source_image {
-                        frame_state.resource_cache.request_image(
-                            image_cpu.key.request,
-                            frame_state.gpu_cache,
-                        );
-                    }
-                }
-            }
             PrimitiveKind::Brush => {
                 let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0];
 
                 match brush.kind {
                     BrushKind::Image {
                         request,
                         sub_rect,
                         stretch_size,
                         ref mut tile_spacing,
                         ref mut current_epoch,
                         ref mut source,
                         ref mut opacity_binding,
+                        ref mut visible_tiles,
                         ..
                     } => {
                         let image_properties = frame_state
                             .resource_cache
                             .get_image_properties(request.key);
 
+
                         // Set if we need to request the source image from the cache this frame.
                         if let Some(image_properties) = image_properties {
                             *current_epoch = image_properties.epoch;
+                            is_tiled_image = image_properties.tiling.is_some();
 
                             // If the opacity changed, invalidate the GPU cache so that
                             // the new color for this primitive gets uploaded.
                             if opacity_binding.update(frame_context.scene_properties) {
                                 frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
                             }
 
                             // Update opacity for this primitive to ensure the correct
                             // batching parameters are used.
                             metadata.opacity.is_opaque =
                                 image_properties.descriptor.is_opaque &&
                                 opacity_binding.current == 1.0;
 
-                            if *tile_spacing != LayoutSize::zero() {
+                            if *tile_spacing != LayoutSize::zero() && !is_tiled_image {
                                 *source = ImageSource::Cache {
                                     // Size in device-pixels we need to allocate in render task cache.
                                     size: DeviceIntSize::new(
                                         image_properties.descriptor.width as i32,
                                         image_properties.descriptor.height as i32
                                     ),
                                     handle: None,
                                 };
                             }
 
                             // Work out whether this image is a normal / simple type, or if
                             // we need to pre-render it to the render task cache.
                             if let Some(rect) = sub_rect {
+                                // We don't properly support this right now.
+                                debug_assert!(!is_tiled_image);
                                 *source = ImageSource::Cache {
                                     // Size in device-pixels we need to allocate in render task cache.
                                     size: rect.size,
                                     handle: None,
                                 };
                             }
 
                             let mut request_source_image = false;
@@ -1691,24 +1578,99 @@ impl PrimitiveStore {
                                     ));
                                 }
                                 ImageSource::Default => {
                                     // Normal images just reference the source texture each frame.
                                     request_source_image = true;
                                 }
                             }
 
-                            if request_source_image {
+                            if let Some(tile_size) = image_properties.tiling {
+
+                                let device_image_size = DeviceUintSize::new(
+                                    image_properties.descriptor.width,
+                                    image_properties.descriptor.height,
+                                );
+
+                                // Tighten the clip rect because decomposing the repeated image can
+                                // produce primitives that are partially covering the original image
+                                // rect and we want to clip these extra parts out.
+                                let tight_clip_rect = metadata.local_clip_rect.intersection(&metadata.local_rect).unwrap();
+
+                                let visible_rect = compute_conservative_visible_rect(
+                                    prim_run_context,
+                                    frame_context,
+                                    &tight_clip_rect
+                                );
+
+                                let base_edge_flags = edge_flags_for_tile_spacing(tile_spacing);
+
+                                let stride = stretch_size + *tile_spacing;
+
+                                visible_tiles.clear();
+
+                                for_each_repetition(
+                                    &metadata.local_rect,
+                                    &visible_rect,
+                                    &stride,
+                                    &mut |origin, edge_flags| {
+                                        let edge_flags = base_edge_flags | edge_flags;
+
+                                        let image_rect = LayoutRect {
+                                            origin: *origin,
+                                            size: stretch_size,
+                                        };
+
+                                        for_each_tile(
+                                            &image_rect,
+                                            &visible_rect,
+                                            &device_image_size,
+                                            tile_size as u32,
+                                            &mut |tile_rect, tile_offset, tile_flags| {
+
+                                                frame_state.resource_cache.request_image(
+                                                    request.with_tile(tile_offset),
+                                                    frame_state.gpu_cache,
+                                                );
+
+                                                let mut handle = GpuCacheHandle::new();
+                                                if let Some(mut request) = frame_state.gpu_cache.request(&mut handle) {
+                                                    request.push(*tile_rect);
+                                                    request.push(tight_clip_rect);
+                                                    request.push(ColorF::new(1.0, 1.0, 1.0, opacity_binding.current).premultiplied());
+                                                    request.push(PremultipliedColorF::WHITE);
+                                                    request.push([tile_rect.size.width, tile_rect.size.height, 0.0, 0.0]);
+                                                    request.write_segment(*tile_rect, [0.0; 4]);
+                                                }
+
+                                                visible_tiles.push(VisibleImageTile {
+                                                    tile_offset,
+                                                    handle,
+                                                    edge_flags: tile_flags & edge_flags,
+                                                });
+                                            }
+                                        );
+                                    }
+                                );
+
+                                if visible_tiles.is_empty() {
+                                    // At this point if we don't have tiles to show it means we could probably
+                                    // have done a better a job at culling during an earlier stage.
+                                    // Clearing the screen rect has the effect of "culling out" the primitive
+                                    // from the point of view of the batch builder, and ensures we don't hit
+                                    // assertions later on because we didn't request any image.
+                                    metadata.screen_rect = None;
+                                }
+                            } else if request_source_image {
                                 frame_state.resource_cache.request_image(
                                     request,
                                     frame_state.gpu_cache,
                                 );
                             }
                         }
-
                     }
                     BrushKind::YuvImage { format, yuv_key, image_rendering, .. } => {
                         let channel_num = format.get_plane_num();
                         debug_assert!(channel_num <= 3);
                         for channel in 0 .. channel_num {
                             frame_state.resource_cache.request_image(
                                 ImageRequest {
                                     key: yuv_key[channel],
@@ -1784,31 +1746,32 @@ impl PrimitiveStore {
                             frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
                         }
                     }
                     BrushKind::Clear => {}
                 }
             }
         }
 
+        if is_tiled_image {
+            // we already requested each tile's gpu data.
+            return;
+        }
+
         // Mark this GPU resource as required for this frame.
         if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) {
             // has to match VECS_PER_BRUSH_PRIM
             request.push(metadata.local_rect);
             request.push(metadata.local_clip_rect);
 
             match metadata.prim_kind {
                 PrimitiveKind::Border => {
                     let border = &self.cpu_borders[metadata.cpu_prim_index.0];
                     border.write_gpu_blocks(request);
                 }
-                PrimitiveKind::Image => {
-                    let image = &self.cpu_images[metadata.cpu_prim_index.0];
-                    image.write_gpu_blocks(request);
-                }
                 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, metadata.local_rect);
 
@@ -2006,16 +1969,18 @@ impl PrimitiveStore {
         prim_index: PrimitiveIndex,
         clips: &Vec<ClipWorkItem>,
         combined_outer_rect: &DeviceIntRect,
         has_clips_from_other_coordinate_systems: bool,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) -> bool {
+        assert!(frame_context.screen_rect.contains_rect(combined_outer_rect));
+
         let metadata = &self.cpu_metadata[prim_index.0];
         let brush = match metadata.prim_kind {
             PrimitiveKind::Brush => {
                 &mut self.cpu_brushes[metadata.cpu_prim_index.0]
             }
             _ => {
                 return false;
             }
@@ -2038,23 +2003,24 @@ impl PrimitiveStore {
         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 = BrushSegmentTaskId::Opaque;
                 continue;
             }
 
-            let segment_screen_rect = calculate_screen_bounding_rect(
+            let intersected_rect = calculate_screen_bounding_rect(
                 &prim_run_context.scroll_node.world_content_transform,
                 &segment.local_rect,
                 frame_context.device_pixel_scale,
+                Some(&combined_outer_rect),
             );
 
-            let bounds = match combined_outer_rect.intersection(&segment_screen_rect) {
+            let bounds = match intersected_rect {
                 Some(bounds) => bounds,
                 None => {
                     segment.clip_task_id = BrushSegmentTaskId::Empty;
                     continue;
                 }
             };
 
             let clip_task = RenderTask::new_mask(
@@ -2117,18 +2083,21 @@ impl PrimitiveStore {
             let metadata = &self.cpu_metadata[prim_index.0];
             metadata.clip_sources.as_ref().map(|clip_sources| {
                 let prim_clips = frame_state.clip_store.get_mut(clip_sources);
                 prim_clips.update(
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_context.device_pixel_scale,
                 );
-                let (screen_inner_rect, screen_outer_rect) =
-                    prim_clips.get_screen_bounds(transform, frame_context.device_pixel_scale);
+                let (screen_inner_rect, screen_outer_rect) = prim_clips.get_screen_bounds(
+                    transform,
+                    frame_context.device_pixel_scale,
+                    Some(&prim_screen_rect),
+                );
 
                 if let Some(outer) = screen_outer_rect {
                     combined_outer_rect = combined_outer_rect.and_then(|r| r.intersection(&outer));
                 }
 
                 Arc::new(ClipChainNode {
                     work_item: ClipWorkItem {
                         scroll_node_data_index: prim_run_context.scroll_node.node_data_index,
@@ -2327,46 +2296,43 @@ impl PrimitiveStore {
         let (local_rect, unclipped_device_rect) = {
             let metadata = &mut self.cpu_metadata[prim_index.0];
             if metadata.local_rect.size.width <= 0.0 ||
                metadata.local_rect.size.height <= 0.0 {
                 //warn!("invalid primitive rect {:?}", metadata.local_rect);
                 return None;
             }
 
+            metadata.screen_rect = None;
+
             // Inflate the local rect for this primitive by the inflation factor of
             // the picture context. This ensures that even if the primitive itself
             // is not visible, any effects from the blur radius will be correctly
             // taken into account.
             let local_rect = metadata.local_rect
                 .inflate(pic_context.inflation_factor, pic_context.inflation_factor)
                 .intersection(&metadata.local_clip_rect)?;
 
-            let screen_bounding_rect = calculate_screen_bounding_rect(
+            let unclipped = calculate_screen_bounding_rect(
                 &prim_run_context.scroll_node.world_content_transform,
                 &local_rect,
                 frame_context.device_pixel_scale,
-            );
+                None, //TODO: inflate `frame_context.screen_rect` appropriately
+            )?;
 
-            metadata.screen_rect = screen_bounding_rect
-                .intersection(&prim_run_context.clip_chain.combined_outer_screen_rect)
-                .map(|clipped| {
-                    ScreenRect {
-                        clipped,
-                        unclipped: screen_bounding_rect,
-                    }
-                });
+            let clipped = unclipped
+                .intersection(&prim_run_context.clip_chain.combined_outer_screen_rect)?;
 
-            if metadata.screen_rect.is_none() {
-                return None;
-            }
-
+            metadata.screen_rect = Some(ScreenRect {
+                clipped,
+                unclipped,
+            });
             metadata.clip_chain_rect_index = prim_run_context.clip_chain_rect_index;
 
-            (local_rect, screen_bounding_rect)
+            (local_rect, unclipped)
         };
 
         if may_need_clip_mask && !self.update_clip_task(
             prim_index,
             prim_run_context,
             &unclipped_device_rect,
             pic_state,
             frame_context,
@@ -2509,16 +2475,49 @@ impl PrimitiveStore {
                 }
             }
         }
 
         result
     }
 }
 
+fn compute_conservative_visible_rect(
+    prim_run_context: &PrimitiveRunContext,
+    frame_context: &FrameBuildingContext,
+    local_clip_rect: &LayoutRect,
+) -> LayoutRect {
+    let world_screen_rect = prim_run_context
+        .clip_chain.combined_outer_screen_rect
+        .to_f32() / frame_context.device_pixel_scale;
+
+    if let Some(layer_screen_rect) = prim_run_context
+        .scroll_node
+        .world_content_transform
+        .unapply(&world_screen_rect) {
+
+        return local_clip_rect.intersection(&layer_screen_rect).unwrap_or(LayoutRect::zero());
+    }
+
+    *local_clip_rect
+}
+
+fn edge_flags_for_tile_spacing(tile_spacing: &LayoutSize) -> EdgeAaSegmentMask {
+    let mut flags = EdgeAaSegmentMask::empty();
+
+    if tile_spacing.width > 0.0 {
+        flags |= EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT;
+    }
+    if tile_spacing.height > 0.0 {
+        flags |= EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM;
+    }
+
+    flags
+}
+
 //Test for one clip region contains another
 trait InsideTest<T> {
     fn might_contain(&self, clip: &T) -> bool;
 }
 
 impl InsideTest<ComplexClipRegion> for ComplexClipRegion {
     // Returns true if clip is inside self, can return false negative
     fn might_contain(&self, clip: &ComplexClipRegion) -> bool {
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -1,25 +1,25 @@
 /* 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::{ApiMsg, BuiltDisplayList, ClearCache, DebugCommand};
 #[cfg(feature = "debugger")]
 use api::{BuiltDisplayListIter, SpecificDisplayItem};
 use api::{DeviceIntPoint, DevicePixelScale, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
-use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestResult};
+use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestFlags, HitTestResult};
 use api::{IdNamespace, LayoutPoint, PipelineId, RenderNotifier, SceneMsg, ScrollClamping};
-use api::{ScrollLocation, ScrollNodeState, TransactionMsg, WorldPoint};
+use api::{ScrollLocation, ScrollNodeState, TransactionMsg};
 use api::channel::{MsgReceiver, Payload};
 #[cfg(feature = "capture")]
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
-use clip_scroll_tree::ClipScrollTree;
+use clip_scroll_tree::{ClipScrollNodeIndex, ClipScrollTree};
 #[cfg(feature = "debugger")]
 use debug_server;
 use display_list_flattener::DisplayListFlattener;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use hit_test::{HitTest, HitTester};
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
 use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
@@ -201,17 +201,16 @@ impl Document {
         self.current.scene.pipeline_epochs.clear();
         let old_scrolling_states = self.clip_scroll_tree.drain();
 
         let frame_builder = DisplayListFlattener::create_frame_builder(
             old_builder,
             &self.pending.scene,
             &mut self.clip_scroll_tree,
             resource_cache.get_font_instances(),
-            resource_cache.get_tiled_image_map(),
             &self.view,
             &self.output_pipelines,
             &self.frame_builder_config,
             &mut self.current.scene,
         );
 
         self.clip_scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
 
@@ -246,17 +245,16 @@ impl Document {
                 error!("ERROR: Invalid window dimensions! Please call api.set_window_size()");
             }
 
             Some(SceneRequest {
                 scene: self.pending.scene.clone(),
                 removed_pipelines: replace(&mut self.pending.removed_pipelines, Vec::new()),
                 view: self.view.clone(),
                 font_instances: resource_cache.get_font_instances(),
-                tiled_image_map: resource_cache.get_tiled_image_map(),
                 output_pipelines: self.output_pipelines.clone(),
             })
         } else {
             None
         };
 
         scene_tx.send(SceneBuilderRequest::Transaction {
             scene: scene_request,
@@ -310,22 +308,22 @@ impl Document {
     }
 
     pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
         self.clip_scroll_tree
             .discard_frame_state_for_pipeline(pipeline_id);
     }
 
     /// Returns true if any nodes actually changed position or false otherwise.
-    pub fn scroll(
+    pub fn scroll_nearest_scrolling_ancestor(
         &mut self,
         scroll_location: ScrollLocation,
-        cursor: WorldPoint,
+        scroll_node_index: Option<ClipScrollNodeIndex>,
     ) -> bool {
-        self.clip_scroll_tree.scroll(scroll_location, cursor)
+        self.clip_scroll_tree.scroll_nearest_scrolling_ancestor(scroll_location, scroll_node_index)
     }
 
     /// Returns true if the node actually changed position or false otherwise.
     pub fn scroll_node(
         &mut self,
         origin: LayoutPoint,
         id: ExternalScrollId,
         clamp: ScrollClamping
@@ -615,19 +613,34 @@ impl RenderBackend {
                 } else {
                     doc.output_pipelines.remove(&pipeline_id);
                 }
                 DocumentOps::nop()
             }
             FrameMsg::Scroll(delta, cursor) => {
                 profile_scope!("Scroll");
 
-                let should_render = doc.scroll(delta, cursor)
-                    && doc.render_on_scroll == Some(true);
+                let mut should_render = true;
+                let node_index = match doc.hit_tester {
+                    Some(ref hit_tester) => {
+                        // Ideally we would call doc.scroll_nearest_scrolling_ancestor here, but
+                        // we need have to avoid a double-borrow.
+                        let test = HitTest::new(None, cursor, HitTestFlags::empty());
+                        hit_tester.find_node_under_point(test)
+                    }
+                    None => {
+                        should_render = false;
+                        None
+                    }
+                };
 
+                let should_render =
+                    should_render &&
+                    doc.scroll_nearest_scrolling_ancestor(delta, node_index) &&
+                    doc.render_on_scroll == Some(true);
                 DocumentOps {
                     scroll: true,
                     render: should_render,
                     composite: should_render,
                     ..DocumentOps::nop()
                 }
             }
             FrameMsg::HitTest(pipeline_id, point, flags, tx) => {
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -212,18 +212,18 @@ pub struct BlurTask {
 impl BlurTask {
     #[cfg(feature = "debugger")]
     fn print_with<T: PrintTreePrinter>(&self, pt: &mut T) {
         pt.add_item(format!("std deviation: {}", self.blur_std_deviation));
         pt.add_item(format!("target: {:?}", self.target_kind));
     }
 }
 
+#[derive(Debug)]
 #[cfg(feature = "pathfinder")]
-#[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GlyphTask {
     /// After job building, this becomes `None`.
     pub mesh: Option<Mesh>,
     pub origin: DeviceIntPoint,
     pub subpixel_offset: TypedPoint2D<f32, DevicePixel>,
     pub render_mode: FontRenderMode,
@@ -595,32 +595,32 @@ impl RenderTask {
             clear_mode: ClearMode::Transparent,
             saved_index: None,
         }
     }
 
     fn uv_rect_kind(&self) -> UvRectKind {
         match self.kind {
             RenderTaskKind::CacheMask(..) |
-            RenderTaskKind::Glyph(_) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) => {
                 unreachable!("bug: unexpected render task");
             }
 
             RenderTaskKind::Picture(ref task) => {
                 task.uv_rect_kind
             }
 
             RenderTaskKind::VerticalBlur(ref task) |
             RenderTaskKind::HorizontalBlur(ref task) => {
                 task.uv_rect_kind
             }
 
             RenderTaskKind::ClipRegion(..) |
+            RenderTaskKind::Glyph(_) |
             RenderTaskKind::Blit(..) => {
                 UvRectKind::Rect
             }
         }
     }
 
     // Write (up to) 8 floats of data specific to the type
     // of render task that is provided to the GPU shaders
@@ -951,16 +951,17 @@ pub struct RenderTaskCacheEntry {
     pub handle: TextureCacheHandle,
 }
 
 #[derive(Debug)]
 pub enum RenderTaskCacheMarker {}
 
 // A cache of render tasks that are stored in the texture
 // cache for usage across frames.
+#[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskCache {
     map: FastHashMap<RenderTaskCacheKey, FreeListHandle<RenderTaskCacheMarker>>,
     cache_entries: FreeList<RenderTaskCacheEntry, RenderTaskCacheMarker>,
 }
 
 pub type RenderTaskCacheEntryHandle = WeakFreeListHandle<RenderTaskCacheMarker>;
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -131,20 +131,16 @@ const GPU_TAG_CACHE_CLIP: GpuProfileTag 
 const GPU_TAG_SETUP_TARGET: GpuProfileTag = GpuProfileTag {
     label: "target init",
     color: debug_colors::SLATEGREY,
 };
 const GPU_TAG_SETUP_DATA: GpuProfileTag = GpuProfileTag {
     label: "data init",
     color: debug_colors::LIGHTGREY,
 };
-const GPU_TAG_PRIM_IMAGE: GpuProfileTag = GpuProfileTag {
-    label: "Image",
-    color: debug_colors::GREEN,
-};
 const GPU_TAG_PRIM_SPLIT_COMPOSITE: GpuProfileTag = GpuProfileTag {
     label: "SplitComposite",
     color: debug_colors::DARKBLUE,
 };
 const GPU_TAG_PRIM_TEXT_RUN: GpuProfileTag = GpuProfileTag {
     label: "TextRun",
     color: debug_colors::BLUE,
 };
@@ -178,31 +174,24 @@ const GPU_SAMPLER_TAG_TRANSPARENT: GpuPr
     color: debug_colors::BLACK,
 };
 
 impl TransformBatchKind {
     #[cfg(feature = "debugger")]
     fn debug_name(&self) -> &'static str {
         match *self {
             TransformBatchKind::TextRun(..) => "TextRun",
-            TransformBatchKind::Image(image_buffer_kind, ..) => match image_buffer_kind {
-                ImageBufferKind::Texture2D => "Image (2D)",
-                ImageBufferKind::TextureRect => "Image (Rect)",
-                ImageBufferKind::TextureExternal => "Image (External)",
-                ImageBufferKind::Texture2DArray => "Image (Array)",
-            },
             TransformBatchKind::BorderCorner => "BorderCorner",
             TransformBatchKind::BorderEdge => "BorderEdge",
         }
     }
 
     fn sampler_tag(&self) -> GpuProfileTag {
         match *self {
             TransformBatchKind::TextRun(..) => GPU_TAG_PRIM_TEXT_RUN,
-            TransformBatchKind::Image(..) => GPU_TAG_PRIM_IMAGE,
             TransformBatchKind::BorderCorner => GPU_TAG_PRIM_BORDER_CORNER,
             TransformBatchKind::BorderEdge => GPU_TAG_PRIM_BORDER_EDGE,
         }
     }
 }
 
 impl BatchKind {
     #[cfg(feature = "debugger")]
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -105,18 +105,16 @@ struct ImageResource {
 }
 
 #[derive(Clone, Debug)]
 pub struct ImageTiling {
     pub image_size: DeviceUintSize,
     pub tile_size: TileSize,
 }
 
-pub type TiledImageMap = FastHashMap<ImageKey, ImageTiling>;
-
 #[derive(Default)]
 struct ImageTemplates {
     images: FastHashMap<ImageKey, ImageResource>,
 }
 
 impl ImageTemplates {
     fn insert(&mut self, key: ImageKey, resource: ImageResource) {
         self.images.insert(key, resource);
@@ -233,16 +231,26 @@ where
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ImageRequest {
     pub key: ImageKey,
     pub rendering: ImageRendering,
     pub tile: Option<TileOffset>,
 }
 
+impl ImageRequest {
+    pub fn with_tile(&self, offset: TileOffset) -> Self {
+        ImageRequest {
+            key: self.key,
+            rendering: self.rendering,
+            tile: Some(offset),
+        }
+    }
+}
+
 impl Into<BlobImageRequest> for ImageRequest {
     fn into(self) -> BlobImageRequest {
         BlobImageRequest {
             key: self.key,
             tile: self.tile,
         }
     }
 }
@@ -873,38 +881,16 @@ impl ResourceCache {
                 descriptor: image_template.descriptor,
                 external_image,
                 tiling: image_template.tiling,
                 epoch: image_template.epoch,
             }
         })
     }
 
-    pub fn get_tiled_image_map(&self) -> TiledImageMap {
-        self.resources
-            .image_templates
-            .images
-            .iter()
-            .filter_map(|(&key, template)| {
-                template.tiling.map(|tile_size| {
-                    (
-                        key,
-                        ImageTiling {
-                            image_size: DeviceUintSize::new(
-                                template.descriptor.width,
-                                template.descriptor.height,
-                            ),
-                            tile_size,
-                        },
-                    )
-                })
-            })
-            .collect()
-    }
-
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         debug_assert_eq!(self.state, State::Idle);
         self.state = State::AddResources;
         self.texture_cache.begin_frame(frame_id);
         self.cached_glyphs.begin_frame(&self.texture_cache, &self.cached_render_tasks);
         self.cached_render_tasks.begin_frame(&mut self.texture_cache);
         self.current_frame_id = frame_id;
     }
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 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::FastHashSet;
-use resource_cache::{FontInstanceMap, TiledImageMap};
+use resource_cache::FontInstanceMap;
 use render_backend::DocumentView;
 use renderer::{PipelineInfo, SceneBuilderHooks};
 use scene::Scene;
 use std::sync::mpsc::{channel, Receiver, Sender};
 
 // Message from render backend to scene builder.
 pub enum SceneBuilderRequest {
     Transaction {
@@ -50,17 +50,16 @@ pub enum SceneSwapResult {
     Aborted,
 }
 
 /// Contains the render backend data needed to build a scene.
 pub struct SceneRequest {
     pub scene: Scene,
     pub view: DocumentView,
     pub font_instances: FontInstanceMap,
-    pub tiled_image_map: TiledImageMap,
     pub output_pipelines: FastHashSet<PipelineId>,
     pub removed_pipelines: Vec<PipelineId>,
 }
 
 pub struct BuiltScene {
     pub scene: Scene,
     pub frame_builder: FrameBuilder,
     pub clip_scroll_tree: ClipScrollTree,
--- a/gfx/webrender/src/shade.rs
+++ b/gfx/webrender/src/shade.rs
@@ -487,17 +487,16 @@ pub struct Shaders {
     // final results on screen. They are aware of tile boundaries.
     // Most draw directly to the framebuffer, but some use inputs
     // from the cache shaders to draw. Specifically, the box
     // shadow primitive shader stretches the box shadow cache
     // output, and the cache_image shader blits the results of
     // a cache shader (e.g. blur) to the screen.
     pub ps_text_run: TextShader,
     pub ps_text_run_dual_source: TextShader,
-    ps_image: Vec<Option<PrimitiveShader>>,
     ps_border_corner: PrimitiveShader,
     ps_border_edge: PrimitiveShader,
 
     ps_split_composite: LazilyCompiledShader,
 }
 
 impl Shaders {
     pub fn new(
@@ -627,35 +626,27 @@ impl Shaders {
         let ps_text_run_dual_source = TextShader::new("ps_text_run",
             device,
             &["DUAL_SOURCE_BLENDING"],
             options.precache_shaders,
         )?;
 
         // All image configuration.
         let mut image_features = Vec::new();
-        let mut ps_image = Vec::new();
         let mut brush_image = Vec::new();
         // PrimitiveShader is not clonable. Use push() to initialize the vec.
         for _ in 0 .. IMAGE_BUFFER_KINDS.len() {
-            ps_image.push(None);
             brush_image.push(None);
         }
         for buffer_kind in 0 .. IMAGE_BUFFER_KINDS.len() {
             if IMAGE_BUFFER_KINDS[buffer_kind].has_platform_support(&gl_type) {
                 let feature_string = IMAGE_BUFFER_KINDS[buffer_kind].get_feature_string();
                 if feature_string != "" {
                     image_features.push(feature_string);
                 }
-                ps_image[buffer_kind] = Some(PrimitiveShader::new(
-                    "ps_image",
-                    device,
-                    &image_features,
-                    options.precache_shaders,
-                )?);
                 brush_image[buffer_kind] = Some(BrushShader::new(
                     "brush_image",
                     device,
                     &image_features,
                     options.precache_shaders,
                     true,
                 )?);
             }
@@ -744,17 +735,16 @@ impl Shaders {
             brush_linear_gradient,
             cs_clip_rectangle,
             cs_clip_box_shadow,
             cs_clip_border,
             cs_clip_image,
             cs_clip_line,
             ps_text_run,
             ps_text_run_dual_source,
-            ps_image,
             ps_border_corner,
             ps_border_edge,
             ps_split_composite,
         })
     }
 
     fn get_yuv_shader_index(
         buffer_kind: ImageBufferKind,
@@ -810,21 +800,16 @@ impl Shaders {
                                 &mut self.ps_text_run_dual_source
                             }
                             _ => {
                                 &mut self.ps_text_run
                             }
                         };
                         return text_shader.get(glyph_format, transform_kind);
                     }
-                    TransformBatchKind::Image(image_buffer_kind) => {
-                        self.ps_image[image_buffer_kind as usize]
-                            .as_mut()
-                            .expect("Unsupported image shader kind")
-                    }
                     TransformBatchKind::BorderCorner => {
                         &mut self.ps_border_corner
                     }
                     TransformBatchKind::BorderEdge => {
                         &mut self.ps_border_edge
                     }
                 };
                 prim_shader.get(transform_kind)
@@ -847,21 +832,16 @@ impl Shaders {
         self.cs_clip_line.deinit(device);
         self.ps_text_run.deinit(device);
         self.ps_text_run_dual_source.deinit(device);
         for shader in self.brush_image {
             if let Some(shader) = shader {
                 shader.deinit(device);
             }
         }
-        for shader in self.ps_image {
-            if let Some(shader) = shader {
-                shader.deinit(device);
-            }
-        }
         for shader in self.brush_yuv_image {
             if let Some(shader) = shader {
                 shader.deinit(device);
             }
         }
         self.ps_border_corner.deinit(device);
         self.ps_border_edge.deinit(device);
         self.ps_split_composite.deinit(device);
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -1,18 +1,20 @@
 /* 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, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
 use api::{DevicePoint, DeviceRect, DeviceSize, LayoutPixel, LayoutPoint, LayoutRect, LayoutSize};
 use api::{WorldPixel, WorldRect};
-use euclid::{Point2D, Rect, Size2D, TypedPoint2D, TypedPoint3D, TypedRect, TypedSize2D};
-use euclid::{TypedTransform2D, TypedTransform3D, TypedVector2D};
+use euclid::{Point2D, Rect, Size2D, TypedPoint2D, TypedRect, TypedSize2D};
+use euclid::{TypedTransform2D, TypedTransform3D, TypedVector2D, TypedVector3D};
+use euclid::{HomogeneousVector};
 use num_traits::Zero;
+use plane_split::{Clipper, Plane, Polygon};
 use std::{i32, f32};
 
 // Matches the definition of SK_ScalarNearlyZero in Skia.
 const NEARLY_ZERO: f32 = 1.0 / 4096.0;
 
 // TODO: Implement these in euclid!
 pub trait MatrixHelpers<Src, Dst> {
     fn preserves_2d_axis_alignment(&self) -> bool;
@@ -155,31 +157,124 @@ pub fn rect_from_points_f(x0: f32, y0: f
 pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
     (b - a) * t + a
 }
 
 pub fn calculate_screen_bounding_rect(
     transform: &LayoutToWorldFastTransform,
     rect: &LayoutRect,
     device_pixel_scale: DevicePixelScale,
-) -> DeviceIntRect {
-    let points = [
-        transform.transform_point2d(&rect.origin),
-        transform.transform_point2d(&rect.top_right()),
-        transform.transform_point2d(&rect.bottom_left()),
-        transform.transform_point2d(&rect.bottom_right()),
+    screen_bounds: Option<&DeviceIntRect>,
+) -> Option<DeviceIntRect> {
+    debug!("rect {:?}", rect);
+    debug!("transform {:?}", transform);
+    debug!("screen_bounds: {:?}", screen_bounds);
+    let homogens = [
+        transform.transform_point2d_homogeneous(&rect.origin),
+        transform.transform_point2d_homogeneous(&rect.top_right()),
+        transform.transform_point2d_homogeneous(&rect.bottom_left()),
+        transform.transform_point2d_homogeneous(&rect.bottom_right()),
     ];
+    debug!("homogeneous points {:?}", homogens);
+    let max_rect = match screen_bounds {
+        Some(bounds) => bounds.to_f32(),
+        None => DeviceRect::max_rect(),
+    };
 
-    let rect = WorldRect::from_points(&points) * device_pixel_scale;
-    let max_rect = DeviceRect::max_rect();
-    rect
+    // Note: we only do the full frustum collision when the polygon approaches the camera plane.
+    // Otherwise, it will be clamped to the screen bounds anyway.
+    let world_rect = if homogens.iter().any(|h| h.w <= 0.0) {
+        let mut clipper = Clipper::new();
+        // using inverse-transpose of the inversed transform
+        let t = transform.to_transform();
+        // camera plane
+        {
+            let normal = TypedVector3D::new(t.m14, t.m24, t.m34);
+            let kf = 1.0 / normal.length();
+            clipper.add(Plane {
+                normal: normal * kf,
+                offset: t.m44 * kf,
+            });
+        }
+
+        // The equations for clip planes come from the following one:
+        // (v * M).x < right
+        // (v, Mx) < right
+        // (v, Mx) - right = 0;
+
+        // left/right planes
+        if let Some(bounds) = screen_bounds {
+            let normal = TypedVector3D::new(t.m11, t.m21, t.m31);
+            let kf = 1.0 / normal.length();
+            clipper.add(Plane {
+                normal: normal * kf,
+                offset: t.m41 * kf - (bounds.origin.x) as f32 / device_pixel_scale.0,
+            });
+            clipper.add(Plane {
+                normal: normal * -kf,
+                offset: t.m41 * -kf + (bounds.origin.x + bounds.size.width) as f32 / device_pixel_scale.0,
+            });
+        }
+        // top/bottom planes
+        if let Some(bounds) = screen_bounds {
+            let normal = TypedVector3D::new(t.m12, t.m22, t.m32);
+            let kf = 1.0 / normal.length();
+            clipper.add(Plane {
+                normal: normal * kf,
+                offset: t.m42 * kf - (bounds.origin.y) as f32 / device_pixel_scale.0,
+            });
+            clipper.add(Plane {
+                normal: normal * -kf,
+                offset: t.m42 * -kf + (bounds.origin.y + bounds.size.height) as f32 / device_pixel_scale.0,
+            });
+        }
+
+        let polygon = Polygon::from_points(
+            [
+                rect.origin.to_3d(),
+                rect.top_right().to_3d(),
+                rect.bottom_left().to_3d(),
+                rect.bottom_right().to_3d(),
+            ],
+            1,
+        );
+        debug!("crossing detected for poly {:?}", polygon);
+        let results = clipper.clip(polygon);
+        debug!("clip results: {:?}", results);
+        if results.is_empty() {
+            return None
+        }
+
+        debug!("points:");
+        WorldRect::from_points(results
+            .into_iter()
+            // filter out parts behind the view plane
+            .flat_map(|poly| &poly.points)
+            .map(|p| {
+                debug!("\tpoint {:?} -> {:?} -> {:?}", p,
+                    transform.transform_point2d_homogeneous(&p.to_2d()),
+                    transform.transform_point2d(&p.to_2d())
+                );
+                transform.transform_point2d(&p.to_2d())
+            })
+        )
+    } else {
+        WorldRect::from_points(&[
+            homogens[0].to_point2d(),
+            homogens[1].to_point2d(),
+            homogens[2].to_point2d(),
+            homogens[3].to_point2d(),
+        ])
+    };
+
+    debug!("world rect {:?}", world_rect);
+    (world_rect * device_pixel_scale)
         .round_out()
         .intersection(&max_rect)
-        .unwrap_or(max_rect)
-        .to_i32()
+        .map(|r| r.to_i32())
 }
 
 pub fn _subtract_rect<U>(
     rect: &TypedRect<f32, U>,
     other: &TypedRect<f32, U>,
     results: &mut Vec<TypedRect<f32, U>>,
 ) {
     results.clear();
@@ -452,21 +547,23 @@ impl<Src, Dst> FastTransform<Src, Dst> {
                 let new_point = *point + offset;
                 TypedPoint2D::from_untyped(&new_point.to_untyped())
             }
             FastTransform::Transform { ref transform, .. } => transform.transform_point2d(point),
         }
     }
 
     #[inline(always)]
-    pub fn transform_point3d(&self, point: &TypedPoint3D<f32, Src>) -> TypedPoint3D<f32, Dst> {
+    pub fn transform_point2d_homogeneous(&self, point: &TypedPoint2D<f32, Src>) -> HomogeneousVector<f32, Dst> {
         match *self {
-            FastTransform::Offset(offset) =>
-                TypedPoint3D::new(point.x + offset.x, point.y + offset.y, point.z),
-            FastTransform::Transform { ref transform, .. } => transform.transform_point3d(point),
+            FastTransform::Offset(offset) => {
+                let new_point = *point + offset;
+                HomogeneousVector::new(new_point.x, new_point.y, 0.0, 1.0)
+            }
+            FastTransform::Transform { ref transform, .. } => transform.transform_point2d_homogeneous(point),
         }
     }
 
     #[inline(always)]
     pub fn transform_rect(&self, rect: &TypedRect<f32, Src>) -> TypedRect<f32, Dst> {
         match *self {
             FastTransform::Offset(offset) =>
                 TypedRect::from_untyped(&rect.to_untyped().translate(&offset.to_untyped())),
@@ -533,28 +630,28 @@ impl<Src, Dst> FastTransform<Src, Dst> {
             // If we break 2D axis alignment or have a perspective component, we need to start a
             // new incompatible coordinate system with which we cannot share clips without masking.
             None
         }
     }
 }
 
 impl<Src, Dst> From<TypedTransform3D<f32, Src, Dst>> for FastTransform<Src, Dst> {
-    fn from(transform: TypedTransform3D<f32, Src, Dst>) -> FastTransform<Src, Dst> {
+    fn from(transform: TypedTransform3D<f32, Src, Dst>) -> Self {
         FastTransform::with_transform(transform)
     }
 }
 
 impl<Src, Dst> Into<TypedTransform3D<f32, Src, Dst>> for FastTransform<Src, Dst> {
     fn into(self) -> TypedTransform3D<f32, Src, Dst> {
         self.to_transform()
     }
 }
 
 impl<Src, Dst> From<TypedVector2D<f32, Src>> for FastTransform<Src, Dst> {
-    fn from(vector: TypedVector2D<f32, Src>) -> FastTransform<Src, Dst> {
+    fn from(vector: TypedVector2D<f32, Src>) -> Self {
         FastTransform::with_vector(vector)
     }
 }
 
 pub type LayoutFastTransform = FastTransform<LayoutPixel, LayoutPixel>;
 pub type LayoutToWorldFastTransform = FastTransform<LayoutPixel, WorldPixel>;
-pub type WorldToLayoutFastTransform = FastTransform<WorldPixel, LayoutPixel>;
\ No newline at end of file
+pub type WorldToLayoutFastTransform = FastTransform<WorldPixel, LayoutPixel>;
--- a/gfx/webrender/tests/angle_shader_validation.rs
+++ b/gfx/webrender/tests/angle_shader_validation.rs
@@ -58,20 +58,16 @@ const SHADERS: &[Shader] = &[
         name: "ps_border_edge",
         features: PRIM_FEATURES,
     },
     Shader {
         name: "ps_split_composite",
         features: PRIM_FEATURES,
     },
     Shader {
-        name: "ps_image",
-        features: PRIM_FEATURES,
-    },
-    Shader {
         name: "ps_text_run",
         features: PRIM_FEATURES,
     },
     // Brush shaders
     Shader {
         name: "brush_yuv_image",
         features: &["", "YUV_NV12", "YUV_PLANAR", "YUV_INTERLEAVED", "YUV_NV12,TEXTURE_RECT"],
     },
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -275,21 +275,17 @@ impl Transaction {
             },
         );
     }
 
     /// Scrolls the scrolling layer under the `cursor`
     ///
     /// WebRender looks for the layer closest to the user
     /// which has `ScrollPolicy::Scrollable` set.
-    pub fn scroll(
-        &mut self,
-        scroll_location: ScrollLocation,
-        cursor: WorldPoint,
-    ) {
+    pub fn scroll(&mut self, scroll_location: ScrollLocation, cursor: WorldPoint) {
         self.frame_ops.push(FrameMsg::Scroll(scroll_location, cursor));
     }
 
     pub fn scroll_node_with_id(
         &mut self,
         origin: LayoutPoint,
         id: ExternalScrollId,
         clamp: ScrollClamping,
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -464,32 +464,25 @@ pub struct RadialGradientDisplayItem {
 
 #[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 scroll_policy: ScrollPolicy,
     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, PartialEq, Serialize)]
-pub enum ScrollPolicy {
-    Scrollable = 0,
-    Fixed = 1,
-}
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum TransformStyle {
     Flat = 0,
     Preserve3D = 1,
 }
 
@@ -831,16 +824,23 @@ impl ClipId {
     }
 
     pub fn is_root_scroll_node(&self) -> bool {
         match *self {
             ClipId::Clip(1, _) => true,
             _ => false,
         }
     }
+
+    pub fn is_root_reference_frame(&self) -> bool {
+        match *self {
+            ClipId::Clip(1, _) => true,
+            _ => false,
+        }
+    }
 }
 
 /// An external identifier that uniquely identifies a scroll frame independent of its ClipId, which
 /// may change from frame to frame. This should be unique within a pipeline. WebRender makes no
 /// attempt to ensure uniqueness. The zero value is reserved for use by the root scroll node of
 /// every pipeline, which always has an external id.
 ///
 /// When setting display lists with the `preserve_frame_state` this id is used to preserve scroll
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -11,24 +11,24 @@ use serde::ser::{Serializer, SerializeSe
 use serde::{Deserialize, Serialize};
 use std::io::{Read, Write};
 use std::marker::PhantomData;
 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, GradientDisplayItem, GradientStop};
-use {IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask, ImageRendering};
-use {LayoutPoint, LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
-use {LineDisplayItem, LineOrientation, LineStyle, MixBlendMode, PipelineId, PropertyBinding};
-use {PushStackingContextDisplayItem, RadialGradient, RadialGradientDisplayItem};
-use {RectangleDisplayItem, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity, Shadow};
-use {SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, StickyOffsetBounds};
-use {TextDisplayItem, TransformStyle, YuvColorSpace, YuvData, YuvImageDisplayItem};
+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};
 
 // 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;
 
@@ -1276,33 +1276,31 @@ impl DisplayListBuilder {
 
         self.push_item(item, info);
     }
 
     pub fn push_stacking_context(
         &mut self,
         info: &LayoutPrimitiveInfo,
         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 {
-                scroll_policy,
                 transform,
                 transform_style,
                 perspective,
                 mix_blend_mode,
                 reference_frame_id,
                 clip_node_id,
                 glyph_raster_space,
             },
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-9a3bc6b965554c04c0bba326cdee45240c3b4ba7
+4811a6e7c06f9dd1b4056e5f5e66983842983ba0
--- a/gfx/wrench/Cargo.toml
+++ b/gfx/wrench/Cargo.toml
@@ -30,15 +30,16 @@ webrender_api = {path = "../webrender_ap
 serde = {version = "1.0", features = ["derive"] }
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-graphics = "0.13"
 core-foundation = "0.5"
 
 [features]
 headless = [ "osmesa-sys", "osmesa-src" ]
+pathfinder = [ "webrender/pathfinder" ]
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
 mozangle = {version = "0.1.5", features = ["egl"]}
 
 [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
 font-loader = "0.6"
--- a/gfx/wrench/src/main.rs
+++ b/gfx/wrench/src/main.rs
@@ -10,17 +10,17 @@ extern crate byteorder;
 extern crate clap;
 #[cfg(target_os = "macos")]
 extern crate core_foundation;
 #[cfg(target_os = "macos")]
 extern crate core_graphics;
 extern crate crossbeam;
 #[cfg(target_os = "windows")]
 extern crate dwrote;
-#[cfg(feature = "logging")]
+#[cfg(feature = "env_logger")]
 extern crate env_logger;
 extern crate euclid;
 #[cfg(any(target_os = "linux", target_os = "macos"))]
 extern crate font_loader;
 extern crate gleam;
 extern crate glutin;
 extern crate image;
 #[macro_use]
@@ -357,17 +357,17 @@ impl RenderNotifier for Notifier {
 }
 
 fn create_notifier() -> (Box<RenderNotifier>, Receiver<NotifierEvent>) {
     let (tx, rx) = channel();
     (Box::new(Notifier { tx: tx }), rx)
 }
 
 fn main() {
-    #[cfg(feature = "logging")]
+    #[cfg(feature = "env_logger")]
     env_logger::init();
 
     let args_yaml = load_yaml!("args.yaml");
     let args = clap::App::from_yaml(args_yaml)
         .setting(clap::AppSettings::ArgRequiredElseHelp)
         .get_matches();
 
     // handle some global arguments
--- a/gfx/wrench/src/rawtest.rs
+++ b/gfx/wrench/src/rawtest.rs
@@ -135,17 +135,17 @@ impl<'a> RawtestHarness<'a> {
         let layout_size = LayoutSize::new(400., 400.);
         let mut resources = ResourceUpdates::new();
         {
             let api = &self.wrench.api;
 
             blob_img = api.generate_image_key();
             resources.add_image(
                 blob_img,
-                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
+                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
                 ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
                 None,
             );
         }
 
         // draw the blob the first time
         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));
@@ -216,24 +216,24 @@ impl<'a> RawtestHarness<'a> {
         let layout_size = LayoutSize::new(400., 400.);
         let mut resources = ResourceUpdates::new();
         let (blob_img, blob_img2) = {
             let api = &self.wrench.api;
 
             blob_img = api.generate_image_key();
             resources.add_image(
                 blob_img,
-                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
+                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
                 ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
                 None,
             );
             blob_img2 = api.generate_image_key();
             resources.add_image(
                 blob_img2,
-                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
+                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
                 ImageData::new_blob_image(blob::serialize_blob(ColorU::new(80, 50, 150, 255))),
                 None,
             );
             (blob_img, blob_img2)
         };
 
         // setup some counters to count how many times each image is requested
         let img1_requested = Arc::new(AtomicIsize::new(0));
@@ -281,38 +281,38 @@ impl<'a> RawtestHarness<'a> {
         self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
         let _pixels_first = self.render_and_get_pixels(window_rect);
 
 
         // update and redraw both images
         let mut resources = ResourceUpdates::new();
         resources.update_image(
             blob_img,
-            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
+            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
             Some(rect(100, 100, 100, 100)),
         );
         resources.update_image(
             blob_img2,
-            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
+            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(59, 50, 150, 255))),
             Some(rect(100, 100, 100, 100)),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         push_images(&mut builder);
         self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
         let _pixels_second = self.render_and_get_pixels(window_rect);
 
 
         // only update the first image
         let mut resources = ResourceUpdates::new();
         resources.update_image(
             blob_img,
-            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
+            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 150, 150, 255))),
             Some(rect(200, 200, 100, 100)),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         push_images(&mut builder);
         self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
         let _pixels_third = self.render_and_get_pixels(window_rect);
@@ -335,17 +335,17 @@ impl<'a> RawtestHarness<'a> {
         );
         let layout_size = LayoutSize::new(400., 400.);
         let mut resources = ResourceUpdates::new();
 
         let blob_img = {
             let img = self.wrench.api.generate_image_key();
             resources.add_image(
                 img,
-                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
+                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
                 ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
                 None,
             );
             img
         };
 
         // draw the blobs the first time
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
@@ -364,17 +364,17 @@ impl<'a> RawtestHarness<'a> {
 
         self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
         let pixels_first = self.render_and_get_pixels(window_rect);
 
         // draw the blob image a second time after updating it with the same color
         let mut resources = ResourceUpdates::new();
         resources.update_image(
             blob_img,
-            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
+            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
             Some(rect(100, 100, 100, 100)),
         );
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
         builder.push_image(
@@ -388,17 +388,17 @@ impl<'a> RawtestHarness<'a> {
 
         self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
         let pixels_second = self.render_and_get_pixels(window_rect);
 
         // draw the blob image a third time after updating it with a different color
         let mut resources = ResourceUpdates::new();
         resources.update_image(
             blob_img,
-            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
+            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 150, 150, 255))),
             Some(rect(200, 200, 100, 100)),
         );
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
         builder.push_image(
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -337,44 +337,60 @@ impl YamlFrameReader {
             Yaml::Array(ref array) => StickyOffsetBounds::new(
                 array[0].as_f32().unwrap_or(0.0),
                 array[1].as_f32().unwrap_or(0.0),
             ),
             _ => StickyOffsetBounds::new(0.0, 0.0),
         }
     }
 
-    pub fn to_clip_id(&self, id: u64, pipeline_id: PipelineId) -> ClipId {
-        if id == 0 {
-            return ClipId::root_scroll_node(pipeline_id);
+    pub fn u64_to_clip_id(&self, number: u64, pipeline_id: PipelineId) -> ClipId {
+        match number {
+            0 => ClipId::root_reference_frame(pipeline_id),
+            1 => ClipId::root_scroll_node(pipeline_id),
+            _ => self.clip_id_map[&number],
         }
-        self.clip_id_map[&id]
+
+    }
+
+    pub fn to_clip_id(&self, item: &Yaml, pipeline_id: PipelineId) -> Option<ClipId> {
+        match *item {
+            Yaml::Integer(value) => Some(self.u64_to_clip_id(value as u64, pipeline_id)),
+            Yaml::String(ref id_string) if id_string == "root-reference-frame" =>
+                Some(ClipId::root_reference_frame(pipeline_id)),
+            Yaml::String(ref id_string) if id_string == "root-scroll-node" =>
+                Some(ClipId::root_scroll_node(pipeline_id)),
+            _ => None,
+        }
+    }
+
+    pub fn add_clip_id_mapping(&mut self, numeric_id: u64, real_id: ClipId) {
+        assert!(numeric_id != 0, "id=0 is reserved for the root reference frame");
+        assert!(numeric_id != 1, "id=1 is reserved for the root scroll node");
+        self.clip_id_map.insert(numeric_id, real_id);
     }
 
     fn to_clip_and_scroll_info(
         &self,
         item: &Yaml,
         pipeline_id: PipelineId
     ) -> Option<ClipAndScrollInfo> {
         match *item {
-            Yaml::Integer(value) => {
-                Some(ClipAndScrollInfo::simple(self.to_clip_id(value as u64, pipeline_id)))
+            Yaml::Array(ref array) if array.len() == 2 => {
+                let scroll_id = match self.to_clip_id(&array[0], pipeline_id) {
+                    Some(id) => id,
+                    None => return None,
+                };
+                let clip_id = match self.to_clip_id(&array[1], pipeline_id) {
+                    Some(id) => id,
+                    None => return None,
+                };
+                Some(ClipAndScrollInfo::new(scroll_id, clip_id))
             }
-            Yaml::Array(ref array) if array.len() == 2 => {
-                let id_ints = (array[0].as_i64(), array[1].as_i64());
-                if let (Some(scroll_node_numeric_id), Some(clip_node_numeric_id)) = id_ints {
-                    Some(ClipAndScrollInfo::new(
-                        self.to_clip_id(scroll_node_numeric_id as u64, pipeline_id),
-                        self.to_clip_id(clip_node_numeric_id as u64, pipeline_id)
-                    ))
-                } else {
-                    None
-                }
-            }
-            _ => None,
+            _ => self.to_clip_id(item, pipeline_id).map(|id| ClipAndScrollInfo::simple(id)),
         }
     }
 
     fn to_hit_testing_tag(&self, item: &Yaml) -> Option<ItemTag> {
         match *item {
             Yaml::Array(ref array) if array.len() == 2 => {
                 match (array[0].as_i64(), array[1].as_i64()) {
                     (Some(first), Some(second)) => Some((first as u64, second as u16)),
@@ -1371,17 +1387,17 @@ impl YamlFrameReader {
             external_id,
             content_rect,
             clip_rect,
             complex_clips,
             image_mask,
             ScrollSensitivity::Script,
         );
         if let Some(numeric_id) = numeric_id {
-            self.clip_id_map.insert(numeric_id, real_id);
+            self.add_clip_id_mapping(numeric_id, real_id);
         }
 
         if !yaml["items"].is_badvalue() {
             dl.push_clip_id(real_id);
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
             dl.pop_clip_id();
         }
     }
@@ -1404,17 +1420,17 @@ impl YamlFrameReader {
                 yaml["margin-left"].as_f32(),
             ),
             self.to_sticky_offset_bounds(&yaml["vertical-offset-bounds"]),
             self.to_sticky_offset_bounds(&yaml["horizontal-offset-bounds"]),
             yaml["previously-applied-offset"].as_vector().unwrap_or(LayoutVector2D::zero()),
         );
 
         if let Some(numeric_id) = numeric_id {
-            self.clip_id_map.insert(numeric_id, real_id);
+            self.add_clip_id_mapping(numeric_id, real_id);
         }
 
         if !yaml["items"].is_badvalue() {
             dl.push_clip_id(real_id);
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
             dl.pop_clip_id();
         }
     }
@@ -1443,41 +1459,39 @@ impl YamlFrameReader {
         dl.pop_all_shadows();
     }
 
     pub fn handle_clip_chain(&mut self, builder: &mut DisplayListBuilder, yaml: &Yaml) {
         let numeric_id = yaml["id"].as_i64().expect("clip chains must have an id");
         let clip_ids: Vec<ClipId> = yaml["clips"]
             .as_vec_u64()
             .unwrap_or_else(Vec::new)
-            .iter().map(|id| self.to_clip_id(*id, builder.pipeline_id))
+            .iter().map(|id| self.u64_to_clip_id(*id, builder.pipeline_id))
             .collect();
 
-        let parent = yaml["parent"].as_i64().map(|id|
-            self.to_clip_id(id as u64, builder.pipeline_id)
-        );
+        let parent = self.to_clip_id(&yaml["parent"], builder.pipeline_id);
         let parent = match parent {
             Some(ClipId::ClipChain(clip_chain_id)) => Some(clip_chain_id),
             Some(_) => panic!("Tried to create a ClipChain with a non-ClipChain parent"),
             None => None,
         };
 
         let real_id = builder.define_clip_chain(parent, clip_ids);
-        self.clip_id_map.insert(numeric_id as u64, ClipId::ClipChain(real_id));
+        self.add_clip_id_mapping(numeric_id as u64, ClipId::ClipChain(real_id));
     }
 
     pub fn handle_clip(&mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, yaml: &Yaml) {
         let clip_rect = yaml["bounds"].as_rect().expect("clip must have a bounds");
         let numeric_id = yaml["id"].as_i64();
         let complex_clips = self.to_complex_clip_regions(&yaml["complex"]);
         let image_mask = self.to_image_mask(&yaml["image-mask"], wrench);
 
         let real_id = dl.define_clip(clip_rect, complex_clips, image_mask);
         if let Some(numeric_id) = numeric_id {
-            self.clip_id_map.insert(numeric_id as u64, real_id);
+            self.add_clip_id_mapping(numeric_id as u64, real_id);
         }
 
         if !yaml["items"].is_badvalue() {
             dl.push_clip_id(real_id);
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
             dl.pop_clip_id();
         }
     }
@@ -1512,63 +1526,66 @@ impl YamlFrameReader {
         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 =
-            yaml["clip-node"].as_i64().map(|id| self.to_clip_id(id as u64, dl.pipeline_id));
+        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 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 scroll_policy = yaml["scroll-policy"]
-            .as_scroll_policy()
-            .unwrap_or(ScrollPolicy::Scrollable);
         let glyph_raster_space = yaml["glyph-raster-space"]
             .as_glyph_raster_space()
             .unwrap_or(GlyphRasterSpace::Screen);
 
         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;
 
-        dl.push_stacking_context(
+        let reference_frame_id = dl.push_stacking_context(
             &info,
             clip_node_id,
-            scroll_policy,
             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();
     }
 }
 
--- a/gfx/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wrench/src/yaml_frame_writer.rs
@@ -180,34 +180,32 @@ fn maybe_radius_yaml(radius: &BorderRadi
 
 fn write_stacking_context(
     parent: &mut Table,
     sc: &StackingContext,
     properties: &SceneProperties,
     filter_iter: AuxIter<FilterOp>,
     clip_id_mapper: &ClipIdMapper,
 ) {
-    enum_node(parent, "scroll-policy", sc.scroll_policy);
-
     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", Yaml::Integer(clip_id_mapper.map_id(&clip_node_id) as i64));
+        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 {
@@ -714,23 +712,17 @@ impl YamlFrameWriter {
             if let Some(tag) = info.tag {
                 yaml_node(
                     &mut v,
                     "hit-testing-tag",
                      Yaml::Array(vec![Yaml::Integer(tag.0 as i64), Yaml::Integer(tag.1 as i64)])
                 );
             }
 
-            let clip_and_scroll_yaml = match clip_id_mapper.map_info(&base.clip_and_scroll()) {
-                (scroll_id, Some(clip_id)) => {
-                    Yaml::Array(vec![Yaml::Integer(scroll_id), Yaml::Integer(clip_id)])
-                }
-                (scroll_id, None) => Yaml::Integer(scroll_id),
-            };
-            yaml_node(&mut v, "clip-and-scroll", clip_and_scroll_yaml);
+            yaml_node(&mut v, "clip-and-scroll", clip_id_mapper.map_info(&base.clip_and_scroll()));
             bool_node(&mut v, "backface-visible", base.is_backface_visible());
 
             match *base.item() {
                 Rectangle(item) => {
                     str_node(&mut v, "type", "rect");
                     color_node(&mut v, "color", item.color);
                 }
                 ClearRectangle => {
@@ -1055,24 +1047,24 @@ impl YamlFrameWriter {
                     }
                 }
                 ClipChain(item) => {
                     str_node(&mut v, "type", "clip-chain");
 
                     let id = ClipId::ClipChain(item.id);
                     u32_node(&mut v, "id", clip_id_mapper.add_id(id) as u32);
 
-                    let clip_ids: Vec<u32> = display_list.get(base.clip_chain_items()).map(|clip_id| {
-                        clip_id_mapper.map_id(&clip_id) as u32
+                    let clip_ids = display_list.get(base.clip_chain_items()).map(|clip_id| {
+                        clip_id_mapper.map_id(&clip_id)
                     }).collect();
-                    u32_vec_node(&mut v, "clips", &clip_ids);
+                    yaml_node(&mut v, "clips", Yaml::Array(clip_ids));
 
                     if let Some(parent) = item.parent {
                         let parent = ClipId::ClipChain(parent);
-                        u32_node(&mut v, "parent", clip_id_mapper.map_id(&parent) as u32);
+                        yaml_node(&mut v, "parent", clip_id_mapper.map_id(&parent));
                     }
                 }
                 ScrollFrame(item) => {
                     str_node(&mut v, "type", "scroll-frame");
                     usize_node(&mut v, "id", clip_id_mapper.add_id(item.scroll_frame_id));
                     size_node(&mut v, "content-size", &base.rect().size);
                     rect_node(&mut v, "bounds", &base.clip_rect());
 
@@ -1222,32 +1214,39 @@ struct ClipIdMapper {
     hash_map: HashMap<ClipId, usize>,
     current_clip_id: usize,
 }
 
 impl ClipIdMapper {
     fn new() -> ClipIdMapper {
         ClipIdMapper {
             hash_map: HashMap::new(),
-            current_clip_id: 1,
+            current_clip_id: 2,
         }
     }
 
     fn add_id(&mut self, id: ClipId) -> usize {
         self.hash_map.insert(id, self.current_clip_id);
         self.current_clip_id += 1;
         self.current_clip_id - 1
     }
 
-    fn map_id(&self, id: &ClipId) -> usize {
-        if id.is_root_scroll_node() {
-            return 0;
+    fn map_id(&self, id: &ClipId) -> Yaml {
+        if id.is_root_reference_frame() {
+            Yaml::String("root-reference-frame".to_owned())
+        } else if id.is_root_scroll_node() {
+            Yaml::String("root-scroll-node".to_owned())
+        } else {
+            Yaml::Integer(*self.hash_map.get(id).unwrap() as i64)
         }
-        *self.hash_map.get(id).unwrap()
     }
 
-    fn map_info(&self, info: &ClipAndScrollInfo) -> (i64, Option<i64>) {
-        (
-            self.map_id(&info.scroll_node_id) as i64,
-            info.clip_node_id.map(|ref id| self.map_id(id) as i64),
-        )
+    fn map_info(&self, info: &ClipAndScrollInfo) -> Yaml {
+        let scroll_node_yaml = self.map_id(&info.scroll_node_id);
+        match info.clip_node_id {
+            Some(ref clip_node_id) => Yaml::Array(vec![
+                scroll_node_yaml,
+                self.map_id(&clip_node_id)
+            ]),
+            None => scroll_node_yaml,
+        }
     }
 }
--- a/gfx/wrench/src/yaml_helper.rs
+++ b/gfx/wrench/src/yaml_helper.rs
@@ -29,17 +29,16 @@ pub trait YamlHelper {
     fn as_pt_to_au(&self) -> Option<Au>;
     fn as_vec_string(&self) -> Option<Vec<String>>;
     fn as_border_radius_component(&self) -> LayoutSize;
     fn as_border_radius(&self) -> Option<BorderRadius>;
     fn as_transform_style(&self) -> Option<TransformStyle>;
     fn as_glyph_raster_space(&self) -> Option<GlyphRasterSpace>;
     fn as_clip_mode(&self) -> Option<ClipMode>;
     fn as_mix_blend_mode(&self) -> Option<MixBlendMode>;
-    fn as_scroll_policy(&self) -> Option<ScrollPolicy>;
     fn as_filter_op(&self) -> Option<FilterOp>;
     fn as_vec_filter_op(&self) -> Option<Vec<FilterOp>>;
 }
 
 fn string_to_color(color: &str) -> Option<ColorF> {
     match color {
         "red" => Some(ColorF::new(1.0, 0.0, 0.0, 1.0)),
         "green" => Some(ColorF::new(0.0, 1.0, 0.0, 1.0)),
@@ -117,18 +116,16 @@ define_string_enum!(
         Exclusion = "exclusion",
         Hue = "hue",
         Saturation = "saturation",
         Color = "color",
         Luminosity = "luminosity"
     ]
 );
 
-define_string_enum!(ScrollPolicy, [Scrollable = "scrollable", Fixed = "fixed"]);
-
 define_string_enum!(
     LineOrientation,
     [Horizontal = "horizontal", Vertical = "vertical"]
 );
 
 define_string_enum!(
     LineStyle,
     [
@@ -535,20 +532,16 @@ impl YamlHelper for Yaml {
             }
         })
     }
 
     fn as_mix_blend_mode(&self) -> Option<MixBlendMode> {
         self.as_str().and_then(|x| StringEnum::from_str(x))
     }
 
-    fn as_scroll_policy(&self) -> Option<ScrollPolicy> {
-        self.as_str().and_then(|x| StringEnum::from_str(x))
-    }
-
     fn as_clip_mode(&self) -> Option<ClipMode> {
         self.as_str().and_then(|x| StringEnum::from_str(x))
     }
 
     fn as_filter_op(&self) -> Option<FilterOp> {
         if let Some(s) = self.as_str() {
             match parse_function(s) {
                 ("blur", ref args, _) if args.len() == 1 => {