Bug 1402321 - Update webrender to commit 9c5f8682e75839ad8c26480b89f87bbb37aa7894. r=jrmuizel
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 28 Sep 2017 14:52:39 -0400
changeset 383651 53da1770ecd10b289fdaddc199431c94b415aaca
parent 383650 13e8329439654bbf9c888d37c6abb342d114a8cd
child 383652 aaf93f2c8f1a433d08fe9a9ae5f3afb956142d5b
push id95615
push userarchaeopteryx@coole-files.de
push dateFri, 29 Sep 2017 11:32:50 +0000
treeherdermozilla-inbound@64949972673f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1402321
milestone58.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 1402321 - Update webrender to commit 9c5f8682e75839ad8c26480b89f87bbb37aa7894. r=jrmuizel MozReview-Commit-ID: KHYwuo29KX
gfx/doc/README.webrender
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_blend.glsl
gfx/webrender/res/ps_image.glsl
gfx/webrender/res/ps_text_run.glsl
gfx/webrender/res/ps_yuv_image.glsl
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/ellipse.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/platform/macos/font.rs
gfx/webrender/src/platform/unix/font.rs
gfx/webrender/src/platform/windows/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender/src/util.rs
gfx/webrender_api/Cargo.toml
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_api/src/font.rs
gfx/webrender_api/src/lib.rs
gfx/webrender_bindings/webrender_ffi_generated.h
toolkit/library/gtest/rust/Cargo.lock
toolkit/library/rust/Cargo.lock
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,10 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: 7a5f60aff33010a44d6acbdc67f27f1f63678b5d
-
+Latest Commit: 9c5f8682e75839ad8c26480b89f87bbb37aa7894
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -544,16 +544,20 @@ TransformVertexInfo write_transform_vert
                                            float z,
                                            Layer layer,
                                            AlphaBatchTask task,
                                            RectWithSize snap_rect) {
     RectWithEndpoint local_rect = to_rect_with_endpoint(instance_rect);
 
     vec2 current_local_pos, prev_local_pos, next_local_pos;
 
+    // Clamp to the two local clip rects.
+    local_rect.p0 = clamp_rect(clamp_rect(local_rect.p0, local_clip_rect), layer.local_clip_rect);
+    local_rect.p1 = clamp_rect(clamp_rect(local_rect.p1, local_clip_rect), layer.local_clip_rect);
+
     // Select the current vertex and the previous/next vertices,
     // based on the vertex ID that is known based on the instance rect.
     switch (gl_VertexID) {
         case 0:
             current_local_pos = vec2(local_rect.p0.x, local_rect.p0.y);
             next_local_pos = vec2(local_rect.p0.x, local_rect.p1.y);
             prev_local_pos = vec2(local_rect.p1.x, local_rect.p0.y);
             break;
--- a/gfx/webrender/res/ps_blend.glsl
+++ b/gfx/webrender/res/ps_blend.glsl
@@ -111,33 +111,47 @@ vec4 Grayscale(vec4 Cs, float amount) {
 
 vec4 HueRotate(vec4 Cs, float amount) {
     vec3 CsHsv = rgbToHsv(Cs.rgb);
     CsHsv.x = mod(CsHsv.x + amount / 6.283185307179586, 1.0);
     return vec4(hsvToRgb(CsHsv), Cs.a);
 }
 
 vec4 Invert(vec4 Cs, float amount) {
-    return mix(Cs, vec4(1.0, 1.0, 1.0, Cs.a) - vec4(Cs.rgb, 0.0), amount);
+    Cs.rgb /= Cs.a;
+
+    vec3 color = mix(Cs.rgb, vec3(1.0) - Cs.rgb, amount);
+
+    // Pre-multiply the alpha into the output value.
+    return vec4(color.rgb * Cs.a, Cs.a);
 }
 
 vec4 Saturate(vec4 Cs, float amount) {
     return vec4(hsvToRgb(min(vec3(1.0, amount, 1.0) * rgbToHsv(Cs.rgb), vec3(1.0))), Cs.a);
 }
 
 vec4 Sepia(vec4 Cs, float amount) {
     float ia = 1.0 - amount;
     return mat4(vec4(0.393 + 0.607 * ia, 0.349 - 0.349 * ia, 0.272 - 0.272 * ia, 0.0),
                 vec4(0.769 - 0.769 * ia, 0.686 + 0.314 * ia, 0.534 - 0.534 * ia, 0.0),
                 vec4(0.189 - 0.189 * ia, 0.168 - 0.168 * ia, 0.131 + 0.869 * ia, 0.0),
                 vec4(0.0, 0.0, 0.0, 1.0)) * Cs;
 }
 
 vec4 Brightness(vec4 Cs, float amount) {
-    return vec4(Cs.rgb * amount, Cs.a);
+    // Un-premultiply the input.
+    Cs.rgb /= Cs.a;
+
+    // Apply the brightness factor.
+    // Resulting color needs to be clamped to output range
+    // since we are pre-multiplying alpha in the shader.
+    vec3 color = clamp(Cs.rgb * amount, vec3(0.0), vec3(1.0));
+
+    // Pre-multiply the alpha into the output value.
+    return vec4(color.rgb * Cs.a, Cs.a);
 }
 
 vec4 Opacity(vec4 Cs, float amount) {
     return Cs * amount;
 }
 
 void main(void) {
     vec2 uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
--- a/gfx/webrender/res/ps_image.glsl
+++ b/gfx/webrender/res/ps_image.glsl
@@ -10,16 +10,17 @@
 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
 varying vec3 vLocalPos;
+flat varying vec4 vLocalRect;
 #else
 varying vec2 vLocalPos;
 #endif
 flat varying vec2 vStretchSize;
 
 #ifdef WR_VERTEX_SHADER
 void main(void) {
     Primitive prim = load_primitive();
@@ -29,16 +30,17 @@ void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect);
     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.layer,
                                  prim.task,
                                  prim.local_rect);
     vLocalPos = vi.local_pos - prim.local_rect.p0;
@@ -84,17 +86,17 @@ void main(void) {
 #ifdef WR_FRAGMENT_SHADER
 void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     float alpha = 0.0;
     vec2 pos = init_transform_fs(vLocalPos, alpha);
 
     // We clamp the texture coordinate calculation here to the local rectangle boundaries,
     // which makes the edge of the texture stretch instead of repeat.
-    vec2 relative_pos_in_rect = clamp(pos, vLocalBounds.xy, vLocalBounds.zw) - vLocalBounds.xy;
+    vec2 relative_pos_in_rect = clamp(pos, vLocalRect.xy, vLocalRect.zw) - vLocalRect.xy;
 #else
     float alpha = 1.0;
     vec2 relative_pos_in_rect = vLocalPos;
 #endif
 
     alpha = min(alpha, do_clip());
 
     // We calculate the particular tile this fragment belongs to, taking into
--- a/gfx/webrender/res/ps_text_run.glsl
+++ b/gfx/webrender/res/ps_text_run.glsl
@@ -65,20 +65,19 @@ void main(void) {
 
 #ifdef WR_FRAGMENT_SHADER
 void main(void) {
     vec3 tc = vec3(clamp(vUv.xy, vUvBorder.xy, vUvBorder.zw), vUv.z);
 #ifdef WR_FEATURE_SUBPIXEL_AA
     //note: the blend mode is not compatible with clipping
     oFragColor = texture(sColor0, tc);
 #else
-    float alpha = texture(sColor0, tc).a;
+    vec4 color = texture(sColor0, tc) * vColor;
 #ifdef WR_FEATURE_TRANSFORM
     float a = 0.0;
     init_transform_fs(vLocalPos, a);
-    alpha *= a;
+    color.a *= a;
 #endif
-    vec4 color = vColor;
-    alpha = min(alpha, do_clip());
-    oFragColor = vec4(vColor.rgb, vColor.a * alpha);
+    color.a = min(color.a, do_clip());
+    oFragColor = color;
 #endif
 }
 #endif
--- a/gfx/webrender/res/ps_yuv_image.glsl
+++ b/gfx/webrender/res/ps_yuv_image.glsl
@@ -14,16 +14,17 @@ flat varying vec2 vTextureSizeY;   // Si
 flat varying vec2 vTextureSizeUv;  // Size of the u and v planes in the texture atlas.
 flat varying vec2 vStretchSize;
 flat varying vec2 vHalfTexelY;     // Normalized length of the half of a Y texel.
 flat varying vec2 vHalfTexelUv;    // Normalized length of the half of u and v texels.
 flat varying vec3 vLayers;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
+flat varying vec4 vLocalRect;
 #else
 varying vec2 vLocalPos;
 #endif
 
 #ifdef WR_VERTEX_SHADER
 struct YuvImage {
     vec2 size;
 };
@@ -38,16 +39,17 @@ void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect);
     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.layer,
                                  prim.task,
                                  prim.local_rect);
     vLocalPos = vi.local_pos - prim.local_rect.p0;
@@ -149,17 +151,17 @@ const mat3 YuvColorMatrix = mat3(
 
 void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     float alpha = 0.0;
     vec2 pos = init_transform_fs(vLocalPos, alpha);
 
     // We clamp the texture coordinate calculation here to the local rectangle boundaries,
     // which makes the edge of the texture stretch instead of repeat.
-    vec2 relative_pos_in_rect = clamp(pos, vLocalBounds.xy, vLocalBounds.zw) - vLocalBounds.xy;
+    vec2 relative_pos_in_rect = clamp(pos, vLocalRect.xy, vLocalRect.zw) - vLocalRect.xy;
 #else
     float alpha = 1.0;;
     vec2 relative_pos_in_rect = vLocalPos;
 #endif
 
     alpha = min(alpha, do_clip());
 
     // We clamp the texture coordinates to the half-pixel offset from the borders
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,15 +1,16 @@
 /* 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, ComplexClipRegion, ImageMask, ImageRendering};
-use api::{DeviceIntRect, LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LocalClip};
+use api::{BorderRadius, ComplexClipRegion, DeviceIntRect, ImageMask, ImageRendering, LayerPoint};
+use api::{LayerRect, LayerSize, LayerToWorldTransform, LayoutPoint, LayoutVector2D, LocalClip};
 use border::BorderCornerClipSource;
+use ellipse::Ellipse;
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use prim_store::{ClipData, ImageMaskData};
 use resource_cache::ResourceCache;
 use std::ops::Not;
 use util::{extract_inner_rect_safe, TransformedRect};
 
 const MAX_CLIP: f32 = 1000000.0;
@@ -107,16 +108,32 @@ impl From<ClipRegion> for ClipSources {
                 ClipMode::Clip,
             ));
         }
 
         ClipSources::new(clips)
     }
 }
 
+impl ClipSource {
+    pub fn contains(&self, point: &LayerPoint) -> bool {
+        // We currently do not handle all types of clip sources, because they
+        // aren't used for ClipScrollNodes and this method is only used during hit testing.
+        match self {
+            &ClipSource::Rectangle(ref rectangle) => rectangle.contains(point),
+            &ClipSource::RoundedRectangle(rect, radii, ClipMode::Clip) =>
+                rounded_rectangle_contains_point(point, &rect, &radii),
+            &ClipSource::RoundedRectangle(rect, radii, ClipMode::ClipOut) =>
+                !rounded_rectangle_contains_point(point, &rect, &radii),
+            _ => unreachable!("Tried to call contains on an unsupported ClipSource."),
+        }
+    }
+
+}
+
 #[derive(Debug)]
 pub struct ClipSources {
     pub clips: Vec<(ClipSource, GpuCacheHandle)>,
     pub bounds: MaskBounds,
 }
 
 impl ClipSources {
     pub fn new(clips: Vec<ClipSource>) -> ClipSources {
@@ -283,8 +300,67 @@ impl MaskBounds {
         }
         if let Some(ref mut inner) = self.inner {
             let transformed =
                 TransformedRect::new(&inner.local_rect, transform, device_pixel_ratio);
             inner.device_rect = transformed.inner_rect;
         }
     }
 }
+
+pub trait Contains {
+    fn contains(&self, point: &LayoutPoint) -> bool;
+}
+
+impl Contains for LocalClip {
+    fn contains(&self, point: &LayoutPoint) -> bool {
+        if !self.clip_rect().contains(point) {
+            return false;
+        }
+        match self {
+            &LocalClip::Rect(..) => true,
+            &LocalClip::RoundedRect(_, complex_clip) => complex_clip.contains(point),
+        }
+    }
+}
+
+impl Contains for ComplexClipRegion {
+    fn contains(&self, point: &LayoutPoint) -> bool {
+        rounded_rectangle_contains_point(point, &self.rect, &self.radii)
+    }
+}
+
+fn rounded_rectangle_contains_point(point: &LayoutPoint,
+                                    rect: &LayerRect,
+                                    radii: &BorderRadius)
+                                    -> bool {
+    if !rect.contains(point) {
+        return false;
+    }
+
+    let top_left_center = rect.origin + radii.top_left.to_vector();
+    if top_left_center.x > point.x && top_left_center.y > point.y &&
+       !Ellipse::new(radii.top_left).contains(*point - top_left_center.to_vector()) {
+        return false;
+    }
+
+    let bottom_right_center = rect.bottom_right() - radii.bottom_right.to_vector();
+    if bottom_right_center.x < point.x && bottom_right_center.y < point.y &&
+       !Ellipse::new(radii.bottom_right).contains(*point - bottom_right_center.to_vector()) {
+        return false;
+    }
+
+    let top_right_center = rect.top_right() +
+                           LayoutVector2D::new(-radii.top_right.width, radii.top_right.height);
+    if top_right_center.x < point.x && top_right_center.y > point.y &&
+       !Ellipse::new(radii.top_right).contains(*point - top_right_center.to_vector()) {
+        return false;
+    }
+
+    let bottom_left_center = rect.bottom_left() +
+                             LayoutVector2D::new(radii.bottom_left.width, -radii.bottom_left.height);
+    if bottom_left_center.x > point.x && bottom_left_center.y < point.y &&
+       !Ellipse::new(radii.bottom_left).contains(*point - bottom_left_center.to_vector()) {
+        return false;
+    }
+
+    true
+}
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -71,19 +71,16 @@ pub enum NodeType {
     /// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here:
     /// https://www.w3.org/TR/css-position-3/#sticky-pos
     StickyFrame(StickyFrameInfo),
 }
 
 /// Contains information common among all types of ClipScrollTree nodes.
 #[derive(Debug)]
 pub struct ClipScrollNode {
-    /// Size of the content inside the scroll region (in logical pixels)
-    pub content_size: LayerSize,
-
     /// Viewing rectangle in the coordinate system of the parent reference frame.
     pub local_viewport_rect: LayerRect,
 
     /// Clip rect of this node - typically the same as viewport rect, except
     /// in overscroll cases.
     pub local_clip_rect: LayerRect,
 
     /// Viewport rectangle clipped against parent layer(s) viewport rectangles.
@@ -123,61 +120,63 @@ impl ClipScrollNode {
     pub fn new_scroll_frame(
         pipeline_id: PipelineId,
         parent_id: ClipId,
         frame_rect: &LayerRect,
         content_size: &LayerSize,
         scroll_sensitivity: ScrollSensitivity,
     ) -> ClipScrollNode {
         ClipScrollNode {
-            content_size: *content_size,
             local_viewport_rect: *frame_rect,
             local_clip_rect: *frame_rect,
             combined_local_viewport_rect: LayerRect::zero(),
             world_viewport_transform: LayerToWorldTransform::identity(),
             world_content_transform: LayerToWorldTransform::identity(),
             reference_frame_relative_scroll_offset: LayerVector2D::zero(),
             parent: Some(parent_id),
             children: Vec::new(),
             pipeline_id,
-            node_type: NodeType::ScrollFrame(ScrollingState::new(scroll_sensitivity)),
+            node_type: NodeType::ScrollFrame(ScrollingState::new(
+                scroll_sensitivity,
+                LayerSize::new(
+                    (content_size.width - frame_rect.size.width).max(0.0),
+                    (content_size.height - frame_rect.size.height).max(0.0)
+                )
+            )),
         }
     }
 
     pub fn new(pipeline_id: PipelineId, parent_id: ClipId, clip_info: ClipInfo) -> ClipScrollNode {
         ClipScrollNode {
-            content_size: clip_info.clip_rect.size,
             local_viewport_rect: clip_info.clip_rect,
             local_clip_rect: clip_info.clip_rect,
             combined_local_viewport_rect: LayerRect::zero(),
             world_viewport_transform: LayerToWorldTransform::identity(),
             world_content_transform: LayerToWorldTransform::identity(),
             reference_frame_relative_scroll_offset: LayerVector2D::zero(),
             parent: Some(parent_id),
             children: Vec::new(),
             pipeline_id,
             node_type: NodeType::Clip(clip_info),
         }
     }
 
     pub fn new_reference_frame(
         parent_id: Option<ClipId>,
         local_viewport_rect: &LayerRect,
-        content_size: LayerSize,
         transform: &LayerToScrollTransform,
         origin_in_parent_reference_frame: LayerVector2D,
         pipeline_id: PipelineId,
     ) -> ClipScrollNode {
         let info = ReferenceFrameInfo {
             transform: *transform,
             origin_in_parent_reference_frame,
         };
 
         ClipScrollNode {
-            content_size,
             local_viewport_rect: *local_viewport_rect,
             local_clip_rect: *local_viewport_rect,
             combined_local_viewport_rect: LayerRect::zero(),
             world_viewport_transform: LayerToWorldTransform::identity(),
             world_content_transform: LayerToWorldTransform::identity(),
             reference_frame_relative_scroll_offset: LayerVector2D::zero(),
             parent: parent_id,
             children: Vec::new(),
@@ -188,17 +187,16 @@ impl ClipScrollNode {
 
     pub fn new_sticky_frame(
         parent_id: ClipId,
         frame_rect: LayerRect,
         sticky_frame_info: StickyFrameInfo,
         pipeline_id: PipelineId,
     ) -> ClipScrollNode {
         ClipScrollNode {
-            content_size: frame_rect.size,
             local_viewport_rect: frame_rect,
             local_clip_rect: frame_rect,
             combined_local_viewport_rect: LayerRect::zero(),
             world_viewport_transform: LayerToWorldTransform::identity(),
             world_content_transform: LayerToWorldTransform::identity(),
             reference_frame_relative_scroll_offset: LayerVector2D::zero(),
             parent: Some(parent_id),
             children: Vec::new(),
@@ -222,18 +220,19 @@ impl ClipScrollNode {
             _ if new_scrolling.offset != LayerVector2D::zero() => {
                 warn!("Tried to scroll a non-scroll node.")
             }
             _ => {}
         }
     }
 
     pub fn set_scroll_origin(&mut self, origin: &LayerPoint, clamp: ScrollClamping) -> bool {
-        let scrollable_height = self.scrollable_height();
-        let scrollable_width = self.scrollable_width();
+        let scrollable_size = self.scrollable_size();
+        let scrollable_width = scrollable_size.width;
+        let scrollable_height = scrollable_size.height;
 
         let scrolling = match self.node_type {
             NodeType::ScrollFrame(ref mut scrolling) => scrolling,
             _ => {
                 warn!("Tried to scroll a non-scroll node.");
                 return false;
             }
         };
@@ -361,28 +360,25 @@ impl ClipScrollNode {
                     (sticky_offset.x + sticky_rect.min_x() + sticky_rect.size.width);
                 sticky_offset.x = sticky_offset.x.min(0.0).max(info.max_offset);
             }
         }
 
         sticky_offset
     }
 
-    pub fn scrollable_height(&self) -> f32 {
-        self.content_size.height - self.local_viewport_rect.size.height
+    pub fn scrollable_size(&self) -> LayerSize {
+        match self.node_type {
+           NodeType:: ScrollFrame(state) => state.scrollable_size,
+            _ => LayerSize::zero(),
+        }
     }
 
-    pub fn scrollable_width(&self) -> f32 {
-        self.content_size.width - self.local_viewport_rect.size.width
-    }
 
     pub fn scroll(&mut self, scroll_location: ScrollLocation, phase: ScrollEventPhase) -> bool {
-        let scrollable_width = self.scrollable_width();
-        let scrollable_height = self.scrollable_height();
-
         let scrolling = match self.node_type {
             NodeType::ScrollFrame(ref mut scrolling) => scrolling,
             _ => return false,
         };
 
         if scrolling.started_bouncing_back && phase == ScrollEventPhase::Move(false) {
             return false;
         }
@@ -394,40 +390,40 @@ impl ClipScrollNode {
                     // Nothing to do on this layer.
                     return false;
                 }
 
                 scrolling.offset.y = 0.0;
                 return true;
             }
             ScrollLocation::End => {
-                let end_pos = self.local_viewport_rect.size.height - self.content_size.height;
-
+                let end_pos = -scrolling.scrollable_size.height;
                 if scrolling.offset.y.round() <= end_pos {
                     // Nothing to do on this layer.
                     return false;
                 }
 
                 scrolling.offset.y = end_pos;
                 return true;
             }
         };
 
-        let overscroll_amount = scrolling.overscroll_amount(scrollable_width, scrollable_height);
-        let overscrolling =
-            CAN_OVERSCROLL && (overscroll_amount.x != 0.0 || overscroll_amount.y != 0.0);
+        let overscroll_amount = scrolling.overscroll_amount();
+        let overscrolling = CAN_OVERSCROLL && (overscroll_amount != LayerVector2D::zero());
         if overscrolling {
             if overscroll_amount.x != 0.0 {
                 delta.x /= overscroll_amount.x.abs()
             }
             if overscroll_amount.y != 0.0 {
                 delta.y /= overscroll_amount.y.abs()
             }
         }
 
+        let scrollable_width = scrolling.scrollable_size.width;
+        let scrollable_height = scrolling.scrollable_size.height;
         let is_unscrollable = scrollable_width <= 0. && scrollable_height <= 0.;
         let original_layer_scroll_offset = scrolling.offset;
 
         if scrollable_width > 0. {
             scrolling.offset.x = scrolling.offset.x + delta.x;
             if is_unscrollable || !CAN_OVERSCROLL {
                 scrolling.offset.x = scrolling.offset.x.min(0.0).max(-scrollable_width).round();
             }
@@ -458,72 +454,80 @@ impl ClipScrollNode {
 
     pub fn tick_scrolling_bounce_animation(&mut self) {
         if let NodeType::ScrollFrame(ref mut scrolling) = self.node_type {
             scrolling.tick_scrolling_bounce_animation();
         }
     }
 
     pub fn ray_intersects_node(&self, cursor: &WorldPoint) -> bool {
-        let inv = self.world_viewport_transform.inverse().unwrap();
+        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_width() <= 0. && self.scrollable_height() <= 0. {
+        if self.scrollable_size() == LayerSize::zero() {
             return false;
         }
+
         ray_intersects_rect(
             p0.to_untyped(),
             p1.to_untyped(),
             self.local_viewport_rect.to_untyped(),
         )
     }
 
     pub fn scroll_offset(&self) -> LayerVector2D {
         match self.node_type {
             NodeType::ScrollFrame(ref scrolling) => scrolling.offset,
             _ => LayerVector2D::zero(),
         }
     }
 
     pub fn is_overscrolling(&self) -> bool {
         match self.node_type {
-            NodeType::ScrollFrame(ref scrolling) => {
-                let overscroll_amount =
-                    scrolling.overscroll_amount(self.scrollable_width(), self.scrollable_height());
-                overscroll_amount.x != 0.0 || overscroll_amount.y != 0.0
-            }
+            NodeType::ScrollFrame(ref state) => state.overscroll_amount() != LayerVector2D::zero(),
             _ => false,
         }
     }
 }
 
 #[derive(Copy, Clone, Debug)]
 pub struct ScrollingState {
     pub offset: LayerVector2D,
     pub spring: Spring,
     pub started_bouncing_back: bool,
     pub bouncing_back: bool,
     pub should_handoff_scroll: bool,
     pub scroll_sensitivity: ScrollSensitivity,
+
+    /// Amount that this ScrollFrame can scroll in both directions.
+    pub scrollable_size: LayerSize,
+
 }
 
 /// Manages scrolling offset, overscroll state, etc.
 impl ScrollingState {
-    pub fn new(scroll_sensitivity: ScrollSensitivity) -> ScrollingState {
+    pub fn new(scroll_sensitivity: ScrollSensitivity,
+               scrollable_size: LayerSize
+    ) -> ScrollingState {
         ScrollingState {
             offset: LayerVector2D::zero(),
             spring: Spring::at(LayerPoint::zero(), STIFFNESS, DAMPING),
             started_bouncing_back: false,
             bouncing_back: false,
             should_handoff_scroll: false,
             scroll_sensitivity,
+            scrollable_size,
         }
     }
 
     pub fn sensitive_to_input_events(&self) -> bool {
         match self.scroll_sensitivity {
             ScrollSensitivity::ScriptAndInputEvents => true,
             ScrollSensitivity::Script => false,
         }
@@ -538,33 +542,29 @@ impl ScrollingState {
     pub fn tick_scrolling_bounce_animation(&mut self) {
         let finished = self.spring.animate();
         self.offset = self.spring.current().to_vector();
         if finished {
             self.bouncing_back = false
         }
     }
 
-    pub fn overscroll_amount(
-        &self,
-        scrollable_width: f32,
-        scrollable_height: f32,
-    ) -> LayerVector2D {
+    pub fn overscroll_amount(&self) -> LayerVector2D {
         let overscroll_x = if self.offset.x > 0.0 {
             -self.offset.x
-        } else if self.offset.x < -scrollable_width {
-            -scrollable_width - self.offset.x
+        } else if self.offset.x < -self.scrollable_size.width {
+            -self.scrollable_size.width - self.offset.x
         } else {
             0.0
         };
 
         let overscroll_y = if self.offset.y > 0.0 {
             -self.offset.y
-        } else if self.offset.y < -scrollable_height {
-            -scrollable_height - self.offset.y
+        } else if self.offset.y < -self.scrollable_size.height {
+            -self.scrollable_size.height - self.offset.y
         } else {
             0.0
         };
 
         LayerVector2D::new(overscroll_x, overscroll_y)
     }
 }
 
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -114,16 +114,76 @@ impl ClipScrollTree {
         })
     }
 
     pub fn find_scrolling_node_at_point(&self, cursor: &WorldPoint) -> ClipId {
         self.find_scrolling_node_at_point_in_node(cursor, self.root_reference_frame_id())
             .unwrap_or(self.topmost_scrolling_node_id())
     }
 
+    pub fn is_point_clipped_in_for_node(
+        &self,
+        point: WorldPoint,
+        node_id: &ClipId,
+        cache: &mut FastHashMap<ClipId, Option<LayerPoint>>,
+        clip_store: &ClipStore
+    ) -> bool {
+        if let Some(point) = cache.get(node_id) {
+            return point.is_some();
+        }
+
+        let node = self.nodes.get(node_id).unwrap();
+        let parent_clipped_in = match node.parent {
+            None => true, // This is the root node.
+            Some(ref parent_id) => {
+                self.is_point_clipped_in_for_node(point, parent_id, cache, clip_store)
+            }
+        };
+
+        if !parent_clipped_in {
+            cache.insert(*node_id, None);
+            return false;
+        }
+
+        let transform = node.world_viewport_transform;
+        let transformed_point = match transform.inverse() {
+            Some(inverted) => inverted.transform_point2d(&point),
+            None => {
+                cache.insert(*node_id, None);
+                return false;
+            }
+        };
+
+        let point_in_layer = transformed_point - node.local_viewport_rect.origin.to_vector();
+        let clip_info = match node.node_type {
+            NodeType::Clip(ref info) => info,
+            _ => {
+                cache.insert(*node_id, Some(point_in_layer));
+                return true;
+            }
+        };
+
+        if !node.local_clip_rect.contains(&transformed_point) {
+            cache.insert(*node_id, None);
+            return false;
+        }
+
+        let point_in_clips = transformed_point - clip_info.clip_rect.origin.to_vector();
+        for &(ref clip, _) in clip_store.get(&clip_info.clip_sources).clips() {
+            if !clip.contains(&point_in_clips) {
+                cache.insert(*node_id, None);
+                return false;
+            }
+        }
+
+        cache.insert(*node_id, Some(point_in_layer));
+
+        true
+    }
+
     pub fn get_scroll_node_state(&self) -> Vec<ScrollLayerState> {
         let mut result = vec![];
         for (id, node) in self.nodes.iter() {
             if let NodeType::ScrollFrame(scrolling) = node.node_type {
                 result.push(ScrollLayerState {
                     id: *id,
                     scroll_offset: scrolling.offset,
                 })
@@ -337,22 +397,27 @@ impl ClipScrollTree {
 
     pub fn add_reference_frame(
         &mut self,
         rect: &LayerRect,
         transform: &LayerToScrollTransform,
         origin_in_parent_reference_frame: LayerVector2D,
         pipeline_id: PipelineId,
         parent_id: Option<ClipId>,
+        root_for_pipeline: bool,
     ) -> ClipId {
-        let reference_frame_id = self.generate_new_clip_id(pipeline_id);
+        let reference_frame_id = if root_for_pipeline {
+            ClipId::root_reference_frame(pipeline_id)
+        } else {
+            self.generate_new_clip_id(pipeline_id)
+        };
+
         let node = ClipScrollNode::new_reference_frame(
             parent_id,
             rect,
-            rect.size,
             transform,
             origin_in_parent_reference_frame,
             pipeline_id,
         );
         self.add_node(node, reference_frame_id);
         reference_frame_id
     }
 
@@ -410,25 +475,25 @@ impl ClipScrollTree {
                 }
                 pt.end_level();
             }
             NodeType::ReferenceFrame(ref info) => {
                 pt.new_level(format!("ReferenceFrame {:?}", info.transform));
             }
             NodeType::ScrollFrame(scrolling_info) => {
                 pt.new_level(format!("ScrollFrame"));
+                pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
                 pt.add_item(format!("scroll.offset: {:?}", scrolling_info.offset));
             }
             NodeType::StickyFrame(sticky_frame_info) => {
                 pt.new_level(format!("StickyFrame"));
                 pt.add_item(format!("sticky info: {:?}", sticky_frame_info));
             }
         }
 
-        pt.add_item(format!("content_size: {:?}", node.content_size));
         pt.add_item(format!(
             "local_viewport_rect: {:?}",
             node.local_viewport_rect
         ));
         pt.add_item(format!("local_clip_rect: {:?}", node.local_clip_rect));
         pt.add_item(format!(
             "combined_local_viewport_rect: {:?}",
             node.combined_local_viewport_rect
@@ -457,9 +522,20 @@ impl ClipScrollTree {
         }
     }
 
     pub fn print_with<T: PrintTreePrinter>(&self, clip_store: &ClipStore, pt: &mut T) {
         if !self.nodes.is_empty() {
             self.print_node(&self.root_reference_frame_id, pt, clip_store);
         }
     }
+
+    pub fn make_node_relative_point_absolute(
+        &self,
+        pipeline_id: Option<PipelineId>,
+        point: &LayerPoint
+    ) -> WorldPoint {
+        pipeline_id.and_then(|id| self.nodes.get(&ClipId::root_reference_frame(id)))
+                   .map(|node| node.world_viewport_transform.transform_point2d(point))
+                   .unwrap_or_else(|| WorldPoint::new(point.x, point.y))
+
+    }
 }
--- a/gfx/webrender/src/ellipse.rs
+++ b/gfx/webrender/src/ellipse.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{LayerPoint, LayerSize};
+use api::{LayerPoint, LayerSize, LayerVector2D};
 use std::f32::consts::FRAC_PI_2;
 
 /// Number of steps to integrate arc length over.
 const STEP_COUNT: usize = 20;
 
 /// Represents an ellipse centred at a local space origin.
 #[derive(Debug, Clone)]
 pub struct Ellipse {
@@ -63,16 +63,72 @@ impl Ellipse {
             self.radius.height * sin_theta,
         );
         let tangent = LayerPoint::new(
             -self.radius.width * sin_theta,
             self.radius.height * cos_theta,
         );
         (point, tangent)
     }
+
+    pub fn contains(&self, point: LayerPoint) -> bool {
+        self.signed_distance(point.to_vector()) <= 0.0
+    }
+
+    /// Find the signed distance from this ellipse given a point.
+    /// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
+    fn signed_distance(&self, point: LayerVector2D) -> f32 {
+        // This algorithm fails for circles, so we handle them here.
+        if self.radius.width == self.radius.height {
+            return point.length() - self.radius.width;
+        }
+
+        let mut p = LayerVector2D::new(point.x.abs(), point.y.abs());
+        let mut ab = self.radius.to_vector();
+        if p.x > p.y {
+            p = p.yx();
+            ab = ab.yx();
+        }
+
+        let l = ab.y * ab.y - ab.x * ab.x;
+
+        let m = ab.x * p.x / l;
+        let n = ab.y * p.y / l;
+        let m2 = m * m;
+        let n2 = n * n;
+
+        let c = (m2 + n2 - 1.0) / 3.0;
+        let c3 = c * c * c;
+
+        let q = c3 + m2 * n2 * 2.0;
+        let d = c3 + m2 * n2;
+        let g = m + m * n2;
+
+        let co = if d < 0.0 {
+            let p = (q / c3).acos() / 3.0;
+            let s = p.cos();
+            let t = p.sin() * (3.0_f32).sqrt();
+            let rx = (-c * (s + t + 2.0) + m2).sqrt();
+            let ry = (-c * (s - t + 2.0) + m2).sqrt();
+            (ry + l.signum() * rx + g.abs() / (rx * ry) - m) / 2.0
+        } else {
+            let h = 2.0 * m * n * d.sqrt();
+            let s = (q + h).signum() * (q + h).abs().powf(1.0 / 3.0);
+            let u = (q - h).signum() * (q - h).abs().powf(1.0 / 3.0);
+            let rx = -s - u - c * 4.0 + 2.0 * m2;
+            let ry = (s - u) * (3.0_f32).sqrt();
+            let rm = (rx * rx + ry * ry).sqrt();
+            let p = ry / (rm - rx).sqrt();
+            (p + 2.0 * g / rm - m) / 2.0
+        };
+
+        let si = (1.0 - co * co).sqrt();
+        let r = LayerVector2D::new(ab.x * co, ab.y * si);
+        return (r - p).length() * (p.y - r.y).signum();
+    }
 }
 
 /// Use Simpsons rule to approximate the arc length of
 /// part of an ellipse. Note that this only works over
 /// the range of [0, pi/2].
 // TODO(gw): This is a simplistic way to estimate the
 // arc length of an ellipse segment. We can probably use
 // a faster / more accurate method!
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -1,20 +1,19 @@
 /* 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::{BuiltDisplayListIter, ClipAndScrollInfo, ClipId, ColorF};
-use api::{ComplexClipRegion, DeviceUintRect, DeviceUintSize, DisplayItemRef, Epoch, FilterOp};
-use api::{ImageDisplayItem, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect, LayerSize,
-          LayerToScrollTransform};
-use api::{LayerVector2D, LayoutSize, LayoutTransform, LocalClip, PipelineId};
-use api::{ScrollClamping, ScrollEventPhase, ScrollLayerState, ScrollLocation};
-use api::{ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext, TileOffset};
-use api::{TransformStyle, WorldPoint};
+use api::{BuiltDisplayListIter, ClipAndScrollInfo, ClipId, ColorF, ComplexClipRegion};
+use api::{DeviceUintRect, DeviceUintSize, DisplayItemRef, Epoch, FilterOp, HitTestFlags};
+use api::{HitTestResult, ImageDisplayItem, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect};
+use api::{LayerSize, LayerToScrollTransform, LayerVector2D, LayoutSize, LayoutTransform};
+use api::{LocalClip, PipelineId, ScrollClamping, ScrollEventPhase, ScrollLayerState};
+use api::{ScrollLocation, ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext};
+use api::{TileOffset, TransformStyle, WorldPoint};
 use clip::ClipRegion;
 use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use euclid::rect;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use internal_types::{FastHashMap, FastHashSet, RendererFrame};
 use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
 use resource_cache::{ResourceCache, TiledImageMap};
@@ -236,16 +235,28 @@ impl Frame {
         &mut self,
         scroll_location: ScrollLocation,
         cursor: WorldPoint,
         phase: ScrollEventPhase,
     ) -> bool {
         self.clip_scroll_tree.scroll(scroll_location, cursor, phase)
     }
 
+    pub fn hit_test(&mut self,
+                    pipeline_id: Option<PipelineId>,
+                    point: WorldPoint,
+                    flags: HitTestFlags)
+                    -> HitTestResult {
+        if let Some(ref builder) = self.frame_builder {
+            builder.hit_test(&self.clip_scroll_tree, pipeline_id, point, flags)
+        } else {
+            HitTestResult::default()
+        }
+    }
+
     pub fn tick_scrolling_bounce_animations(&mut self) {
         self.clip_scroll_tree.tick_scrolling_bounce_animations();
     }
 
     pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
         self.clip_scroll_tree
             .discard_frame_state_for_pipeline(pipeline_id);
     }
@@ -429,16 +440,17 @@ impl Frame {
             let reference_frame_bounds = LayerRect::new(LayerPoint::zero(), bounds.size);
             let mut clip_id = context.apply_scroll_frame_id_replacement(context_scroll_node_id);
             clip_id = context.builder.push_reference_frame(
                 Some(clip_id),
                 pipeline_id,
                 &reference_frame_bounds,
                 &transform,
                 origin,
+                false,
                 &mut self.clip_scroll_tree,
             );
             context.replacements.push((context_scroll_node_id, clip_id));
             reference_frame_relative_offset = LayerVector2D::zero();
         } else {
             reference_frame_relative_offset = LayerVector2D::new(
                 reference_frame_relative_offset.x + bounds.origin.x,
                 reference_frame_relative_offset.y + bounds.origin.y,
@@ -506,16 +518,17 @@ impl Frame {
         let origin = reference_frame_relative_offset + bounds.origin.to_vector();
         let transform = LayerToScrollTransform::create_translation(origin.x, origin.y, 0.0);
         let iframe_reference_frame_id = context.builder.push_reference_frame(
             Some(clip_id),
             pipeline_id,
             &iframe_rect,
             &transform,
             origin,
+            true,
             &mut self.clip_scroll_tree,
         );
 
         context.builder.add_scroll_frame(
             ClipId::root_scroll_node(pipeline_id),
             iframe_reference_frame_id,
             pipeline_id,
             &iframe_rect,
@@ -618,16 +631,17 @@ impl Frame {
                     );
                 }
             }
             SpecificDisplayItem::Line(ref info) => {
                 let prim_info = LayerPrimitiveInfo {
                     rect: LayerRect::zero(),
                     local_clip: *item.local_clip(),
                     is_backface_visible: prim_info.is_backface_visible,
+                    tag: prim_info.tag,
                 };
 
                 context.builder.add_line(
                     clip_and_scroll,
                     &prim_info,
                     info.baseline,
                     info.start,
                     info.end,
@@ -1286,16 +1300,17 @@ fn try_to_add_rectangle_splitting_on_cli
     // less masking some cases.
     let mut clipped_rects = Vec::new();
     subtract_rect(&info.rect, &inner_unclipped_rect, &mut clipped_rects);
 
     let prim_info = LayerPrimitiveInfo {
         rect: inner_unclipped_rect,
         local_clip: LocalClip::from(*info.local_clip.clip_rect()),
         is_backface_visible: info.is_backface_visible,
+        tag: None,
     };
 
     context.builder.add_solid_rectangle(
         *clip_and_scroll,
         &prim_info,
         color,
         PrimitiveFlags::None,
     );
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,25 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{BorderDetails, BorderDisplayItem, BorderRadius, BoxShadowClipMode, ClipAndScrollInfo,
-          ClipId, ColorF};
-use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
-use api::{device_length, ExtendMode, FilterOp, FontInstance, FontRenderMode};
-use api::{GlyphInstance, GlyphOptions, GradientStop};
-use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect,
-          LayerSize};
-use api::{LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation, LineStyle};
-use api::{LocalClip, PipelineId, RepeatMode, ScrollSensitivity, SubpixelDirection, TextShadow};
-use api::{TileOffset, TransformStyle, WorldPixel, YuvColorSpace, YuvData};
+use api::{BorderDetails, BorderDisplayItem, BorderRadius, BoxShadowClipMode, ClipAndScrollInfo};
+use api::{ClipId, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect};
+use api::{DeviceUintSize, ExtendMode, FIND_ALL, FilterOp, FontInstance, FontRenderMode};
+use api::{GlyphInstance, GlyphOptions, GradientStop, HitTestFlags, HitTestItem, HitTestResult};
+use api::{ImageKey, ImageRendering, ItemRange, ItemTag, LayerPoint, LayerPrimitiveInfo, LayerRect};
+use api::{LayerSize, LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation};
+use api::{LineStyle, LocalClip, POINT_RELATIVE_TO_PIPELINE_VIEWPORT, PipelineId, RepeatMode};
+use api::{ScrollSensitivity, SubpixelDirection, TextShadow, TileOffset, TransformStyle};
+use api::{WorldPixel, WorldPoint, YuvColorSpace, YuvData, device_length};
 use app_units::Au;
 use border::ImageBorderSegment;
-use clip::{ClipMode, ClipRegion, ClipSource, ClipSources, ClipStore};
+use clip::{ClipMode, ClipRegion, ClipSource, ClipSources, ClipStore, Contains};
 use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use euclid::{SideOffsets2D, vec2, vec3};
 use frame::FrameId;
 use gpu_cache::GpuCache;
 use internal_types::{FastHashMap, FastHashSet, HardwareCompositeOp};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{BoxShadowPrimitiveCpu, TexelRect, YuvImagePrimitiveCpu};
@@ -60,22 +59,42 @@ fn make_polygon(
 
 #[derive(Clone, Copy)]
 pub struct FrameBuilderConfig {
     pub enable_scrollbars: bool,
     pub default_font_render_mode: FontRenderMode,
     pub debug: bool,
 }
 
+#[derive(Debug)]
+pub struct HitTestingItem {
+    rect: LayerRect,
+    clip: LocalClip,
+    tag: ItemTag,
+}
+
+impl HitTestingItem {
+    fn new(tag: ItemTag, info: &LayerPrimitiveInfo) -> HitTestingItem {
+        HitTestingItem {
+            rect: info.rect,
+            clip: info.local_clip,
+            tag: tag,
+        }
+    }
+}
+
+pub struct HitTestingRun(Vec<HitTestingItem>, ClipAndScrollInfo);
+
 pub struct FrameBuilder {
     screen_size: DeviceUintSize,
     background_color: Option<ColorF>,
     prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     cmds: Vec<PrimitiveRunCmd>,
+    hit_testing_runs: Vec<HitTestingRun>,
     config: FrameBuilderConfig,
 
     stacking_context_store: Vec<StackingContext>,
     clip_scroll_group_store: Vec<ClipScrollGroup>,
     clip_scroll_group_indices: FastHashMap<ClipAndScrollInfo, ClipScrollGroupIndex>,
     packed_layers: Vec<PackedLayer>,
 
     // A stack of the current text-shadow primitives.
@@ -90,29 +109,120 @@ pub struct FrameBuilder {
     /// A stack of stacking contexts used for creating ClipScrollGroups as
     /// primitives are added to the frame.
     stacking_context_stack: Vec<StackingContextIndex>,
 
     /// Whether or not we've pushed a root stacking context for the current pipeline.
     has_root_stacking_context: bool,
 }
 
+pub struct PrimitiveContext<'a> {
+    pub packed_layer_index: PackedLayerIndex,
+    pub packed_layer: &'a PackedLayer,
+    pub device_pixel_ratio: f32,
+
+    // Clip items that apply for this primitive run.
+    // In the future, we'll build these once at the
+    // start of the frame when updating the
+    // clip-scroll tree.
+    pub current_clip_stack: Vec<ClipWorkItem>,
+    pub clip_bounds: DeviceIntRect,
+    pub clip_id: ClipId,
+}
+
+impl<'a> PrimitiveContext<'a> {
+    fn new(
+        packed_layer_index: PackedLayerIndex,
+        packed_layer: &'a PackedLayer,
+        clip_id: ClipId,
+        screen_rect: &DeviceIntRect,
+        clip_scroll_tree: &ClipScrollTree,
+        clip_store: &ClipStore,
+        device_pixel_ratio: f32) -> Option<Self> {
+
+        let mut current_clip_stack = Vec::new();
+        let mut clip_bounds = *screen_rect;
+        let mut current_id = Some(clip_id);
+        // Indicates if the next non-reference-frame that we encounter needs to have its
+        // local combined clip rectangle backed into the clip mask.
+        let mut next_node_needs_region_mask = false;
+        while let Some(id) = current_id {
+            let node = &clip_scroll_tree.nodes.get(&id).unwrap();
+            current_id = node.parent;
+
+            let clip = match node.node_type {
+                NodeType::ReferenceFrame(ref info) => {
+                    // if the transform is non-aligned, bake the next LCCR into the clip mask
+                    next_node_needs_region_mask |= !info.transform.preserves_2d_axis_alignment();
+                    continue;
+                }
+                NodeType::Clip(ref clip) => clip,
+                NodeType::StickyFrame(..) | NodeType::ScrollFrame(..) => {
+                    continue;
+                }
+            };
+
+            let clip_sources = clip_store.get(&clip.clip_sources);
+            if !clip_sources.is_masking() {
+                continue;
+            }
+
+            // apply the screen bounds of the clip node
+            //Note: these are based on the local combined viewport, so can be tighter
+            if let Some((_kind, ref screen_rect)) = clip.screen_bounding_rect {
+                clip_bounds = match clip_bounds.intersection(screen_rect) {
+                    Some(rect) => rect,
+                    None => return None,
+                }
+            }
+
+            // apply the outer device bounds of the clip stack
+            if let Some(ref outer) = clip_sources.bounds.outer {
+                clip_bounds = match clip_bounds.intersection(&outer.device_rect) {
+                    Some(rect) => rect,
+                    None => return None,
+                }
+            }
+
+            //TODO-LCCR: bake a single LCCR instead of all aligned rects?
+            current_clip_stack.push(ClipWorkItem {
+                layer_index: clip.packed_layer_index,
+                clip_sources: clip.clip_sources.weak(),
+                apply_rectangles: next_node_needs_region_mask,
+            });
+            next_node_needs_region_mask = false;
+        }
+
+        current_clip_stack.reverse();
+
+        Some(PrimitiveContext {
+            packed_layer_index,
+            packed_layer,
+            current_clip_stack,
+            clip_bounds,
+            device_pixel_ratio,
+            clip_id,
+        })
+    }
+}
+
 impl FrameBuilder {
     pub fn new(
         previous: Option<FrameBuilder>,
         screen_size: DeviceUintSize,
         background_color: Option<ColorF>,
         config: FrameBuilderConfig,
     ) -> FrameBuilder {
         match previous {
             Some(prev) => FrameBuilder {
                 stacking_context_store: recycle_vec(prev.stacking_context_store),
                 clip_scroll_group_store: recycle_vec(prev.clip_scroll_group_store),
                 clip_scroll_group_indices: FastHashMap::default(),
                 cmds: recycle_vec(prev.cmds),
+                hit_testing_runs: recycle_vec(prev.hit_testing_runs),
                 packed_layers: recycle_vec(prev.packed_layers),
                 shadow_prim_stack: recycle_vec(prev.shadow_prim_stack),
                 scrollbar_prims: recycle_vec(prev.scrollbar_prims),
                 reference_frame_stack: recycle_vec(prev.reference_frame_stack),
                 stacking_context_stack: recycle_vec(prev.stacking_context_stack),
                 prim_store: prev.prim_store.recycle(),
                 clip_store: prev.clip_store.recycle(),
                 screen_size,
@@ -120,16 +230,17 @@ impl FrameBuilder {
                 config,
                 has_root_stacking_context: false,
             },
             None => FrameBuilder {
                 stacking_context_store: Vec::new(),
                 clip_scroll_group_store: Vec::new(),
                 clip_scroll_group_indices: FastHashMap::default(),
                 cmds: Vec::new(),
+                hit_testing_runs: Vec::new(),
                 packed_layers: Vec::new(),
                 shadow_prim_stack: Vec::new(),
                 scrollbar_prims: Vec::new(),
                 reference_frame_stack: Vec::new(),
                 stacking_context_stack: Vec::new(),
                 prim_store: PrimitiveStore::new(),
                 clip_store: ClipStore::new(),
                 screen_size,
@@ -171,22 +282,46 @@ impl FrameBuilder {
         }
 
         let clip_sources = self.clip_store.insert(ClipSources::new(clip_sources));
         let prim_index = self.prim_store.add_primitive(
             &info.rect,
             &info.local_clip.clip_rect(),
             info.is_backface_visible,
             clip_sources,
+            info.tag,
             container,
         );
 
         prim_index
     }
 
+    pub fn add_primitive_to_hit_testing_list(
+        &mut self,
+        info: &LayerPrimitiveInfo,
+        clip_and_scroll: ClipAndScrollInfo
+    ) {
+        let tag = match info.tag {
+            Some(tag) => tag,
+            None => return,
+        };
+
+        let new_item = HitTestingItem::new(tag, info);
+        match self.hit_testing_runs.last_mut() {
+            Some(&mut HitTestingRun(ref mut items, prev_clip_and_scroll))
+                if prev_clip_and_scroll == clip_and_scroll => {
+                items.push(new_item);
+                return;
+            }
+            _ => {}
+        }
+
+        self.hit_testing_runs.push(HitTestingRun(vec![new_item], clip_and_scroll));
+    }
+
     /// Add an already created primitive to the draw lists.
     pub fn add_primitive_to_draw_list(
         &mut self,
         prim_index: PrimitiveIndex,
         clip_and_scroll: ClipAndScrollInfo,
     ) {
         match self.cmds.last_mut().unwrap() {
             &mut PrimitiveRunCmd::PrimitiveRun(
@@ -214,20 +349,20 @@ impl FrameBuilder {
     /// to the draw list.
     pub fn add_primitive(
         &mut self,
         clip_and_scroll: ClipAndScrollInfo,
         info: &LayerPrimitiveInfo,
         clip_sources: Vec<ClipSource>,
         container: PrimitiveContainer,
     ) -> PrimitiveIndex {
+        self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
         let prim_index = self.create_primitive(clip_and_scroll, info, clip_sources, container);
 
         self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
-
         prim_index
     }
 
     pub fn create_clip_scroll_group(&mut self, info: ClipAndScrollInfo) -> ClipScrollGroupIndex {
         let packed_layer_index = PackedLayerIndex(self.packed_layers.len());
         self.packed_layers.push(PackedLayer::empty());
 
         self.clip_scroll_group_store.push(ClipScrollGroup {
@@ -301,24 +436,26 @@ impl FrameBuilder {
 
     pub fn push_reference_frame(
         &mut self,
         parent_id: Option<ClipId>,
         pipeline_id: PipelineId,
         rect: &LayerRect,
         transform: &LayerToScrollTransform,
         origin_in_parent_reference_frame: LayerVector2D,
+        root_for_pipeline: bool,
         clip_scroll_tree: &mut ClipScrollTree,
     ) -> ClipId {
         let new_id = clip_scroll_tree.add_reference_frame(
             rect,
             transform,
             origin_in_parent_reference_frame,
             pipeline_id,
             parent_id,
+            root_for_pipeline,
         );
         self.reference_frame_stack.push(new_id);
         new_id
     }
 
     pub fn current_reference_frame_id(&self) -> ClipId {
         *self.reference_frame_stack.last().unwrap()
     }
@@ -378,16 +515,17 @@ impl FrameBuilder {
         let viewport_rect = LayerRect::new(LayerPoint::zero(), *viewport_size);
         let identity = &LayerToScrollTransform::identity();
         self.push_reference_frame(
             None,
             pipeline_id,
             &viewport_rect,
             identity,
             LayerVector2D::zero(),
+            true,
             clip_scroll_tree,
         );
 
         let topmost_scrolling_node_id = ClipId::root_scroll_node(pipeline_id);
         clip_scroll_tree.topmost_scrolling_node_id = topmost_scrolling_node_id;
 
         self.add_scroll_frame(
             topmost_scrolling_node_id,
@@ -490,16 +628,23 @@ impl FrameBuilder {
         &mut self,
         clip_and_scroll: ClipAndScrollInfo,
         info: &LayerPrimitiveInfo,
         color: &ColorF,
         flags: PrimitiveFlags,
     ) {
         let prim = RectanglePrimitive { color: *color };
 
+        // Don't add transparent rectangles to the draw list, but do consider them for hit
+        // testing. This allows specifying invisible hit testing areas.
+        if color.a == 0.0 {
+            self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
+            return;
+        }
+
         let prim_index = self.add_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
             PrimitiveContainer::Rectangle(prim),
         );
 
         match flags {
@@ -569,16 +714,17 @@ impl FrameBuilder {
         let prim_index = self.create_primitive(
             clip_and_scroll,
             &info,
             Vec::new(),
             PrimitiveContainer::Line(line),
         );
 
         if color.a > 0.0 {
+            self.add_primitive_to_hit_testing_list(&info, clip_and_scroll);
             self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
         }
 
         for shadow_prim_index in &self.shadow_prim_stack {
             let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
             debug_assert_eq!(shadow_metadata.prim_kind, PrimitiveKind::TextShadow);
             let shadow_prim =
                 &mut self.prim_store.cpu_text_shadows[shadow_metadata.cpu_prim_index.0];
@@ -991,63 +1137,73 @@ impl FrameBuilder {
             if let Some(sc_index) = self.stacking_context_stack.last() {
                 let stacking_context = &self.stacking_context_store[sc_index.0];
                 if stacking_context.composite_ops.count() > 0 {
                     normal_render_mode = FontRenderMode::Alpha;
                 }
             }
         }
 
+        let color = match font.render_mode {
+            FontRenderMode::Bitmap => ColorF::new(1.0, 1.0, 1.0, 1.0),
+            FontRenderMode::Subpixel |
+            FontRenderMode::Alpha |
+            FontRenderMode::Mono => *color,
+        };
+
         // Shadows never use subpixel AA, but need to respect the alpha/mono flag
         // for reftests.
         let (shadow_render_mode, subpx_dir) = match default_render_mode {
             FontRenderMode::Subpixel | FontRenderMode::Alpha => {
                 // TODO(gw): Expose subpixel direction in API once WR supports
                 //           vertical text runs.
                 (FontRenderMode::Alpha, font.subpx_dir)
             }
             FontRenderMode::Mono => (FontRenderMode::Mono, SubpixelDirection::None),
+            FontRenderMode::Bitmap => (FontRenderMode::Bitmap, font.subpx_dir),
         };
 
         let prim_font = FontInstance::new(
             font.font_key,
             font.size,
-            *color,
+            color,
             normal_render_mode,
             subpx_dir,
             font.platform_options,
             font.variations.clone(),
             font.synthetic_italics,
         );
         let prim = TextRunPrimitiveCpu {
             font: prim_font,
             glyph_range,
             glyph_count,
             glyph_gpu_blocks: Vec::new(),
             glyph_keys: Vec::new(),
             shadow_render_mode,
             offset: run_offset,
-            color: *color,
+            color: color,
         };
 
         // Text shadows that have a blur radius of 0 need to be rendered as normal
         // text elements to get pixel perfect results for reftests. It's also a big
         // performance win to avoid blurs and render target allocations where
         // possible. For any text shadows that have zero blur, create a normal text
         // primitive with the shadow's color and offset. These need to be added
         // *before* the visual text primitive in order to get the correct paint
         // order. Store them in a Vec first to work around borrowck issues.
         // TODO(gw): Refactor to avoid having to store them in a Vec first.
         let mut fast_text_shadow_prims = Vec::new();
         for shadow_prim_index in &self.shadow_prim_stack {
             let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
             let shadow_prim = &self.prim_store.cpu_text_shadows[shadow_metadata.cpu_prim_index.0];
             if shadow_prim.shadow.blur_radius == 0.0 {
                 let mut text_prim = prim.clone();
-                text_prim.font.color = shadow_prim.shadow.color.into();
+                if font.render_mode != FontRenderMode::Bitmap {
+                    text_prim.font.color = shadow_prim.shadow.color.into();
+                }
                 // If we have translucent text, we need to ensure it won't go
                 // through the subpixel blend mode, which doesn't work with
                 // traditional alpha blending.
                 if shadow_prim.shadow.color.a != 1.0 {
                     text_prim.font.render_mode = text_prim.font.render_mode.limit_by(FontRenderMode::Alpha);
                 }
                 text_prim.color = shadow_prim.shadow.color;
                 text_prim.offset += shadow_prim.shadow.offset;
@@ -1072,16 +1228,17 @@ impl FrameBuilder {
             clip_and_scroll,
             info,
             Vec::new(),
             PrimitiveContainer::TextRun(prim),
         );
 
         // Only add a visual element if it can contribute to the scene.
         if color.a > 0.0 {
+            self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
             self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
         }
 
         // Now add this primitive index to all the currently active text shadow
         // primitives. Although we're adding the indices *after* the visual
         // primitive here, they will still draw before the visual text, since
         // the text-shadow primitive itself has been added to the draw cmd
         // list *before* the visual element, during push_text_shadow. We need
@@ -1379,54 +1536,413 @@ impl FrameBuilder {
         self.add_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
             PrimitiveContainer::YuvImage(prim_cpu),
         );
     }
 
+    fn handle_push_stacking_context(&mut self, stacking_context_index: StackingContextIndex) {
+        self.stacking_context_stack.push(stacking_context_index);
+
+        // Reset bounding rect to zero. We will calculate it as we collect primitives
+        // from various scroll layers. In handle_pop_stacking_context , we use this to
+        // calculate the device bounding rect. In the future, we could cache this during
+        // the initial adding of items for the common case (where there is only a single
+        // scroll layer for items in a stacking context).
+        let stacking_context =
+            &mut self.stacking_context_store[stacking_context_index.0];
+        stacking_context.screen_bounds = DeviceIntRect::zero();
+        stacking_context.isolated_items_bounds = LayerRect::zero();
+    }
+
+    pub fn get_packed_layer_index_if_visible(
+        &self,
+        clip_and_scroll: &ClipAndScrollInfo
+    ) -> Option<PackedLayerIndex> {
+        let group_index = self.clip_scroll_group_indices.get(&clip_and_scroll).unwrap();
+        let clip_scroll_group = &self.clip_scroll_group_store[group_index.0];
+        if clip_scroll_group.is_visible() {
+            Some(clip_scroll_group.packed_layer_index)
+        } else {
+            None
+        }
+    }
+
+    pub fn hit_test(
+        &self,
+        clip_scroll_tree: &ClipScrollTree,
+        pipeline_id: Option<PipelineId>,
+        point: WorldPoint,
+        flags: HitTestFlags
+    ) -> HitTestResult {
+        let point = if flags.contains(POINT_RELATIVE_TO_PIPELINE_VIEWPORT) {
+            let point = LayerPoint::new(point.x, point.y);
+            clip_scroll_tree.make_node_relative_point_absolute(pipeline_id, &point)
+        } else {
+            point
+        };
+
+        let mut node_cache = FastHashMap::default();
+        let mut result = HitTestResult::default();
+        for &HitTestingRun(ref items, ref clip_and_scroll) in self.hit_testing_runs.iter().rev() {
+            let scroll_node = &clip_scroll_tree.nodes[&clip_and_scroll.scroll_node_id];
+            match (pipeline_id, scroll_node.pipeline_id) {
+                (Some(id), node_id) if node_id != id => continue,
+                _ => {},
+            }
+
+            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.contains(&point_in_layer) {
+                    continue;
+                }
+
+                let clip_id = &clip_and_scroll.clip_node_id();
+                if !clipped_in {
+                    clipped_in = clip_scroll_tree.is_point_clipped_in_for_node(point,
+                                                                               clip_id,
+                                                                               &mut node_cache,
+                                                                               &self.clip_store);
+                    if !clipped_in {
+                        break;
+                    }
+                }
+
+                let root_pipeline_reference_frame_id =
+                    ClipId::root_reference_frame(clip_id.pipeline_id());
+                let point_in_viewport = match node_cache.get(&root_pipeline_reference_frame_id) {
+                    Some(&Some(point)) => point,
+                    _ => unreachable!("Hittest target's root reference frame not hit."),
+                };
+
+                result.items.push(HitTestItem {
+                    pipeline: clip_and_scroll.clip_node_id().pipeline_id(),
+                    tag: item.tag,
+                    point_in_viewport,
+                });
+                if !flags.contains(FIND_ALL) {
+                    return result;
+                }
+            }
+        }
+
+        result.items.dedup();
+        return result;
+    }
+
+
+    fn handle_primitive_run(
+        &mut self,
+        base_prim_index: PrimitiveIndex,
+        prim_count: usize,
+        clip_and_scroll: ClipAndScrollInfo,
+        render_tasks: &mut RenderTaskTree,
+        gpu_cache: &mut GpuCache,
+        resource_cache: &mut ResourceCache,
+        pipelines: &FastHashMap<PipelineId, ScenePipeline>,
+        clip_scroll_tree: &ClipScrollTree,
+        screen_rect: &DeviceIntRect,
+        device_pixel_ratio: f32,
+        profile_counters: &mut FrameProfileCounters,
+    ) {
+        let stacking_context_index = *self.stacking_context_stack.last().unwrap();
+        let packed_layer_index =
+            match self.get_packed_layer_index_if_visible(&clip_and_scroll) {
+            Some(index) => index,
+            None => {
+                debug!("{:?} of invisible {:?}", base_prim_index, stacking_context_index);
+                return;
+            }
+        };
+
+        let pipeline_id = {
+            let stacking_context =
+                &mut self.stacking_context_store[stacking_context_index.0];
+            if !stacking_context.can_contribute_to_scene() {
+                return;
+            }
+
+            // At least one primitive in this stacking context is visible, so the stacking
+            // context is visible.
+            stacking_context.is_visible = true;
+            stacking_context.pipeline_id
+        };
+
+        debug!(
+            "\t{:?} of {:?} at {:?}",
+            base_prim_index,
+            stacking_context_index,
+            packed_layer_index
+        );
+
+        let stacking_context =
+            &mut self.stacking_context_store[stacking_context_index.0];
+        let packed_layer = &self.packed_layers[packed_layer_index.0];
+        let display_list = &pipelines
+            .get(&pipeline_id)
+            .expect("No display list?")
+            .display_list;
+
+        if !stacking_context.is_backface_visible && packed_layer.transform.is_backface_visible() {
+            return;
+        }
+
+        let prim_context = PrimitiveContext::new(
+            packed_layer_index,
+            packed_layer,
+            clip_and_scroll.clip_node_id(),
+            screen_rect,
+            clip_scroll_tree,
+            &self.clip_store,
+            device_pixel_ratio,
+        );
+
+        let prim_context = match prim_context {
+            Some(prim_context) => prim_context,
+            None => return,
+        };
+
+        debug!(
+            "\tclip_bounds {:?}, layer_local_clip {:?}",
+            prim_context.clip_bounds,
+            packed_layer.local_clip_rect
+        );
+
+        for i in 0 .. prim_count {
+            let prim_index = PrimitiveIndex(base_prim_index.0 + i);
+
+            if let Some(prim_geom) = self.prim_store.prepare_prim_for_render(
+                prim_index,
+                &prim_context,
+                resource_cache,
+                gpu_cache,
+                display_list,
+                TextRunMode::Normal,
+                render_tasks,
+                &mut self.clip_store,
+            ) {
+                stacking_context.screen_bounds = stacking_context
+                    .screen_bounds
+                    .union(&prim_geom.device_rect);
+                stacking_context.isolated_items_bounds = stacking_context
+                    .isolated_items_bounds
+                    .union(&prim_geom.local_rect);
+
+                profile_counters.visible_primitives.inc();
+            }
+        }
+    }
+
+    fn handle_pop_stacking_context(&mut self, screen_rect: &DeviceIntRect) {
+        let stacking_context_index = self.stacking_context_stack.pop().unwrap();
+
+        let (bounding_rect, is_visible, is_preserve_3d, reference_id, reference_bounds) = {
+            let stacking_context =
+                &mut self.stacking_context_store[stacking_context_index.0];
+            stacking_context.screen_bounds = stacking_context
+                .screen_bounds
+                .intersection(screen_rect)
+                .unwrap_or(DeviceIntRect::zero());
+            (
+                stacking_context.screen_bounds.clone(),
+                stacking_context.is_visible,
+                stacking_context.isolation == ContextIsolation::Items,
+                stacking_context.reference_frame_id,
+                stacking_context
+                    .isolated_items_bounds
+                    .translate(&stacking_context.reference_frame_offset),
+            )
+        };
+
+        if let Some(ref mut parent_index) = self.stacking_context_stack.last_mut() {
+            let parent = &mut self.stacking_context_store[parent_index.0];
+            parent.screen_bounds = parent.screen_bounds.union(&bounding_rect);
+            // add children local bounds only for non-item-isolated contexts
+            if !is_preserve_3d && parent.reference_frame_id == reference_id {
+                let child_bounds = reference_bounds.translate(&-parent.reference_frame_offset);
+                parent.isolated_items_bounds = parent.isolated_items_bounds.union(&child_bounds);
+            }
+            // Per-primitive stacking context visibility checks do not take into account
+            // visibility of child stacking contexts, so do that now.
+            parent.is_visible = parent.is_visible || is_visible;
+        }
+    }
+
+    fn recalculate_clip_scroll_nodes(
+        &mut self,
+        clip_scroll_tree: &mut ClipScrollTree,
+        gpu_cache: &mut GpuCache,
+        resource_cache: &mut ResourceCache,
+        screen_rect: &DeviceIntRect,
+        device_pixel_ratio: f32
+    ) {
+        for (_, ref mut node) in clip_scroll_tree.nodes.iter_mut() {
+            let node_clip_info = match node.node_type {
+                NodeType::Clip(ref mut clip_info) => clip_info,
+                _ => continue,
+            };
+
+            let packed_layer_index = node_clip_info.packed_layer_index;
+            let packed_layer = &mut self.packed_layers[packed_layer_index.0];
+
+            // The coordinates of the mask are relative to the origin of the node itself,
+            // so we need to account for that origin in the transformation we assign to
+            // the packed layer.
+            let transform = node.world_viewport_transform
+                .pre_translate(node.local_viewport_rect.origin.to_vector().to_3d());
+
+            node_clip_info.screen_bounding_rect = if packed_layer.set_transform(transform) {
+                // Meanwhile, the combined viewport rect is relative to the reference frame, so
+                // we move it into the local coordinate system of the node.
+                let local_viewport_rect = node.combined_local_viewport_rect
+                    .translate(&-node.local_viewport_rect.origin.to_vector());
+
+                packed_layer.set_rect(
+                    &local_viewport_rect,
+                    screen_rect,
+                    device_pixel_ratio,
+                )
+            } else {
+                None
+            };
+
+            let clip_sources = self.clip_store.get_mut(&node_clip_info.clip_sources);
+            clip_sources.update(
+                &transform,
+                gpu_cache,
+                resource_cache,
+                device_pixel_ratio,
+            );
+        }
+    }
+
+    fn recalculate_clip_scroll_groups(
+        &mut self,
+        clip_scroll_tree: &ClipScrollTree,
+        screen_rect: &DeviceIntRect,
+        device_pixel_ratio: f32
+    ) {
+        debug!("recalculate_clip_scroll_groups");
+        for ref mut group in &mut self.clip_scroll_group_store {
+            let scroll_node = &clip_scroll_tree.nodes[&group.scroll_node_id];
+            let clip_node = &clip_scroll_tree.nodes[&group.clip_node_id];
+            let packed_layer = &mut self.packed_layers[group.packed_layer_index.0];
+
+            debug!(
+                "\tProcessing group scroll={:?}, clip={:?}",
+                group.scroll_node_id,
+                group.clip_node_id
+            );
+
+            let transform = scroll_node.world_content_transform;
+            if !packed_layer.set_transform(transform) {
+                debug!("\t\tUnable to set transform {:?}", transform);
+                return;
+            }
+
+            // Here we move the viewport rectangle into the coordinate system
+            // of the stacking context content.
+            let local_viewport_rect = clip_node
+                .combined_local_viewport_rect
+                .translate(&clip_node.reference_frame_relative_scroll_offset)
+                .translate(&-scroll_node.reference_frame_relative_scroll_offset)
+                .translate(&-scroll_node.scroll_offset());
+
+            group.screen_bounding_rect = packed_layer.set_rect(
+                &local_viewport_rect,
+                screen_rect,
+                device_pixel_ratio,
+            );
+
+            debug!(
+                "\t\tlocal viewport {:?} screen bound {:?}",
+                local_viewport_rect,
+                group.screen_bounding_rect
+            );
+        }
+    }
+
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(
         &mut self,
         screen_rect: &DeviceIntRect,
         clip_scroll_tree: &mut ClipScrollTree,
         pipelines: &FastHashMap<PipelineId, ScenePipeline>,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         profile_counters: &mut FrameProfileCounters,
         device_pixel_ratio: f32,
     ) {
         profile_scope!("cull");
-        LayerRectCalculationAndCullingPass::create_and_run(
-            self,
-            screen_rect,
+
+        self.recalculate_clip_scroll_nodes(
             clip_scroll_tree,
-            pipelines,
+            gpu_cache,
             resource_cache,
-            gpu_cache,
-            render_tasks,
-            profile_counters,
-            device_pixel_ratio,
+            screen_rect,
+            device_pixel_ratio
+        );
+        self.recalculate_clip_scroll_groups(
+            clip_scroll_tree,
+            screen_rect,
+            device_pixel_ratio
         );
+
+        debug!("processing commands...");
+        let commands = mem::replace(&mut self.cmds, Vec::new());
+        for cmd in &commands {
+            match *cmd {
+                PrimitiveRunCmd::PushStackingContext(stacking_context_index) => {
+                    self.handle_push_stacking_context(stacking_context_index)
+                }
+                PrimitiveRunCmd::PrimitiveRun(prim_index, prim_count, clip_and_scroll) => {
+                    self.handle_primitive_run(
+                        prim_index,
+                        prim_count,
+                        clip_and_scroll,
+                        render_tasks,
+                        gpu_cache,
+                        resource_cache,
+                        pipelines,
+                        clip_scroll_tree,
+                        screen_rect,
+                        device_pixel_ratio,
+                        profile_counters,
+                    );
+                }
+                PrimitiveRunCmd::PopStackingContext => {
+                    self.handle_pop_stacking_context(screen_rect);
+                }
+            }
+        }
+
+        mem::replace(&mut self.cmds, commands);
     }
 
     fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache) {
         let distance_from_edge = 8.0;
 
         for scrollbar_prim in &self.scrollbar_prims {
             let metadata = &mut self.prim_store.cpu_metadata[scrollbar_prim.prim_index.0];
             let clip_scroll_node = &clip_scroll_tree.nodes[&scrollbar_prim.clip_id];
 
             // Invalidate what's in the cache so it will get rebuilt.
             gpu_cache.invalidate(&metadata.gpu_location);
 
-            let scrollable_distance = clip_scroll_node.scrollable_height();
+            let scrollable_distance = clip_scroll_node.scrollable_size().height;
 
             if scrollable_distance <= 0.0 {
                 metadata.local_clip_rect.size = LayerSize::zero();
                 continue;
             }
 
             let scroll_offset = clip_scroll_node.scroll_offset();
             let f = -scroll_offset.y / scrollable_distance;
@@ -1736,17 +2252,17 @@ impl FrameBuilder {
                         continue;
                     }
 
                     debug!("\trun of {} items", prim_count);
 
                     for i in 0 .. prim_count {
                         let prim_index = PrimitiveIndex(first_prim_index.0 + i);
 
-                        if self.prim_store.cpu_bounding_rects[prim_index.0].is_some() {
+                        if self.prim_store.cpu_metadata[prim_index.0].screen_rect.is_some() {
                             self.prim_store
                                 .add_render_tasks_for_prim(prim_index, &mut current_task);
                             let item =
                                 AlphaRenderItem::Primitive(Some(group_index), prim_index, next_z);
                             current_task.as_alpha_batch_mut().items.push(item);
                             next_z += 1;
                         }
                     }
@@ -1869,438 +2385,8 @@ impl FrameBuilder {
             passes,
             layer_texture_data: self.packed_layers.clone(),
             render_tasks,
             deferred_resolves,
             gpu_cache_updates: Some(gpu_cache_updates),
         }
     }
 }
-
-struct LayerRectCalculationAndCullingPass<'a> {
-    frame_builder: &'a mut FrameBuilder,
-    screen_rect: &'a DeviceIntRect,
-    clip_scroll_tree: &'a mut ClipScrollTree,
-    pipelines: &'a FastHashMap<PipelineId, ScenePipeline>,
-    resource_cache: &'a mut ResourceCache,
-    gpu_cache: &'a mut GpuCache,
-    profile_counters: &'a mut FrameProfileCounters,
-    device_pixel_ratio: f32,
-    stacking_context_stack: Vec<StackingContextIndex>,
-    render_tasks: &'a mut RenderTaskTree,
-
-    /// A cached clip info stack, which should handle the most common situation,
-    /// which is that we are using the same clip info stack that we were using
-    /// previously.
-    current_clip_stack: Vec<ClipWorkItem>,
-
-    /// Information about the cached clip stack, which is used to avoid having
-    /// to recalculate it for every primitive.
-    current_clip_info: Option<(ClipId, Option<DeviceIntRect>)>,
-}
-
-impl<'a> LayerRectCalculationAndCullingPass<'a> {
-    fn create_and_run(
-        frame_builder: &'a mut FrameBuilder,
-        screen_rect: &'a DeviceIntRect,
-        clip_scroll_tree: &'a mut ClipScrollTree,
-        pipelines: &'a FastHashMap<PipelineId, ScenePipeline>,
-        resource_cache: &'a mut ResourceCache,
-        gpu_cache: &'a mut GpuCache,
-        render_tasks: &'a mut RenderTaskTree,
-        profile_counters: &'a mut FrameProfileCounters,
-        device_pixel_ratio: f32,
-    ) {
-        let mut pass = LayerRectCalculationAndCullingPass {
-            frame_builder,
-            screen_rect,
-            clip_scroll_tree,
-            pipelines,
-            resource_cache,
-            gpu_cache,
-            profile_counters,
-            device_pixel_ratio,
-            stacking_context_stack: Vec::new(),
-            current_clip_stack: Vec::new(),
-            current_clip_info: None,
-            render_tasks,
-        };
-        pass.run();
-    }
-
-    fn run(&mut self) {
-        self.recalculate_clip_scroll_nodes();
-        self.recalculate_clip_scroll_groups();
-
-        debug!("processing commands...");
-        let commands = mem::replace(&mut self.frame_builder.cmds, Vec::new());
-        for cmd in &commands {
-            match *cmd {
-                PrimitiveRunCmd::PushStackingContext(stacking_context_index) => {
-                    self.handle_push_stacking_context(stacking_context_index)
-                }
-                PrimitiveRunCmd::PrimitiveRun(prim_index, prim_count, clip_and_scroll) => {
-                    self.handle_primitive_run(prim_index, prim_count, clip_and_scroll)
-                }
-                PrimitiveRunCmd::PopStackingContext => self.handle_pop_stacking_context(),
-            }
-        }
-
-        mem::replace(&mut self.frame_builder.cmds, commands);
-    }
-
-    fn recalculate_clip_scroll_nodes(&mut self) {
-        for (_, ref mut node) in self.clip_scroll_tree.nodes.iter_mut() {
-            let node_clip_info = match node.node_type {
-                NodeType::Clip(ref mut clip_info) => clip_info,
-                _ => continue,
-            };
-
-            let packed_layer_index = node_clip_info.packed_layer_index;
-            let packed_layer = &mut self.frame_builder.packed_layers[packed_layer_index.0];
-
-            // The coordinates of the mask are relative to the origin of the node itself,
-            // so we need to account for that origin in the transformation we assign to
-            // the packed layer.
-            let transform = node.world_viewport_transform
-                .pre_translate(node.local_viewport_rect.origin.to_vector().to_3d());
-
-            node_clip_info.screen_bounding_rect = if packed_layer.set_transform(transform) {
-                // Meanwhile, the combined viewport rect is relative to the reference frame, so
-                // we move it into the local coordinate system of the node.
-                let local_viewport_rect = node.combined_local_viewport_rect
-                    .translate(&-node.local_viewport_rect.origin.to_vector());
-
-                packed_layer.set_rect(
-                    &local_viewport_rect,
-                    self.screen_rect,
-                    self.device_pixel_ratio,
-                )
-            } else {
-                None
-            };
-
-            let clip_sources = self.frame_builder
-                .clip_store
-                .get_mut(&node_clip_info.clip_sources);
-            clip_sources.update(
-                &transform,
-                self.gpu_cache,
-                self.resource_cache,
-                self.device_pixel_ratio,
-            );
-        }
-    }
-
-    fn recalculate_clip_scroll_groups(&mut self) {
-        debug!("recalculate_clip_scroll_groups");
-        for ref mut group in &mut self.frame_builder.clip_scroll_group_store {
-            let scroll_node = &self.clip_scroll_tree.nodes[&group.scroll_node_id];
-            let clip_node = &self.clip_scroll_tree.nodes[&group.clip_node_id];
-            let packed_layer = &mut self.frame_builder.packed_layers[group.packed_layer_index.0];
-
-            debug!(
-                "\tProcessing group scroll={:?}, clip={:?}",
-                group.scroll_node_id,
-                group.clip_node_id
-            );
-
-            let transform = scroll_node.world_content_transform;
-            if !packed_layer.set_transform(transform) {
-                debug!("\t\tUnable to set transform {:?}", transform);
-                return;
-            }
-
-            // Here we move the viewport rectangle into the coordinate system
-            // of the stacking context content.
-            let local_viewport_rect = clip_node
-                .combined_local_viewport_rect
-                .translate(&clip_node.reference_frame_relative_scroll_offset)
-                .translate(&-scroll_node.reference_frame_relative_scroll_offset)
-                .translate(&-scroll_node.scroll_offset());
-
-            group.screen_bounding_rect = packed_layer.set_rect(
-                &local_viewport_rect,
-                self.screen_rect,
-                self.device_pixel_ratio,
-            );
-
-            debug!(
-                "\t\tlocal viewport {:?} screen bound {:?}",
-                local_viewport_rect,
-                group.screen_bounding_rect
-            );
-        }
-    }
-
-    fn handle_pop_stacking_context(&mut self) {
-        let stacking_context_index = self.stacking_context_stack.pop().unwrap();
-
-        let (bounding_rect, is_visible, is_preserve_3d, reference_id, reference_bounds) = {
-            let stacking_context =
-                &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
-            stacking_context.screen_bounds = stacking_context
-                .screen_bounds
-                .intersection(self.screen_rect)
-                .unwrap_or(DeviceIntRect::zero());
-            (
-                stacking_context.screen_bounds.clone(),
-                stacking_context.is_visible,
-                stacking_context.isolation == ContextIsolation::Items,
-                stacking_context.reference_frame_id,
-                stacking_context
-                    .isolated_items_bounds
-                    .translate(&stacking_context.reference_frame_offset),
-            )
-        };
-
-        if let Some(ref mut parent_index) = self.stacking_context_stack.last_mut() {
-            let parent = &mut self.frame_builder.stacking_context_store[parent_index.0];
-            parent.screen_bounds = parent.screen_bounds.union(&bounding_rect);
-            // add children local bounds only for non-item-isolated contexts
-            if !is_preserve_3d && parent.reference_frame_id == reference_id {
-                let child_bounds = reference_bounds.translate(&-parent.reference_frame_offset);
-                parent.isolated_items_bounds = parent.isolated_items_bounds.union(&child_bounds);
-            }
-            // Per-primitive stacking context visibility checks do not take into account
-            // visibility of child stacking contexts, so do that now.
-            parent.is_visible = parent.is_visible || is_visible;
-        }
-    }
-
-    fn handle_push_stacking_context(&mut self, stacking_context_index: StackingContextIndex) {
-        self.stacking_context_stack.push(stacking_context_index);
-
-        // Reset bounding rect to zero. We will calculate it as we collect primitives
-        // from various scroll layers. In handle_pop_stacking_context , we use this to
-        // calculate the device bounding rect. In the future, we could cache this during
-        // the initial adding of items for the common case (where there is only a single
-        // scroll layer for items in a stacking context).
-        let stacking_context =
-            &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
-        stacking_context.screen_bounds = DeviceIntRect::zero();
-        stacking_context.isolated_items_bounds = LayerRect::zero();
-    }
-
-    fn rebuild_clip_info_stack_if_necessary(&mut self, clip_id: ClipId) -> Option<DeviceIntRect> {
-        if let Some((current_id, bounding_rect)) = self.current_clip_info {
-            if current_id == clip_id {
-                return bounding_rect;
-            }
-        }
-
-        // TODO(mrobinson): If we notice that this process is expensive, we can special-case
-        // more common situations, such as moving from a child or a parent.
-        self.current_clip_stack.clear();
-        self.current_clip_info = Some((clip_id, None));
-
-        let mut bounding_rect = *self.screen_rect;
-        let mut current_id = Some(clip_id);
-        // Indicates if the next non-reference-frame that we encounter needs to have its
-        // local combined clip rectangle backed into the clip mask.
-        let mut next_node_needs_region_mask = false;
-        while let Some(id) = current_id {
-            let node = &self.clip_scroll_tree.nodes.get(&id).unwrap();
-            current_id = node.parent;
-
-            let clip = match node.node_type {
-                NodeType::ReferenceFrame(ref info) => {
-                    // if the transform is non-aligned, bake the next LCCR into the clip mask
-                    next_node_needs_region_mask |= !info.transform.preserves_2d_axis_alignment();
-                    continue;
-                }
-                NodeType::Clip(ref clip) => clip,
-                NodeType::StickyFrame(..) | NodeType::ScrollFrame(..) => {
-                    continue;
-                }
-            };
-
-            let clip_sources = self.frame_builder.clip_store.get(&clip.clip_sources);
-            if !clip_sources.is_masking() {
-                continue;
-            }
-
-            // apply the screen bounds of the clip node
-            //Note: these are based on the local combined viewport, so can be tighter
-            if let Some((_kind, ref screen_rect)) = clip.screen_bounding_rect {
-                bounding_rect = match bounding_rect.intersection(screen_rect) {
-                    Some(rect) => rect,
-                    None => return None,
-                }
-            }
-
-            // apply the outer device bounds of the clip stack
-            if let Some(ref outer) = clip_sources.bounds.outer {
-                bounding_rect = match bounding_rect.intersection(&outer.device_rect) {
-                    Some(rect) => rect,
-                    None => return None,
-                }
-            }
-
-            //TODO-LCCR: bake a single LCCR instead of all aligned rects?
-            self.current_clip_stack.push(ClipWorkItem {
-                layer_index: clip.packed_layer_index,
-                clip_sources: clip.clip_sources.weak(),
-                apply_rectangles: next_node_needs_region_mask,
-            });
-            next_node_needs_region_mask = false;
-        }
-
-        self.current_clip_stack.reverse();
-        self.current_clip_info = Some((clip_id, Some(bounding_rect)));
-        Some(bounding_rect)
-    }
-
-    fn handle_primitive_run(
-        &mut self,
-        base_prim_index: PrimitiveIndex,
-        prim_count: usize,
-        clip_and_scroll: ClipAndScrollInfo,
-    ) {
-        let stacking_context_index = *self.stacking_context_stack.last().unwrap();
-        let (packed_layer_index, pipeline_id) = {
-            let stacking_context =
-                &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
-            if !stacking_context.can_contribute_to_scene() {
-                return;
-            }
-
-            let group_index = self.frame_builder
-                .clip_scroll_group_indices
-                .get(&clip_and_scroll)
-                .unwrap();
-            let clip_scroll_group = &self.frame_builder.clip_scroll_group_store[group_index.0];
-            if !clip_scroll_group.is_visible() {
-                debug!(
-                    "{:?} of invisible {:?}",
-                    base_prim_index,
-                    stacking_context_index
-                );
-                return;
-            }
-
-            // At least one primitive in this stacking context is visible, so the stacking
-            // context is visible.
-            stacking_context.is_visible = true;
-
-            (
-                clip_scroll_group.packed_layer_index,
-                stacking_context.pipeline_id,
-            )
-        };
-
-
-        debug!(
-            "\t{:?} of {:?} at {:?}",
-            base_prim_index,
-            stacking_context_index,
-            packed_layer_index
-        );
-        let clip_bounds =
-            match self.rebuild_clip_info_stack_if_necessary(clip_and_scroll.clip_node_id()) {
-                Some(rect) => rect,
-                None => return,
-            };
-
-        let stacking_context =
-            &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
-        let packed_layer = &self.frame_builder.packed_layers[packed_layer_index.0];
-        let display_list = &self.pipelines
-            .get(&pipeline_id)
-            .expect("No display list?")
-            .display_list;
-        debug!(
-            "\tclip_bounds {:?}, layer_local_clip {:?}",
-            clip_bounds,
-            packed_layer.local_clip_rect
-        );
-
-        if !stacking_context.is_backface_visible && packed_layer.transform.is_backface_visible() {
-            return;
-        }
-
-        for i in 0 .. prim_count {
-            let prim_index = PrimitiveIndex(base_prim_index.0 + i);
-            let prim_store = &mut self.frame_builder.prim_store;
-            let (prim_local_rect, prim_screen_rect) = match prim_store.build_bounding_rect(
-                prim_index,
-                &clip_bounds,
-                &packed_layer.transform,
-                &packed_layer.local_clip_rect,
-                self.device_pixel_ratio,
-            ) {
-                Some(rects) => rects,
-                None => continue,
-            };
-
-            debug!("\t\t{:?} bound is {:?}", prim_index, prim_screen_rect);
-
-            let prim_metadata = prim_store.prepare_prim_for_render(
-                prim_index,
-                self.resource_cache,
-                self.gpu_cache,
-                &packed_layer.transform,
-                self.device_pixel_ratio,
-                display_list,
-                TextRunMode::Normal,
-                &mut self.render_tasks,
-                &mut self.frame_builder.clip_store,
-            );
-
-            stacking_context.screen_bounds =
-                stacking_context.screen_bounds.union(&prim_screen_rect);
-            stacking_context.isolated_items_bounds = stacking_context
-                .isolated_items_bounds
-                .union(&prim_local_rect);
-
-            // Try to create a mask if we may need to.
-            let prim_clips = self.frame_builder
-                .clip_store
-                .get(&prim_metadata.clip_sources);
-            let clip_task = if prim_clips.is_masking() {
-                // Take into account the actual clip info of the primitive, and
-                // mutate the current bounds accordingly.
-                let mask_rect = match prim_clips.bounds.outer {
-                    Some(ref outer) => match prim_screen_rect.intersection(&outer.device_rect) {
-                        Some(rect) => rect,
-                        None => continue,
-                    },
-                    _ => prim_screen_rect,
-                };
-
-                let extra = ClipWorkItem {
-                    layer_index: packed_layer_index,
-                    clip_sources: prim_metadata.clip_sources.weak(),
-                    apply_rectangles: false,
-                };
-
-                RenderTask::new_mask(
-                    None,
-                    mask_rect,
-                    &self.current_clip_stack,
-                    Some(extra),
-                    prim_screen_rect,
-                    &self.frame_builder.clip_store,
-                )
-            } else if !self.current_clip_stack.is_empty() {
-                // If the primitive doesn't have a specific clip, key the task ID off the
-                // stacking context. This means that two primitives which are only clipped
-                // by the stacking context stack can share clip masks during render task
-                // assignment to targets.
-                RenderTask::new_mask(
-                    Some(clip_and_scroll.clip_node_id()),
-                    clip_bounds,
-                    &self.current_clip_stack,
-                    None,
-                    prim_screen_rect,
-                    &self.frame_builder.clip_store,
-                )
-            } else {
-                None
-            };
-
-            let render_tasks = &mut self.render_tasks;
-            prim_metadata.clip_task_id = clip_task.map(|clip_task| render_tasks.add(clip_task));
-
-            self.profile_counters.visible_primitives.inc();
-        }
-    }
-}
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -241,16 +241,22 @@ impl GlyphRasterizer {
         font: &FontInstance,
         glyph_key: &GlyphKey,
     ) -> Option<GlyphDimensions> {
         self.font_contexts
             .lock_shared_context()
             .get_glyph_dimensions(font, glyph_key)
     }
 
+    pub fn is_bitmap_font(&self, font_key: FontKey) -> bool {
+        self.font_contexts
+            .lock_shared_context()
+            .is_bitmap_font(font_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)
     }
 
     pub fn resolve_glyphs(
         &mut self,
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -6,26 +6,26 @@ use api::{ColorU, FontKey, FontRenderMod
 use api::{FontInstance, FontVariation, NativeFontHandle};
 use api::GlyphKey;
 use app_units::Au;
 use core_foundation::array::{CFArray, CFArrayRef};
 use core_foundation::base::TCFType;
 use core_foundation::dictionary::{CFDictionary, CFDictionaryRef};
 use core_foundation::number::{CFNumber, CFNumberRef};
 use core_foundation::string::{CFString, CFStringRef};
-use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGImageAlphaPremultipliedLast};
+use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGImageAlphaPremultipliedFirst, kCGImageAlphaPremultipliedLast};
 use core_graphics::base::kCGBitmapByteOrder32Little;
 use core_graphics::color_space::CGColorSpace;
 use core_graphics::context::{CGContext, CGTextDrawingMode};
 use core_graphics::data_provider::CGDataProvider;
 use core_graphics::font::{CGFont, CGFontRef, CGGlyph};
 use core_graphics::geometry::{CGPoint, CGRect, CGSize};
 use core_text;
 use core_text::font::{CTFont, CTFontRef};
-use core_text::font_descriptor::kCTFontDefaultOrientation;
+use core_text::font_descriptor::{kCTFontDefaultOrientation, kCTFontColorGlyphsTrait};
 use gamma_lut::{Color as ColorLut, GammaLut};
 use internal_types::FastHashMap;
 use std::collections::hash_map::Entry;
 use std::ptr;
 use std::sync::Arc;
 
 pub struct FontContext {
     cg_fonts: FastHashMap<FontKey, CGFont>,
@@ -417,16 +417,28 @@ impl FontContext {
                 let r = pixel[2];
                 let a = pixel[3];
                 print!("({}, {}, {}, {}) ", r, g, b, a);
             }
             println!("");
         }
     }
 
+    pub fn is_bitmap_font(&mut self, font_key: FontKey) -> bool {
+        match self.get_ct_font(font_key, Au(16 * 60), &[]) {
+            Some(ref ct_font) => {
+                let traits = ct_font.symbolic_traits();
+                (traits & kCTFontColorGlyphsTrait) != 0
+            }
+            None => {
+                false
+            }
+        }
+    }
+
     pub fn rasterize_glyph(
         &mut self,
         font: &FontInstance,
         key: &GlyphKey,
     ) -> Option<RasterizedGlyph> {
         let ct_font = match self.get_ct_font(font.font_key, font.size, &font.variations) {
             Some(font) => font,
             None => return Some(RasterizedGlyph::blank()),
@@ -435,18 +447,25 @@ impl FontContext {
         let glyph = key.index as CGGlyph;
         let (x_offset, y_offset) = font.get_subpx_offset(key);
         let metrics = get_glyph_metrics(&ct_font, glyph, x_offset, y_offset);
         if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
             return Some(RasterizedGlyph::blank());
         }
 
         let context_flags = match font.render_mode {
-            FontRenderMode::Subpixel => kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
-            FontRenderMode::Alpha | FontRenderMode::Mono => kCGImageAlphaPremultipliedLast,
+            FontRenderMode::Subpixel => {
+                kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst
+            }
+            FontRenderMode::Alpha | FontRenderMode::Mono => {
+                kCGImageAlphaPremultipliedLast
+            }
+            FontRenderMode::Bitmap => {
+                kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst
+            }
         };
 
         let mut cg_context = CGContext::create_bitmap_context(
             None,
             metrics.rasterized_width as usize,
             metrics.rasterized_height as usize,
             8,
             metrics.rasterized_width as usize * 4,
@@ -471,20 +490,21 @@ impl FontContext {
         // If we draw grayscale/mono on an opaque background
         // the RGB channels are the alpha values from transparent backgrounds
         // with the alpha set as opaque.
         // At the end of all this, WR expects individual RGB channels and ignores alpha
         // for subpixel AA.
         // For alpha/mono, WR ignores all channels other than alpha.
         // Also note that WR expects text to be black bg with white text, so invert
         // when we draw the glyphs.
-        let (antialias, smooth) = match font.render_mode {
-            FontRenderMode::Subpixel => (true, true),
-            FontRenderMode::Alpha => (true, false),
-            FontRenderMode::Mono => (false, false),
+        let (antialias, smooth, bg_color) = match font.render_mode {
+            FontRenderMode::Subpixel => (true, true, 1.0),
+            FontRenderMode::Alpha => (true, false, 1.0),
+            FontRenderMode::Bitmap => (true, false, 0.0),
+            FontRenderMode::Mono => (false, false, 1.0),
         };
 
         // These are always true in Gecko, even for non-AA fonts
         cg_context.set_allows_font_subpixel_positioning(true);
         cg_context.set_should_subpixel_position_fonts(true);
 
         // Don't quantize because we're doing it already.
         cg_context.set_allows_font_subpixel_quantization(false);
@@ -498,73 +518,73 @@ impl FontContext {
         // CG Origin is bottom left, WR is top left. Need -y offset
         let rasterization_origin = CGPoint {
             x: -metrics.rasterized_left as f64 + x_offset,
             y: metrics.rasterized_descent as f64 - y_offset,
         };
 
         // Always draw black text on a white background
         // Fill the background
-        cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
+        cg_context.set_rgb_fill_color(bg_color, bg_color, bg_color, bg_color);
         let rect = CGRect {
             origin: CGPoint { x: 0.0, y: 0.0 },
             size: CGSize {
                 width: metrics.rasterized_width as f64,
                 height: metrics.rasterized_height as f64,
             },
         };
         cg_context.fill_rect(rect);
 
         // Set the text color
         cg_context.set_rgb_fill_color(0.0, 0.0, 0.0, 1.0);
         cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
         ct_font.draw_glyphs(&[glyph], &[rasterization_origin], cg_context.clone());
 
         let mut rasterized_pixels = cg_context.data().to_vec();
 
-        // Convert to linear space for subpixel AA.
-        // We explicitly do not do this for grayscale AA
-        if font.render_mode == FontRenderMode::Subpixel {
-            self.gamma_lut.coregraphics_convert_to_linear_bgra(
+        if font.render_mode != FontRenderMode::Bitmap {
+            // Convert to linear space for subpixel AA.
+            // We explicitly do not do this for grayscale AA
+            if font.render_mode == FontRenderMode::Subpixel {
+                self.gamma_lut.coregraphics_convert_to_linear_bgra(
+                    &mut rasterized_pixels,
+                    metrics.rasterized_width as usize,
+                    metrics.rasterized_height as usize,
+                );
+            }
+
+            // We need to invert the pixels back since right now
+            // transparent pixels are actually opaque white.
+            for i in 0 .. metrics.rasterized_height {
+                let current_height = (i * metrics.rasterized_width * 4) as usize;
+                let end_row = current_height + (metrics.rasterized_width as usize * 4);
+
+                for pixel in rasterized_pixels[current_height .. end_row].chunks_mut(4) {
+                    pixel[0] = 255 - pixel[0];
+                    pixel[1] = 255 - pixel[1];
+                    pixel[2] = 255 - pixel[2];
+
+                    pixel[3] = match font.render_mode {
+                        FontRenderMode::Subpixel => 255,
+                        _ => {
+                            pixel[0]
+                        }
+                    }; // end match
+                } // end row
+            } // end height
+
+            self.gamma_correct_pixels(
                 &mut rasterized_pixels,
                 metrics.rasterized_width as usize,
                 metrics.rasterized_height as usize,
+                font.render_mode,
+                font.color,
             );
         }
 
-        // We need to invert the pixels back since right now
-        // transparent pixels are actually opaque white.
-        for i in 0 .. metrics.rasterized_height {
-            let current_height = (i * metrics.rasterized_width * 4) as usize;
-            let end_row = current_height + (metrics.rasterized_width as usize * 4);
-
-            for pixel in rasterized_pixels[current_height .. end_row].chunks_mut(4) {
-                pixel[0] = 255 - pixel[0];
-                pixel[1] = 255 - pixel[1];
-                pixel[2] = 255 - pixel[2];
-
-                pixel[3] = match font.render_mode {
-                    FontRenderMode::Subpixel => 255,
-                    _ => {
-                        assert_eq!(pixel[0], pixel[1]);
-                        assert_eq!(pixel[0], pixel[2]);
-                        pixel[0]
-                    }
-                }; // end match
-            } // end row
-        } // end height
-
-        self.gamma_correct_pixels(
-            &mut rasterized_pixels,
-            metrics.rasterized_width as usize,
-            metrics.rasterized_height as usize,
-            font.render_mode,
-            font.color,
-        );
-
         Some(RasterizedGlyph {
             left: metrics.rasterized_left as f32,
             top: metrics.rasterized_ascent as f32,
             width: metrics.rasterized_width,
             height: metrics.rasterized_height,
             bytes: rasterized_pixels,
         })
     }
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -181,17 +181,19 @@ impl FontContext {
         }
 
         // Convert the subpixel offset to floats.
         let (dx, dy) = font.get_subpx_offset(glyph);
 
         // Apply extra pixel of padding for subpixel AA, due to the filter.
         let padding = match font.render_mode {
             FontRenderMode::Subpixel => self.lcd_extra_pixels * 64,
-            FontRenderMode::Alpha | FontRenderMode::Mono => 0,
+            FontRenderMode::Alpha |
+            FontRenderMode::Mono |
+            FontRenderMode::Bitmap => 0,
         };
         cbox.xMin -= padding as FT_Pos;
         cbox.xMax += padding as FT_Pos;
 
         // Offset the bounding box by subpixel positioning.
         // Convert to 26.6 fixed point format for FT.
         match font.subpx_dir {
             SubpixelDirection::None => {}
@@ -257,30 +259,36 @@ impl FontContext {
         &mut self,
         font: &FontInstance,
         key: &GlyphKey,
     ) -> Option<GlyphDimensions> {
         let slot = self.load_glyph(font, key);
         slot.and_then(|slot| self.get_glyph_dimensions_impl(slot, font, key))
     }
 
+    pub fn is_bitmap_font(&mut self, _font_key: FontKey) -> bool {
+        // TODO(gw): Support bitmap fonts in Freetype.
+        false
+    }
+
     pub fn rasterize_glyph(
         &mut self,
         font: &FontInstance,
         key: &GlyphKey,
     ) -> Option<RasterizedGlyph> {
         let slot = match self.load_glyph(font, key) {
             Some(slot) => slot,
             None => return None,
         };
 
         let render_mode = match font.render_mode {
             FontRenderMode::Mono => FT_Render_Mode::FT_RENDER_MODE_MONO,
             FontRenderMode::Alpha => FT_Render_Mode::FT_RENDER_MODE_NORMAL,
             FontRenderMode::Subpixel => FT_Render_Mode::FT_RENDER_MODE_LCD,
+            FontRenderMode::Bitmap => FT_Render_Mode::FT_RENDER_MODE_NORMAL,
         };
 
         // Get dimensions of the glyph, to see if we need to rasterize it.
         let dimensions = match self.get_glyph_dimensions_impl(slot, font, key) {
             Some(val) => val,
             None => return None,
         };
 
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -33,17 +33,17 @@ pub struct RasterizedGlyph {
     pub left: f32,
     pub width: u32,
     pub height: u32,
     pub bytes: Vec<u8>,
 }
 
 fn dwrite_texture_type(render_mode: FontRenderMode) -> dwrote::DWRITE_TEXTURE_TYPE {
     match render_mode {
-        FontRenderMode::Mono => dwrote::DWRITE_TEXTURE_ALIASED_1x1,
+        FontRenderMode::Mono | FontRenderMode::Bitmap => dwrote::DWRITE_TEXTURE_ALIASED_1x1,
         FontRenderMode::Alpha | FontRenderMode::Subpixel => dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1,
     }
 }
 
 fn dwrite_measure_mode(
     render_mode: FontRenderMode,
     options: Option<FontInstancePlatformOptions>,
 ) -> dwrote::DWRITE_MEASURING_MODE {
@@ -51,17 +51,17 @@ fn dwrite_measure_mode(
         force_gdi_rendering: true,
         ..
     }) = options
     {
         return dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC;
     }
 
     match render_mode {
-        FontRenderMode::Mono => dwrote::DWRITE_MEASURING_MODE_GDI_NATURAL,
+        FontRenderMode::Mono | FontRenderMode::Bitmap => dwrote::DWRITE_MEASURING_MODE_GDI_NATURAL,
         FontRenderMode::Alpha | FontRenderMode::Subpixel => dwrote::DWRITE_MEASURING_MODE_NATURAL,
     }
 }
 
 fn dwrite_render_mode(
     font_face: &dwrote::FontFace,
     render_mode: FontRenderMode,
     em_size: f32,
@@ -72,17 +72,17 @@ fn dwrite_render_mode(
         force_gdi_rendering: true,
         ..
     }) = options
     {
         return dwrote::DWRITE_RENDERING_MODE_GDI_CLASSIC;
     }
 
     let dwrite_render_mode = match render_mode {
-        FontRenderMode::Mono => dwrote::DWRITE_RENDERING_MODE_ALIASED,
+        FontRenderMode::Mono | FontRenderMode::Bitmap => dwrote::DWRITE_RENDERING_MODE_ALIASED,
         FontRenderMode::Alpha | FontRenderMode::Subpixel => {
             font_face.get_recommended_rendering_mode_default_params(em_size, 1.0, measure_mode)
         }
     };
 
     if dwrite_render_mode == dwrote::DWRITE_RENDERING_MODE_OUTLINE {
         // Outline mode is not supported
         return dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC;
@@ -259,16 +259,19 @@ impl FontContext {
                 }
             })
     }
 
     // DWRITE gives us values in RGB. WR doesn't really touch it after. Note, CG returns in BGR
     // TODO: Decide whether all fonts should return RGB or BGR
     fn convert_to_rgba(&self, pixels: &[u8], render_mode: FontRenderMode) -> Vec<u8> {
         match render_mode {
+            FontRenderMode::Bitmap => {
+                unreachable!("TODO: bitmap fonts");
+            }
             FontRenderMode::Mono => {
                 let mut rgba_pixels: Vec<u8> = vec![0; pixels.len() * 4];
                 for i in 0 .. pixels.len() {
                     rgba_pixels[i * 4 + 0] = pixels[i];
                     rgba_pixels[i * 4 + 1] = pixels[i];
                     rgba_pixels[i * 4 + 2] = pixels[i];
                     rgba_pixels[i * 4 + 3] = pixels[i];
                 }
@@ -296,16 +299,21 @@ impl FontContext {
                     rgba_pixels[i * 4 + 2] = pixels[i * 3 + 2];
                     rgba_pixels[i * 4 + 3] = 0xff;
                 }
                 rgba_pixels
             }
         }
     }
 
+    pub fn is_bitmap_font(&mut self, _font_key: FontKey) -> bool {
+        // TODO(gw): Support bitmap fonts in DWrite.
+        false
+    }
+
     pub fn rasterize_glyph(
         &mut self,
         font: &FontInstance,
         key: &GlyphKey,
     ) -> Option<RasterizedGlyph> {
         let analysis = self.create_glyph_analysis(font, key);
         let texture_type = dwrite_texture_type(font.render_mode);
 
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,24 +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::{BorderRadius, ExtendMode, FontRenderMode, GlyphInstance, GradientStop};
-use api::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint};
-use api::{device_length, FontInstance, LayerVector2D, LineOrientation, LineStyle};
-use api::{GlyphKey, LayerToWorldTransform, TileOffset, YuvColorSpace, YuvFormat};
-use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow};
+use api::{BorderRadius, BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize};
+use api::{DevicePoint, ExtendMode, FontInstance, FontRenderMode, GlyphInstance, GlyphKey};
+use api::{GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, LayerPoint, LayerRect};
+use api::{LayerSize, LayerVector2D, LineOrientation, LineStyle, TextShadow};
+use api::{TileOffset, YuvColorSpace, YuvFormat, device_length};
 use app_units::Au;
 use border::BorderCornerInstance;
-use clip::{ClipMode, ClipSourcesHandle, ClipStore};
+use clip::{ClipMode, ClipSourcesHandle, ClipStore, Geometry};
 use euclid::Size2D;
+use frame_builder::PrimitiveContext;
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
-use render_task::{RenderTask, RenderTaskId, RenderTaskTree};
+use render_task::{ClipWorkItem, RenderTask, RenderTaskId, RenderTaskTree};
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 use resource_cache::{ImageProperties, ResourceCache};
 use std::{mem, usize};
 use util::{pack_as_float, recycle_vec, TransformedRect};
 
 #[derive(Debug, Copy, Clone)]
 pub struct PrimitiveOpacity {
     pub is_opaque: bool,
@@ -139,16 +140,21 @@ pub struct PrimitiveMetadata {
     pub clip_task_id: Option<RenderTaskId>,
 
     // TODO(gw): In the future, we should just pull these
     //           directly from the DL item, instead of
     //           storing them here.
     pub local_rect: LayerRect,
     pub local_clip_rect: LayerRect,
     pub is_backface_visible: bool,
+    pub screen_rect: Option<DeviceIntRect>,
+
+    /// A tag used to identify this primitive outside of WebRender. This is
+    /// used for returning useful data during hit testing.
+    pub tag: Option<ItemTag>,
 }
 
 #[derive(Debug)]
 #[repr(C)]
 pub struct RectanglePrimitive {
     pub color: ColorF,
 }
 
@@ -802,17 +808,16 @@ pub enum PrimitiveContainer {
     RadialGradient(RadialGradientPrimitiveCpu),
     BoxShadow(BoxShadowPrimitiveCpu),
     TextShadow(TextShadowPrimitiveCpu),
     Line(LinePrimitive),
 }
 
 pub struct PrimitiveStore {
     /// CPU side information only.
-    pub cpu_bounding_rects: Vec<Option<DeviceIntRect>>,
     pub cpu_rectangles: Vec<RectanglePrimitive>,
     pub cpu_text_runs: Vec<TextRunPrimitiveCpu>,
     pub cpu_text_shadows: Vec<TextShadowPrimitiveCpu>,
     pub cpu_images: Vec<ImagePrimitiveCpu>,
     pub cpu_yuv_images: Vec<YuvImagePrimitiveCpu>,
     pub cpu_gradients: Vec<GradientPrimitiveCpu>,
     pub cpu_radial_gradients: Vec<RadialGradientPrimitiveCpu>,
     pub cpu_metadata: Vec<PrimitiveMetadata>,
@@ -821,34 +826,32 @@ pub struct PrimitiveStore {
     pub cpu_lines: Vec<LinePrimitive>,
 }
 
 impl PrimitiveStore {
     pub fn new() -> PrimitiveStore {
         PrimitiveStore {
             cpu_metadata: Vec::new(),
             cpu_rectangles: Vec::new(),
-            cpu_bounding_rects: Vec::new(),
             cpu_text_runs: Vec::new(),
             cpu_text_shadows: Vec::new(),
             cpu_images: Vec::new(),
             cpu_yuv_images: Vec::new(),
             cpu_gradients: Vec::new(),
             cpu_radial_gradients: Vec::new(),
             cpu_borders: Vec::new(),
             cpu_box_shadows: Vec::new(),
             cpu_lines: Vec::new(),
         }
     }
 
     pub fn recycle(self) -> Self {
         PrimitiveStore {
             cpu_metadata: recycle_vec(self.cpu_metadata),
             cpu_rectangles: recycle_vec(self.cpu_rectangles),
-            cpu_bounding_rects: recycle_vec(self.cpu_bounding_rects),
             cpu_text_runs: recycle_vec(self.cpu_text_runs),
             cpu_text_shadows: recycle_vec(self.cpu_text_shadows),
             cpu_images: recycle_vec(self.cpu_images),
             cpu_yuv_images: recycle_vec(self.cpu_yuv_images),
             cpu_gradients: recycle_vec(self.cpu_gradients),
             cpu_radial_gradients: recycle_vec(self.cpu_radial_gradients),
             cpu_borders: recycle_vec(self.cpu_borders),
             cpu_box_shadows: recycle_vec(self.cpu_box_shadows),
@@ -857,196 +860,156 @@ impl PrimitiveStore {
     }
 
     pub fn add_primitive(
         &mut self,
         local_rect: &LayerRect,
         local_clip_rect: &LayerRect,
         is_backface_visible: bool,
         clip_sources: ClipSourcesHandle,
+        tag: Option<ItemTag>,
         container: PrimitiveContainer,
     ) -> PrimitiveIndex {
         let prim_index = self.cpu_metadata.len();
-        self.cpu_bounding_rects.push(None);
+
+        let base_metadata = PrimitiveMetadata {
+            clip_sources,
+            gpu_location: GpuCacheHandle::new(),
+            clip_task_id: None,
+            local_rect: *local_rect,
+            local_clip_rect: *local_clip_rect,
+            is_backface_visible: is_backface_visible,
+            screen_rect: None,
+            tag,
+
+            opacity: PrimitiveOpacity::translucent(),
+            prim_kind: PrimitiveKind::Rectangle,
+            cpu_prim_index: SpecificPrimitiveIndex(0),
+        };
 
         let metadata = match container {
             PrimitiveContainer::Rectangle(rect) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::from_alpha(rect.color.a),
-                    clip_sources,
                     prim_kind: PrimitiveKind::Rectangle,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_rectangles.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_rectangles.push(rect);
 
                 metadata
             }
             PrimitiveContainer::Line(line) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::Line,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_lines.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_lines.push(line);
                 metadata
             }
             PrimitiveContainer::TextRun(text_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::TextRun,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_runs.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_text_runs.push(text_cpu);
                 metadata
             }
             PrimitiveContainer::TextShadow(text_shadow) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::TextShadow,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_shadows.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_text_shadows.push(text_shadow);
                 metadata
             }
             PrimitiveContainer::Image(image_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::Image,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_images.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_images.push(image_cpu);
                 metadata
             }
             PrimitiveContainer::YuvImage(image_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::opaque(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::YuvImage,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_yuv_images.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_yuv_images.push(image_cpu);
                 metadata
             }
             PrimitiveContainer::Border(border_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::Border,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_borders.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_borders.push(border_cpu);
                 metadata
             }
             PrimitiveContainer::AlignedGradient(gradient_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::AlignedGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_gradients.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_gradients.push(gradient_cpu);
                 metadata
             }
             PrimitiveContainer::AngleGradient(gradient_cpu) => {
                 let metadata = PrimitiveMetadata {
                     // TODO: calculate if the gradient is actually opaque
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::AngleGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_gradients.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_gradients.push(gradient_cpu);
                 metadata
             }
             PrimitiveContainer::RadialGradient(radial_gradient_cpu) => {
                 let metadata = PrimitiveMetadata {
                     // TODO: calculate if the gradient is actually opaque
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::RadialGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_radial_gradients.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_radial_gradients.push(radial_gradient_cpu);
                 metadata
             }
             PrimitiveContainer::BoxShadow(box_shadow) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clip_sources,
                     prim_kind: PrimitiveKind::BoxShadow,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_box_shadows.len()),
-                    gpu_location: GpuCacheHandle::new(),
-                    clip_task_id: None,
-                    local_rect: *local_rect,
-                    local_clip_rect: *local_clip_rect,
-                    is_backface_visible: is_backface_visible,
+                    ..base_metadata
                 };
 
                 self.cpu_box_shadows.push(box_shadow);
                 metadata
             }
         };
 
         self.cpu_metadata.push(metadata);
@@ -1057,44 +1020,16 @@ impl PrimitiveStore {
     pub fn get_metadata(&self, index: PrimitiveIndex) -> &PrimitiveMetadata {
         &self.cpu_metadata[index.0]
     }
 
     pub fn prim_count(&self) -> usize {
         self.cpu_metadata.len()
     }
 
-    pub fn build_bounding_rect(
-        &mut self,
-        prim_index: PrimitiveIndex,
-        screen_rect: &DeviceIntRect,
-        layer_transform: &LayerToWorldTransform,
-        layer_combined_local_clip_rect: &LayerRect,
-        device_pixel_ratio: f32,
-    ) -> Option<(LayerRect, DeviceIntRect)> {
-        let metadata = &self.cpu_metadata[prim_index.0];
-
-        if !metadata.is_backface_visible && layer_transform.is_backface_visible() {
-            return None;
-        }
-
-        let local_rect = metadata
-            .local_rect
-            .intersection(&metadata.local_clip_rect)
-            .and_then(|rect| rect.intersection(layer_combined_local_clip_rect));
-
-        let bounding_rect = local_rect.and_then(|local_rect| {
-            let xf_rect = TransformedRect::new(&local_rect, layer_transform, device_pixel_ratio);
-            xf_rect.bounding_rect.intersection(screen_rect)
-        });
-
-        self.cpu_bounding_rects[prim_index.0] = bounding_rect;
-        bounding_rect.map(|screen_bound| (local_rect.unwrap(), screen_bound))
-    }
-
     /// Add any task dependencies for this primitive to the provided task.
     pub fn add_render_tasks_for_prim(&self, prim_index: PrimitiveIndex, task: &mut RenderTask) {
         // Add any dynamic render tasks needed to render this primitive
         let metadata = &self.cpu_metadata[prim_index.0];
 
         let render_task_id = match metadata.prim_kind {
             PrimitiveKind::BoxShadow => {
                 let box_shadow = &self.cpu_box_shadows[metadata.cpu_prim_index.0];
@@ -1123,71 +1058,145 @@ impl PrimitiveStore {
             task.children.push(clip_task_id);
         }
     }
 
     /// Returns true if the bounding box needs to be updated.
     pub fn prepare_prim_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
+        prim_context: &PrimitiveContext,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
-        layer_transform: &LayerToWorldTransform,
-        device_pixel_ratio: f32,
         display_list: &BuiltDisplayList,
         text_run_mode: TextRunMode,
         render_tasks: &mut RenderTaskTree,
         clip_store: &mut ClipStore,
-    ) -> &mut PrimitiveMetadata {
-        let (prim_kind, cpu_prim_index) = {
-            let metadata = &self.cpu_metadata[prim_index.0];
-            (metadata.prim_kind, metadata.cpu_prim_index)
+    ) -> Option<Geometry> {
+        let (prim_local_rect, prim_screen_rect, prim_kind, cpu_prim_index) = {
+            let metadata = &mut self.cpu_metadata[prim_index.0];
+            metadata.screen_rect = None;
+
+            if !metadata.is_backface_visible &&
+               prim_context.packed_layer.transform.is_backface_visible() {
+                return None;
+            }
+
+            let local_rect = metadata
+                .local_rect
+                .intersection(&metadata.local_clip_rect)
+                .and_then(|rect| rect.intersection(&prim_context.packed_layer.local_clip_rect));
+
+            let local_rect = match local_rect {
+                Some(local_rect) => local_rect,
+                None => return None,
+            };
+
+            let xf_rect = TransformedRect::new(
+                &local_rect,
+                &prim_context.packed_layer.transform,
+                prim_context.device_pixel_ratio
+            );
+
+            metadata.screen_rect = xf_rect
+                .bounding_rect
+                .intersection(&prim_context.clip_bounds);
+
+            match metadata.screen_rect {
+                Some(screen_rect) => (local_rect, screen_rect, metadata.prim_kind, metadata.cpu_prim_index),
+                None => return None,
+            }
         };
 
         // Recurse into any sub primitives and prepare them for rendering first.
         // TODO(gw): This code is a bit hacky to work around the borrow checker.
         //           Specifically, the clone() below on the primitive list for
         //           text shadow primitives. Consider restructuring this code to
         //           avoid borrow checker issues.
         if prim_kind == PrimitiveKind::TextShadow {
             for sub_prim_index in self.cpu_text_shadows[cpu_prim_index.0].primitives.clone() {
                 self.prepare_prim_for_render(
                     sub_prim_index,
+                    prim_context,
                     resource_cache,
                     gpu_cache,
-                    layer_transform,
-                    device_pixel_ratio,
                     display_list,
                     TextRunMode::Shadow,
                     render_tasks,
                     clip_store,
                 );
             }
         }
 
         let metadata = &mut self.cpu_metadata[prim_index.0];
         clip_store.get_mut(&metadata.clip_sources).update(
-            layer_transform,
+            &prim_context.packed_layer.transform,
             gpu_cache,
             resource_cache,
-            device_pixel_ratio,
+            prim_context.device_pixel_ratio,
         );
 
+        // Try to create a mask if we may need to.
+        let prim_clips = clip_store.get(&metadata.clip_sources);
+        let clip_task = if prim_clips.is_masking() {
+            // Take into account the actual clip info of the primitive, and
+            // mutate the current bounds accordingly.
+            let mask_rect = match prim_clips.bounds.outer {
+                Some(ref outer) => match prim_screen_rect.intersection(&outer.device_rect) {
+                    Some(rect) => rect,
+                    None => return None,
+                },
+                _ => prim_screen_rect,
+            };
+
+            let extra = ClipWorkItem {
+                layer_index: prim_context.packed_layer_index,
+                clip_sources: metadata.clip_sources.weak(),
+                apply_rectangles: false,
+            };
+
+            RenderTask::new_mask(
+                None,
+                mask_rect,
+                &prim_context.current_clip_stack,
+                Some(extra),
+                prim_screen_rect,
+                clip_store,
+            )
+        } else if !prim_context.current_clip_stack.is_empty() {
+            // If the primitive doesn't have a specific clip, key the task ID off the
+            // stacking context. This means that two primitives which are only clipped
+            // by the stacking context stack can share clip masks during render task
+            // assignment to targets.
+            RenderTask::new_mask(
+                Some(prim_context.clip_id),
+                prim_context.clip_bounds,
+                &prim_context.current_clip_stack,
+                None,
+                prim_screen_rect,
+                clip_store,
+            )
+        } else {
+            None
+        };
+
+        metadata.clip_task_id = clip_task.map(|clip_task| render_tasks.add(clip_task));
+
         match metadata.prim_kind {
             PrimitiveKind::Rectangle | PrimitiveKind::Border | PrimitiveKind::Line => {}
             PrimitiveKind::BoxShadow => {
                 // TODO(gw): Account for zoom factor!
                 // Here, we calculate the size of the patch required in order
                 // to create the box shadow corner. First, scale it by the
                 // device pixel ratio since the cache shader expects vertices
                 // in device space. The shader adds a 1-pixel border around
                 // the patch, in order to prevent bilinear filter artifacts as
                 // the patch is clamped / mirrored across the box shadow rect.
                 let box_shadow = &mut self.cpu_box_shadows[cpu_prim_index.0];
-                let edge_size = box_shadow.edge_size.ceil() * device_pixel_ratio;
+                let edge_size = box_shadow.edge_size.ceil() * prim_context.device_pixel_ratio;
                 let edge_size = edge_size as i32 + 2; // Account for bilinear filtering
                 let cache_size = DeviceIntSize::new(edge_size, edge_size);
 
                 let cache_key = BoxShadowPrimitiveCacheKey {
                     blur_radius: Au::from_f32_px(box_shadow.blur_radius),
                     border_radius: Au::from_f32_px(box_shadow.border_radius),
                     inverted: box_shadow.inverted != 0.0,
                     shadow_rect_size: Size2D::new(
@@ -1198,45 +1207,49 @@ impl PrimitiveStore {
 
                 // Create a render task for this box shadow primitive. This renders a small
                 // portion of the box shadow to a render target. That portion is then
                 // stretched over the actual primitive rect by the box shadow primitive
                 // shader, to reduce the number of pixels that the expensive box
                 // shadow shader needs to run on.
                 // TODO(gw): In the future, we can probably merge the box shadow
                 // primitive (stretch) shader with the generic cached primitive shader.
-                let render_task = RenderTask::new_box_shadow(cache_key, cache_size, prim_index);
+                let render_task = RenderTask::new_box_shadow(
+                    cache_key,
+                    cache_size,
+                    prim_index
+                );
                 let render_task_id = render_tasks.add(render_task);
 
                 box_shadow.render_task_id = Some(render_task_id);
             }
             PrimitiveKind::TextShadow => {
                 let shadow = &mut self.cpu_text_shadows[cpu_prim_index.0];
 
                 // This is a text-shadow element. Create a render task that will
                 // render the text run to a target, and then apply a gaussian
                 // blur to that text run in order to build the actual primitive
                 // which will be blitted to the framebuffer.
                 let cache_width =
-                    (metadata.local_rect.size.width * device_pixel_ratio).ceil() as i32;
+                    (metadata.local_rect.size.width * prim_context.device_pixel_ratio).ceil() as i32;
                 let cache_height =
-                    (metadata.local_rect.size.height * device_pixel_ratio).ceil() as i32;
+                    (metadata.local_rect.size.height * prim_context.device_pixel_ratio).ceil() as i32;
                 let cache_size = DeviceIntSize::new(cache_width, cache_height);
-                let blur_radius = device_length(shadow.shadow.blur_radius, device_pixel_ratio);
+                let blur_radius = device_length(shadow.shadow.blur_radius, prim_context.device_pixel_ratio);
                 let prim_cache_task = RenderTask::new_prim_cache(cache_size, prim_index);
                 let prim_cache_task_id = render_tasks.add(prim_cache_task);
                 let render_task =
                     RenderTask::new_blur(blur_radius, prim_cache_task_id, render_tasks);
                 shadow.render_task_id = Some(render_tasks.add(render_task));
             }
             PrimitiveKind::TextRun => {
                 let text = &mut self.cpu_text_runs[cpu_prim_index.0];
                 text.prepare_for_render(
                     resource_cache,
-                    device_pixel_ratio,
+                    prim_context.device_pixel_ratio,
                     display_list,
                     text_run_mode,
                     gpu_cache,
                 );
             }
             PrimitiveKind::Image => {
                 let image_cpu = &mut self.cpu_images[cpu_prim_index.0];
 
@@ -1332,17 +1345,20 @@ impl PrimitiveStore {
                         prim.shadow.offset.y,
                         prim.shadow.blur_radius,
                         0.0,
                     ]);
                 }
             }
         }
 
-        metadata
+        Some(Geometry {
+            local_rect: prim_local_rect,
+            device_rect: prim_screen_rect,
+        })
     }
 }
 
 
 //Test for one clip region contains another
 trait InsideTest<T> {
     fn might_contain(&self, clip: &T) -> bool;
 }
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -326,16 +326,22 @@ impl RenderBackend {
                         &mut self.gpu_cache,
                         &mut profile_counters.resources,
                     );
                     DocumentOp::Scrolled(frame)
                 } else {
                     DocumentOp::ScrolledNop
                 }
             }
+            DocumentMsg::HitTest(pipeline_id, point, flags, tx) => {
+                profile_scope!("HitTest");
+                let result = doc.frame.hit_test(pipeline_id, point, flags);
+                tx.send(result).unwrap();
+                DocumentOp::Nop
+            }
             DocumentMsg::ScrollNodeWithId(origin, id, clamp) => {
                 profile_scope!("ScrollNodeWithScrollId");
                 let _timer = profile_counters.total_time.timer();
 
                 if doc.frame.scroll_node(origin, id, clamp) && doc.render_on_scroll == Some(true) {
                     let frame = doc.render(
                         &mut self.resource_cache,
                         &mut self.gpu_cache,
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -339,16 +339,19 @@ impl ResourceCache {
     ) {
         let mut requested_render_mode = FontRenderMode::Subpixel;
         let mut subpx_dir = SubpixelDirection::Horizontal;
         if let Some(options) = options {
             if let Some(render_mode) = options.render_mode {
                 requested_render_mode = render_mode;
             }
         }
+        if self.glyph_rasterizer.is_bitmap_font(font_key) {
+            requested_render_mode = requested_render_mode.limit_by(FontRenderMode::Bitmap);
+        }
         if requested_render_mode == FontRenderMode::Mono {
             subpx_dir = SubpixelDirection::None;
         }
         let instance = FontInstance::new(
             font_key,
             glyph_size,
             ColorF::new(0.0, 0.0, 0.0, 1.0),
             requested_render_mode,
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -50,17 +50,19 @@ impl AlphaBatchHelpers for PrimitiveStor
         let needs_blending = !metadata.opacity.is_opaque || metadata.clip_task_id.is_some() ||
             transform_kind == TransformedRectKind::Complex;
 
         match metadata.prim_kind {
             PrimitiveKind::TextRun => {
                 let text_run_cpu = &self.cpu_text_runs[metadata.cpu_prim_index.0];
                 match text_run_cpu.font.render_mode {
                     FontRenderMode::Subpixel => BlendMode::Subpixel(text_run_cpu.color),
-                    FontRenderMode::Alpha | FontRenderMode::Mono => BlendMode::Alpha,
+                    FontRenderMode::Alpha |
+                    FontRenderMode::Mono |
+                    FontRenderMode::Bitmap => BlendMode::Alpha,
                 }
             }
             PrimitiveKind::Image |
             PrimitiveKind::AlignedGradient |
             PrimitiveKind::AngleGradient |
             PrimitiveKind::RadialGradient |
             PrimitiveKind::TextShadow => if needs_blending {
                 BlendMode::PremultipliedAlpha
@@ -352,19 +354,17 @@ impl AlphaRenderItem {
                 let (transform_kind, packed_layer_index) = match clip_scroll_group_index_opt {
                     Some(group_index) => {
                         let group = &ctx.clip_scroll_group_store[group_index.0];
                         let bounding_rect = group.screen_bounding_rect.as_ref().unwrap();
                         (bounding_rect.0, group.packed_layer_index)
                     }
                     None => (TransformedRectKind::AxisAligned, PackedLayerIndex(0)),
                 };
-                let item_bounding_rect = ctx.prim_store.cpu_bounding_rects[prim_index.0]
-                    .as_ref()
-                    .unwrap();
+                let item_bounding_rect = prim_metadata.screen_rect.as_ref().unwrap();
                 let prim_cache_address = 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,
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -1,17 +1,16 @@
 /* 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, ComplexClipRegion, LayoutRect};
-use api::{DeviceIntRect, DevicePoint, DeviceRect, DeviceSize};
-use api::{LayerRect, LayerToWorldTransform, WorldPoint3D};
-use euclid::{Point2D, Rect, Size2D};
-use euclid::{TypedPoint2D, TypedRect, TypedSize2D, TypedTransform2D, TypedTransform3D};
+use api::{BorderRadius, ComplexClipRegion, DeviceIntRect, DevicePoint, DeviceRect, DeviceSize};
+use api::{LayerRect, LayerToWorldTransform, LayoutRect, WorldPoint3D};
+use euclid::{Point2D, Rect, Size2D, TypedPoint2D, TypedRect, TypedSize2D, TypedTransform2D};
+use euclid::TypedTransform3D;
 use num_traits::Zero;
 use std::f32::consts::FRAC_1_SQRT_2;
 
 // 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> {
--- a/gfx/webrender_api/Cargo.toml
+++ b/gfx/webrender_api/Cargo.toml
@@ -7,16 +7,17 @@ repository = "https://github.com/servo/w
 
 [features]
 nightly = ["euclid/unstable", "serde/unstable"]
 ipc = ["ipc-channel"]
 
 [dependencies]
 app_units = "0.5.6"
 bincode = "0.8"
+bitflags = "0.9"
 byteorder = "1.0"
 euclid = "0.15"
 fxhash = "0.2.1"
 heapsize = ">= 0.3.6, < 0.5"
 ipc-channel = {version = "0.8", optional = true}
 serde = { version = "1.0", features = ["rc", "derive"] }
 time = "0.1"
 
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -1,18 +1,17 @@
 /* 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 {BuiltDisplayList, BuiltDisplayListDescriptor, ClipId, ColorF, DeviceIntPoint};
-use {DeviceUintRect, DeviceUintSize, FontInstanceKey, FontKey, GlyphDimensions, GlyphKey};
-use {FontInstance, FontInstanceOptions, FontInstancePlatformOptions, FontVariation,
-     NativeFontHandle, WorldPoint};
-use {ImageData, ImageDescriptor, ImageKey, LayoutPoint, LayoutSize, LayoutTransform,
-     LayoutVector2D};
+use {BuiltDisplayList, BuiltDisplayListDescriptor, ClipId, ColorF, DeviceIntPoint, DeviceUintRect};
+use {DeviceUintSize, FontInstance, FontInstanceKey, FontInstanceOptions};
+use {FontInstancePlatformOptions, FontKey, FontVariation, GlyphDimensions, GlyphKey, ImageData};
+use {ImageDescriptor, ImageKey, ItemTag, LayoutPoint, LayoutSize, LayoutTransform, LayoutVector2D};
+use {NativeFontHandle, WorldPoint};
 use app_units::Au;
 use channel::{self, MsgSender, Payload, PayloadSender, PayloadSenderHelperMethods};
 use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
 
 pub type TileSize = u16;
 
@@ -138,28 +137,56 @@ pub struct UpdateImage {
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum AddFont {
     Raw(FontKey, Vec<u8>, u32),
     Native(FontKey, NativeFontHandle),
 }
 
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct HitTestItem {
+    /// The pipeline that the display item that was hit belongs to.
+    pub pipeline: PipelineId,
+
+    /// The tag of the hit display item.
+    pub tag: ItemTag,
+
+    /// The hit point in the coordinate space of the "viewport" of the display item. The
+    /// viewport is the scroll node formed by the root reference frame of the display item's
+    /// pipeline.
+    pub point_in_viewport: LayoutPoint,
+}
+
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+pub struct HitTestResult {
+    pub items: Vec<HitTestItem>,
+}
+
+bitflags! {
+    #[derive(Deserialize, Serialize)]
+    pub struct HitTestFlags: u8 {
+        const FIND_ALL = 0b00000001;
+        const POINT_RELATIVE_TO_PIPELINE_VIEWPORT = 0b00000010;
+    }
+}
+
 #[derive(Clone, Deserialize, Serialize)]
 pub struct AddFontInstance {
     pub key: FontInstanceKey,
     pub font_key: FontKey,
     pub glyph_size: Au,
     pub options: Option<FontInstanceOptions>,
     pub platform_options: Option<FontInstancePlatformOptions>,
     pub variations: Vec<FontVariation>,
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum DocumentMsg {
+    HitTest(Option<PipelineId>, WorldPoint, HitTestFlags, MsgSender<HitTestResult>),
     SetDisplayList {
         list_descriptor: BuiltDisplayListDescriptor,
         epoch: Epoch,
         pipeline_id: PipelineId,
         background: Option<ColorF>,
         viewport_size: LayoutSize,
         content_size: LayoutSize,
         preserve_frame_state: bool,
@@ -182,16 +209,17 @@ pub enum DocumentMsg {
     GetScrollNodeState(MsgSender<Vec<ScrollLayerState>>),
     GenerateFrame(Option<DynamicProperties>),
 }
 
 impl fmt::Debug for DocumentMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             DocumentMsg::SetDisplayList { .. } => "DocumentMsg::SetDisplayList",
+            DocumentMsg::HitTest(..) => "DocumentMsg::HitTest",
             DocumentMsg::SetPageZoom(..) => "DocumentMsg::SetPageZoom",
             DocumentMsg::SetPinchZoom(..) => "DocumentMsg::SetPinchZoom",
             DocumentMsg::SetPan(..) => "DocumentMsg::SetPan",
             DocumentMsg::SetRootPipeline(..) => "DocumentMsg::SetRootPipeline",
             DocumentMsg::RemovePipeline(..) => "DocumentMsg::RemovePipeline",
             DocumentMsg::SetWindowParameters { .. } => "DocumentMsg::SetWindowParameters",
             DocumentMsg::Scroll(..) => "DocumentMsg::Scroll",
             DocumentMsg::ScrollNodeWithId(..) => "DocumentMsg::ScrollNodeWithId",
@@ -607,16 +635,28 @@ impl RenderApi {
         clamp: ScrollClamping,
     ) {
         self.send(
             document_id,
             DocumentMsg::ScrollNodeWithId(origin, id, clamp),
         );
     }
 
+    /// Does a hit test as the given point
+    pub fn hit_test(&self,
+                    document_id: DocumentId,
+                    pipeline_id: Option<PipelineId>,
+                    point: WorldPoint,
+                    flags: HitTestFlags)
+                    -> HitTestResult {
+        let (tx, rx) = channel::msg_channel().unwrap();
+        self.send(document_id, DocumentMsg::HitTest(pipeline_id, point, flags, tx));
+        rx.recv().unwrap()
+    }
+
     pub fn set_page_zoom(&self, document_id: DocumentId, page_zoom: ZoomFactor) {
         self.send(document_id, DocumentMsg::SetPageZoom(page_zoom));
     }
 
     pub fn set_pinch_zoom(&self, document_id: DocumentId, pinch_zoom: ZoomFactor) {
         self.send(document_id, DocumentMsg::SetPinchZoom(pinch_zoom));
     }
 
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -33,28 +33,36 @@ impl ClipAndScrollInfo {
         }
     }
 
     pub fn clip_node_id(&self) -> ClipId {
         self.clip_node_id.unwrap_or(self.scroll_node_id)
     }
 }
 
+/// A tag that can be used to identify items during hit testing. If the tag
+/// is missing then the item doesn't take part in hit testing at all. This
+/// is composed of two numbers. In Servo, the first is an identifier while the
+/// second is used to select the cursor that should be used during mouse
+/// movement.
+pub type ItemTag = (u64, u8);
+
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct DisplayItem {
     pub item: SpecificDisplayItem,
     pub clip_and_scroll: ClipAndScrollInfo,
     pub info: LayoutPrimitiveInfo,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct PrimitiveInfo<T> {
     pub rect: TypedRect<f32, T>,
     pub local_clip: LocalClip,
     pub is_backface_visible: bool,
+    pub tag: Option<ItemTag>,
 }
 
 impl LayerPrimitiveInfo {
     pub fn new(rect: TypedRect<f32, LayerPixel>) -> Self {
         Self::with_clip_rect(rect, rect)
     }
 
     pub fn with_clip_rect(rect: TypedRect<f32, LayerPixel>,
@@ -63,16 +71,17 @@ impl LayerPrimitiveInfo {
         Self::with_clip(rect, LocalClip::from(clip_rect))
     }
 
     pub fn with_clip(rect: TypedRect<f32, LayerPixel>, clip: LocalClip) -> Self {
         PrimitiveInfo {
             rect: rect,
             local_clip: clip,
             is_backface_visible: true,
+            tag: None,
         }
     }
 }
 
 pub type LayoutPrimitiveInfo = PrimitiveInfo<LayoutPixel>;
 pub type LayerPrimitiveInfo = PrimitiveInfo<LayerPixel>;
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -338,16 +338,17 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> {
     }
 
     pub fn get_layer_primitive_info(&self, offset: &LayoutVector2D) -> LayerPrimitiveInfo {
         let info = self.iter.cur_item.info;
         LayerPrimitiveInfo {
             rect: info.rect.translate(&offset),
             local_clip: info.local_clip.create_with_offset(offset),
             is_backface_visible: info.is_backface_visible,
+            tag: info.tag,
         }
     }
 
     pub fn local_clip(&self) -> &LocalClip {
         &self.iter.cur_item.info.local_clip
     }
 
     pub fn clip_and_scroll(&self) -> ClipAndScrollInfo {
@@ -992,16 +993,17 @@ impl DisplayListBuilder {
             image_mask: image_mask,
             scroll_sensitivity,
         });
 
         let info = LayoutPrimitiveInfo {
             rect: content_rect,
             local_clip: LocalClip::from(clip_rect),
             is_backface_visible: true,
+            tag: None,
         };
 
         self.push_item(item, &info);
         self.push_iter(complex_clips);
         id
     }
 
     pub fn define_clip<I>(
--- a/gfx/webrender_api/src/font.rs
+++ b/gfx/webrender_api/src/font.rs
@@ -88,16 +88,17 @@ pub enum FontTemplate {
 }
 
 #[repr(u32)]
 #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
 pub enum FontRenderMode {
     Mono = 0,
     Alpha,
     Subpixel,
+    Bitmap,
 }
 
 #[repr(u32)]
 #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
 pub enum SubpixelDirection {
     None = 0,
     Horizontal,
     Vertical,
@@ -124,16 +125,17 @@ impl FontRenderMode {
             5...6 => SubpixelOffset::ThreeQuarters,
             _ => unreachable!("bug: unexpected quantized result"),
         }
     }
 
     // Combine two font render modes such that the lesser amount of AA limits the AA of the result.
     pub fn limit_by(self, other: FontRenderMode) -> FontRenderMode {
         match (self, other) {
+            (FontRenderMode::Bitmap, _) | (_, FontRenderMode::Bitmap) => FontRenderMode::Bitmap,
             (FontRenderMode::Subpixel, _) | (_, FontRenderMode::Mono) => other,
             _ => self,
         }
     }
 }
 
 #[repr(u8)]
 #[derive(Hash, Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
@@ -232,18 +234,24 @@ impl FontInstance {
         subpx_dir: SubpixelDirection,
         platform_options: Option<FontInstancePlatformOptions>,
         variations: Vec<FontVariation>,
         synthetic_italics: bool,
     ) -> FontInstance {
         // In alpha/mono mode, the color of the font is irrelevant.
         // Forcing it to black in those cases saves rasterizing glyphs
         // of different colors when not needed.
-        if render_mode != FontRenderMode::Subpixel {
-            color = ColorF::new(0.0, 0.0, 0.0, 1.0);
+        match render_mode {
+            FontRenderMode::Alpha | FontRenderMode::Mono => {
+                color = ColorF::new(0.0, 0.0, 0.0, 1.0);
+            }
+            FontRenderMode::Bitmap => {
+                color = ColorF::new(1.0, 1.0, 1.0, 1.0);
+            }
+            FontRenderMode::Subpixel => {}
         }
 
         FontInstance {
             font_key,
             size,
             color: color.into(),
             render_mode,
             subpx_dir,
--- a/gfx/webrender_api/src/lib.rs
+++ b/gfx/webrender_api/src/lib.rs
@@ -2,16 +2,18 @@
  * 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_attr(feature = "nightly", feature(nonzero))]
 #![cfg_attr(feature = "cargo-clippy", allow(too_many_arguments, float_cmp))]
 
 extern crate app_units;
 extern crate bincode;
+#[macro_use]
+extern crate bitflags;
 extern crate byteorder;
 #[cfg(feature = "nightly")]
 extern crate core;
 extern crate euclid;
 extern crate fxhash;
 #[macro_use]
 extern crate heapsize;
 #[cfg(feature = "ipc")]
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -58,16 +58,17 @@ enum class ExternalImageType : uint32_t 
 
   Sentinel /* this must be last for serialization purposes. */
 };
 
 enum class FontRenderMode : uint32_t {
   Mono = 0,
   Alpha = 1,
   Subpixel = 2,
+  Bitmap = 3,
 
   Sentinel /* this must be last for serialization purposes. */
 };
 
 enum class ImageFormat : uint32_t {
   Invalid = 0,
   A8 = 1,
   RGB8 = 2,
--- a/toolkit/library/gtest/rust/Cargo.lock
+++ b/toolkit/library/gtest/rust/Cargo.lock
@@ -1597,16 +1597,17 @@ dependencies = [
 ]
 
 [[package]]
 name = "webrender_api"
 version = "0.52.0"
 dependencies = [
  "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
--- a/toolkit/library/rust/Cargo.lock
+++ b/toolkit/library/rust/Cargo.lock
@@ -1584,16 +1584,17 @@ dependencies = [
 ]
 
 [[package]]
 name = "webrender_api"
 version = "0.52.0"
 dependencies = [
  "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",