Bug 1383041 - Update WR to cset 0748e02d1be5f889fc17de2eb81c0c363ee3aa80. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Fri, 28 Jul 2017 08:18:28 -0400
changeset 617337 fa4e990721e2fb9abf993f2bdf5e1b2f2ac3d64b
parent 616915 36f95aeb4c77f7cf3b3366583008cd6e4b6b1dba
child 617338 de849b6984e180badd39d79d12524e265c8a0248
push id71042
push userkgupta@mozilla.com
push dateFri, 28 Jul 2017 12:19:16 +0000
reviewersjrmuizel
bugs1383041
milestone56.0a1
Bug 1383041 - Update WR to cset 0748e02d1be5f889fc17de2eb81c0c363ee3aa80. r?jrmuizel MozReview-Commit-ID: BO0bDTnS1y2
gfx/doc/README.webrender
gfx/webrender/examples/nested_display_list.rs
gfx/webrender/examples/scrolling.rs
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_line.fs.glsl
gfx/webrender/res/ps_line.glsl
gfx/webrender/res/ps_line.vs.glsl
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/debug_render.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/internal_types.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/profiler.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/color.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/image.rs
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +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: b83c200c657f6b6fb17d09f329ba77803420b46a
+Latest Commit: 0748e02d1be5f889fc17de2eb81c0c363ee3aa80
--- a/gfx/webrender/examples/nested_display_list.rs
+++ b/gfx/webrender/examples/nested_display_list.rs
@@ -31,17 +31,18 @@ fn body(_api: &RenderApi,
 
     let outer_scroll_frame_rect = (100, 100).to(600, 400);
     builder.push_rect(outer_scroll_frame_rect, None, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
     let nested_clip_id = builder.define_scroll_frame(None,
                                                      (100, 100).to(1000, 1000),
                                                      outer_scroll_frame_rect,
                                                      vec![],
-                                                     None);
+                                                     None,
+                                                     ScrollSensitivity::ScriptAndInputEvents);
     builder.push_clip_id(nested_clip_id);
 
     let mut builder2 = DisplayListBuilder::new(*pipeline_id, *layout_size);
     let mut builder3 = DisplayListBuilder::new(*pipeline_id, *layout_size);
 
     let rect = (110, 110).to(210, 210);
     builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
 
@@ -58,21 +59,23 @@ fn body(_api: &RenderApi,
     builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
     builder3.pop_stacking_context();
 
     // Now we push an inner scroll frame that should have the same id as the outer one,
     // but the WebRender nested display list replacement code should convert it into
     // a unique ClipId.
     let inner_scroll_frame_rect = (330, 110).to(530, 360);
     builder3.push_rect(inner_scroll_frame_rect, None, ColorF::new(1.0, 0.0, 1.0, 0.5));
-    let inner_nested_clip_id = builder3.define_scroll_frame(None,
-                                                            (330, 110).to(2000, 2000),
-                                                            inner_scroll_frame_rect,
-                                                            vec![],
-                                                            None);
+    let inner_nested_clip_id =
+        builder3.define_scroll_frame(None,
+                                     (330, 110).to(2000, 2000),
+                                     inner_scroll_frame_rect,
+                                     vec![],
+                                     None,
+                                     ScrollSensitivity::ScriptAndInputEvents);
     builder3.push_clip_id(inner_nested_clip_id);
     let rect = (340, 120).to(440, 220);
     builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
     builder3.pop_clip_id();
 
     let (_, _, built_list) = builder3.finalize();
     builder2.push_nested_display_list(&built_list);
     let (_, _, built_list) = builder2.finalize();
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -40,17 +40,18 @@ fn body(_api: &RenderApi,
                                       None,
                                       MixBlendMode::Normal,
                                       Vec::new());
         // set the scrolling clip
         let clip_id = builder.define_scroll_frame(None,
                                                   (0, 0).by(1000, 1000),
                                                   scrollbox,
                                                   vec![],
-                                                  None);
+                                                  None,
+                                                  ScrollSensitivity::ScriptAndInputEvents);
         builder.push_clip_id(clip_id);
 
         // now put some content into it.
         // start with a white background
         builder.push_rect((0, 0).to(1000, 1000), None, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
         // let's make a 50x50 blue square as a visual reference
         builder.push_rect((0, 0).to(50, 50), None, ColorF::new(0.0, 0.0, 1.0, 1.0));
@@ -63,17 +64,18 @@ fn body(_api: &RenderApi,
 
         // Below the above rectangles, set up a nested scrollbox. It's still in
         // the same stacking context, so note that the rects passed in need to
         // be relative to the stacking context.
         let nested_clip_id = builder.define_scroll_frame(None,
                                                          (0, 100).to(300, 400),
                                                          (0, 100).to(200, 300),
                                                          vec![],
-                                                         None);
+                                                         None,
+                                                         ScrollSensitivity::ScriptAndInputEvents);
         builder.push_clip_id(nested_clip_id);
 
         // give it a giant gray background just to distinguish it and to easily
         // visually identify the nested scrollbox
         builder.push_rect((-1000, -1000).to(5000, 5000), None, ColorF::new(0.5, 0.5, 0.5, 1.0));
 
         // add a teal square to visualize the scrolling/clipping behaviour
         // as you scroll the nested scrollbox with WASD keys
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -49,16 +49,21 @@
 #define BORDER_STYLE_OUTSET       9
 
 #define UV_NORMALIZED    uint(0)
 #define UV_PIXEL         uint(1)
 
 #define EXTEND_MODE_CLAMP  0
 #define EXTEND_MODE_REPEAT 1
 
+#define LINE_STYLE_SOLID        0
+#define LINE_STYLE_DOTTED       1
+#define LINE_STYLE_DASHED       2
+#define LINE_STYLE_WAVY         3
+
 uniform sampler2DArray sCacheA8;
 uniform sampler2DArray sCacheRGBA8;
 
 uniform sampler2D sGradients;
 
 struct RectWithSize {
     vec2 p0;
     vec2 size;
@@ -778,16 +783,27 @@ struct TextShadow {
     float blur_radius;
 };
 
 TextShadow fetch_text_shadow(int address) {
     vec4 data[2] = fetch_from_resource_cache_2(address);
     return TextShadow(data[0], data[1].xy, data[1].z);
 }
 
+struct Line {
+    vec4 color;
+    float style;
+    float orientation;
+};
+
+Line fetch_line(int address) {
+    vec4 data[2] = fetch_from_resource_cache_2(address);
+    return Line(data[0], data[1].x, data[1].y);
+}
+
 struct TextRun {
     vec4 color;
     vec2 offset;
 };
 
 TextRun fetch_text_run(int address) {
     vec4 data[2] = fetch_from_resource_cache_2(address);
     return TextRun(data[0], data[1].xy);
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ps_line.fs.glsl
@@ -0,0 +1,123 @@
+#line 1
+/* 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/. */
+
+float det(vec2 a, vec2 b) {
+    return a.x * b.y - b.x * a.y;
+}
+
+// From: http://research.microsoft.com/en-us/um/people/hoppe/ravg.pdf
+vec2 get_distance_vector(vec2 b0, vec2 b1, vec2 b2) {
+    float a = det(b0, b2);
+    float b = 2.0 * det(b1, b0);
+    float d = 2.0 * det(b2, b1);
+
+    float f = b * d - a * a;
+    vec2 d21 = b2 - b1;
+    vec2 d10 = b1 - b0;
+    vec2 d20 = b2 - b0;
+
+    vec2 gf = 2.0 * (b *d21 + d * d10 + a * d20);
+    gf = vec2(gf.y,-gf.x);
+    vec2 pp = -f * gf / dot(gf, gf);
+    vec2 d0p = b0 - pp;
+    float ap = det(d0p, d20);
+    float bp = 2.0 * det(d10, d0p);
+
+    float t = clamp((ap + bp) / (2.0 * a + b + d), 0.0, 1.0);
+    return mix(mix(b0, b1, t), mix(b1,b2,t), t);
+}
+
+// Approximate distance from point to quadratic bezier.
+float approx_distance(vec2 p, vec2 b0, vec2 b1, vec2 b2) {
+    return length(get_distance_vector(b0 - p, b1 - p, b2 - p));
+}
+
+void main(void) {
+    float alpha = 1.0;
+
+#ifdef WR_FEATURE_CACHE
+    vec2 local_pos = vLocalPos;
+#else
+    #ifdef WR_FEATURE_TRANSFORM
+        alpha = 0.0;
+        vec2 local_pos = init_transform_fs(vLocalPos, alpha);
+    #else
+        vec2 local_pos = vLocalPos;
+    #endif
+
+        alpha = min(alpha, do_clip());
+#endif
+
+    // Find the appropriate distance to apply the step over.
+    vec2 fw = fwidth(local_pos);
+    float afwidth = length(fw);
+
+    // Select the x/y coord, depending on which axis this edge is.
+    vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect);
+
+    switch (vStyle) {
+        case LINE_STYLE_SOLID: {
+            break;
+        }
+        case LINE_STYLE_DASHED: {
+            // Get the main-axis position relative to closest dot or dash.
+            float x = mod(pos.x - vLocalOrigin.x, vParams.x);
+
+            // Calculate dash alpha (on/off) based on dash length
+            alpha = min(alpha, step(x, vParams.y));
+            break;
+        }
+        case LINE_STYLE_DOTTED: {
+            // Get the main-axis position relative to closest dot or dash.
+            float x = mod(pos.x - vLocalOrigin.x, vParams.x);
+
+            // Get the dot alpha
+            vec2 dot_relative_pos = vec2(x, pos.y) - vParams.yz;
+            float dot_distance = length(dot_relative_pos) - vParams.y;
+            alpha = min(alpha, 1.0 - smoothstep(-0.5 * afwidth,
+                                                0.5 * afwidth,
+                                                dot_distance));
+            break;
+        }
+        case LINE_STYLE_WAVY: {
+            vec2 normalized_local_pos = pos - vLocalOrigin.xy;
+
+            float y0 = vParams.y;
+            float dy = vParams.z;
+            float dx = vParams.w;
+
+            // Flip the position of the bezier center points each
+            // wave period.
+            dy *= step(mod(normalized_local_pos.x, 4.0 * dx), 2.0 * dx) * 2.0 - 1.0;
+
+            // Convert pos to a local position within one wave period.
+            normalized_local_pos.x = dx + mod(normalized_local_pos.x, 2.0 * dx);
+
+            // Evaluate SDF to the first bezier.
+            vec2 b0_0 = vec2(0.0 * dx,  y0);
+            vec2 b1_0 = vec2(1.0 * dx,  y0 - dy);
+            vec2 b2_0 = vec2(2.0 * dx,  y0);
+            float d1 = approx_distance(normalized_local_pos, b0_0, b1_0, b2_0);
+
+            // Evaluate SDF to the second bezier.
+            vec2 b0_1 = vec2(2.0 * dx,  y0);
+            vec2 b1_1 = vec2(3.0 * dx,  y0 + dy);
+            vec2 b2_1 = vec2(4.0 * dx,  y0);
+            float d2 = approx_distance(normalized_local_pos, b0_1, b1_1, b2_1);
+
+            // SDF union - this is needed to avoid artifacts where the
+            // bezier curves join.
+            float d = min(d1, d2);
+
+            // Apply AA based on the thickness of the wave.
+            alpha = 1.0 - smoothstep(vParams.x - 0.5 * afwidth,
+                                     vParams.x + 0.5 * afwidth,
+                                     d);
+            break;
+        }
+    }
+
+    oFragColor = vColor * vec4(1.0, 1.0, 1.0, alpha);
+}
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ps_line.glsl
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+varying vec4 vColor;
+flat varying int vStyle;
+flat varying float vAxisSelect;
+flat varying vec4 vParams;
+flat varying vec2 vLocalOrigin;
+
+#ifdef WR_FEATURE_TRANSFORM
+varying vec3 vLocalPos;
+#else
+varying vec2 vLocalPos;
+#endif
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ps_line.vs.glsl
@@ -0,0 +1,116 @@
+#line 1
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define LINE_ORIENTATION_VERTICAL       0
+#define LINE_ORIENTATION_HORIZONTAL     1
+
+void main(void) {
+    Primitive prim = load_primitive();
+    Line line = fetch_line(prim.specific_prim_address);
+
+    vec2 pos, size;
+
+    switch (int(line.orientation)) {
+        case LINE_ORIENTATION_HORIZONTAL:
+            vAxisSelect = 0.0;
+            pos = prim.local_rect.p0;
+            size = prim.local_rect.size;
+            break;
+        case LINE_ORIENTATION_VERTICAL:
+            vAxisSelect = 1.0;
+            pos = prim.local_rect.p0.yx;
+            size = prim.local_rect.size.yx;
+            break;
+    }
+
+    vLocalOrigin = pos;
+    vStyle = int(line.style);
+
+    switch (vStyle) {
+        case LINE_STYLE_SOLID: {
+            break;
+        }
+        case LINE_STYLE_DASHED: {
+            // y = dash on + off length
+            // z = dash length
+            // w = center line of edge cross-axis (for dots only)
+            float desired_dash_length = size.y * 3.0;
+            // Consider half total length since there is an equal on/off for each dash.
+            float dash_count = 1.0 + ceil(size.x / desired_dash_length);
+            float dash_length = size.x / dash_count;
+            vParams = vec4(2.0 * dash_length,
+                           dash_length,
+                           0.0,
+                           0.0);
+            break;
+        }
+        case LINE_STYLE_DOTTED: {
+            float diameter = size.y;
+            float radius = 0.5 * diameter;
+            float dot_count = ceil(0.5 * size.x / diameter);
+            float empty_space = size.x - dot_count * diameter;
+            float distance_between_centers = diameter + empty_space / dot_count;
+            float center_line = pos.y + 0.5 * size.y;
+            vParams = vec4(distance_between_centers,
+                           radius,
+                           center_line,
+                           0.0);
+            break;
+        }
+        case LINE_STYLE_WAVY: {
+            // Choose some arbitrary values to scale thickness,
+            // wave period etc.
+            // TODO(gw): Tune these to get closer to what Gecko uses.
+            float thickness = 0.2 * size.y;
+            vParams = vec4(thickness,
+                           size.y * 0.5,
+                           size.y * 0.75,
+                           max(3.0, thickness * 2.0));
+            break;
+        }
+    }
+
+#ifdef WR_FEATURE_CACHE
+    int text_shadow_address = prim.user_data0;
+    PrimitiveGeometry shadow_geom = fetch_primitive_geometry(text_shadow_address);
+    TextShadow shadow = fetch_text_shadow(text_shadow_address + VECS_PER_PRIM_HEADER);
+
+    vec2 device_origin = prim.task.render_target_origin +
+                         uDevicePixelRatio * (prim.local_rect.p0 + shadow.offset - shadow_geom.local_rect.p0);
+    vec2 device_size = uDevicePixelRatio * prim.local_rect.size;
+
+    vec2 device_pos = mix(device_origin,
+                          device_origin + device_size,
+                          aPosition.xy);
+
+    vColor = shadow.color;
+    vLocalPos = mix(prim.local_rect.p0,
+                    prim.local_rect.p0 + prim.local_rect.size,
+                    aPosition.xy);
+
+    gl_Position = uTransform * vec4(device_pos, 0.0, 1.0);
+#else
+    vColor = line.color;
+
+    #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);
+    #else
+        VertexInfo vi = write_vertex(prim.local_rect,
+                                     prim.local_clip_rect,
+                                     prim.z,
+                                     prim.layer,
+                                     prim.task,
+                                     prim.local_rect);
+    #endif
+
+    vLocalPos = vi.local_pos;
+    write_clip(vi.screen_pos, prim.clip_area);
+#endif
+}
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ClipId, DeviceIntRect, LayerPixel, LayerPoint, LayerRect, LayerSize};
 use api::{LayerToScrollTransform, LayerToWorldTransform, LayerVector2D, PipelineId};
-use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, WorldPoint};
+use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollSensitivity, WorldPoint};
 use geometry::ray_intersects_rect;
 use mask_cache::{ClipRegion, ClipSource, MaskCacheInfo};
 use spring::{DAMPING, STIFFNESS, Spring};
 use tiling::PackedLayerIndex;
 use util::{MatrixHelpers, TransformedRectKind};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
@@ -117,30 +117,31 @@ pub struct ClipScrollNode {
     /// Whether or not this node is a reference frame.
     pub node_type: NodeType,
 }
 
 impl ClipScrollNode {
     pub fn new_scroll_frame(pipeline_id: PipelineId,
                             parent_id: ClipId,
                             frame_rect: &LayerRect,
-                            content_size: &LayerSize)
+                            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()),
+            node_type: NodeType::ScrollFrame(ScrollingState::new(scroll_sensitivity)),
         }
     }
 
     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,
@@ -175,24 +176,28 @@ impl ClipScrollNode {
             node_type: NodeType::ReferenceFrame(*local_transform),
         }
     }
 
     pub fn add_child(&mut self, child: ClipId) {
         self.children.push(child);
     }
 
-    pub fn finalize(&mut self, new_scrolling: &ScrollingState) {
+    pub fn apply_old_scrolling_state(&mut self, new_scrolling: &ScrollingState) {
         match self.node_type {
             NodeType::ReferenceFrame(_) | NodeType::Clip(_) => {
                 if new_scrolling.offset != LayerVector2D::zero() {
                     warn!("Tried to scroll a non-scroll node.");
                 }
             }
-            NodeType::ScrollFrame(ref mut scrolling) => *scrolling = *new_scrolling,
+            NodeType::ScrollFrame(ref mut scrolling) => {
+                let scroll_sensitivity = scrolling.scroll_sensitivity;
+                *scrolling = *new_scrolling;
+                scrolling.scroll_sensitivity = scroll_sensitivity;
+            }
         }
     }
 
     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 scrolling = match self.node_type {
@@ -399,28 +404,37 @@ impl ClipScrollNode {
 }
 
 #[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 should_handoff_scroll: bool,
+    pub scroll_sensitivity: ScrollSensitivity,
 }
 
 /// Manages scrolling offset, overscroll state, etc.
 impl ScrollingState {
-    pub fn new() -> ScrollingState {
+    pub fn new(scroll_sensitivity: ScrollSensitivity) -> ScrollingState {
         ScrollingState {
             offset: LayerVector2D::zero(),
             spring: Spring::at(LayerPoint::zero(), STIFFNESS, DAMPING),
             started_bouncing_back: false,
             bouncing_back: false,
-            should_handoff_scroll: false
+            should_handoff_scroll: false,
+            scroll_sensitivity,
+        }
+    }
+
+    pub fn sensitive_to_input_events(&self) -> bool {
+        match self.scroll_sensitivity {
+            ScrollSensitivity::ScriptAndInputEvents => true,
+            ScrollSensitivity::Script => false,
         }
     }
 
     pub fn stretch_overscroll_spring(&mut self, overscroll_amount: LayerVector2D) {
         let offset = self.offset.to_point();
         self.spring.coords(offset, offset, offset + overscroll_amount);
     }
 
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -88,17 +88,17 @@ impl ClipScrollTree {
             for child_layer_id in node.children.iter().rev() {
                 if let Some(layer_id) =
                    self.find_scrolling_node_at_point_in_node(cursor, *child_layer_id) {
                     return Some(layer_id);
                 }
             }
 
             match node.node_type {
-                NodeType::ScrollFrame(..) => {},
+                NodeType::ScrollFrame(state) if state.sensitive_to_input_events() => {},
                 _ => return None,
             }
 
             if node.ray_intersects_node(cursor) {
                 Some(clip_id)
             } else {
                 None
             }
@@ -298,22 +298,19 @@ impl ClipScrollTree {
             node.tick_scrolling_bounce_animation()
         }
     }
 
     pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
         // TODO(gw): These are all independent - can be run through thread pool if it shows up
         // in the profile!
         for (clip_id, node) in &mut self.nodes {
-            let scrolling_state = match old_states.get(clip_id) {
-                Some(old_scrolling_state) => *old_scrolling_state,
-                None => ScrollingState::new(),
-            };
-
-            node.finalize(&scrolling_state);
+            if let Some(scrolling_state) = old_states.get(clip_id) {
+                node.apply_old_scrolling_state(scrolling_state);
+            }
 
             if let Some((pending_offset, clamping)) = self.pending_scroll_offsets.remove(clip_id) {
                 node.set_scroll_origin(&pending_offset, clamping);
             }
         }
     }
 
     pub fn generate_new_clip_id(&mut self, pipeline_id: PipelineId) -> ClipId {
--- a/gfx/webrender/src/debug_render.rs
+++ b/gfx/webrender/src/debug_render.rs
@@ -2,19 +2,19 @@
  * 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 debug_font_data;
 use device::{Device, GpuMarker, ProgramId, VAOId, TextureId, VertexFormat};
 use device::{TextureFilter, VertexUsageHint, TextureTarget};
 use euclid::{Transform3D, Point2D, Size2D, Rect};
 use internal_types::{ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, TextureSampler};
-use internal_types::{DebugFontVertex, DebugColorVertex, RenderTargetMode, PackedColor};
+use internal_types::{DebugFontVertex, DebugColorVertex, RenderTargetMode};
 use std::f32;
-use api::{ColorF, ImageFormat, DeviceUintSize};
+use api::{ColorU, ImageFormat, DeviceUintSize};
 
 pub struct DebugRenderer {
     font_vertices: Vec<DebugFontVertex>,
     font_indices: Vec<u32>,
     font_program_id: ProgramId,
     font_vao: VAOId,
     font_texture_id: TextureId,
 
@@ -62,21 +62,20 @@ impl DebugRenderer {
     pub fn line_height(&self) -> f32 {
         debug_font_data::FONT_SIZE as f32 * 1.1
     }
 
     pub fn add_text(&mut self,
                     x: f32,
                     y: f32,
                     text: &str,
-                    color: &ColorF) -> Rect<f32> {
+                    color: ColorU) -> Rect<f32> {
         let mut x_start = x;
         let ipw = 1.0 / debug_font_data::BMP_WIDTH as f32;
         let iph = 1.0 / debug_font_data::BMP_HEIGHT as f32;
-        let color = PackedColor::from_color(color);
 
         let mut min_x = f32::MAX;
         let mut max_x = -f32::MAX;
         let mut min_y = f32::MAX;
         let mut max_y = -f32::MAX;
 
         for c in text.chars() {
             let c = c as usize - debug_font_data::FIRST_GLYPH_INDEX as usize;
@@ -120,20 +119,18 @@ impl DebugRenderer {
         Rect::new(Point2D::new(min_x, min_y), Size2D::new(max_x-min_x, max_y-min_y))
     }
 
     pub fn add_quad(&mut self,
                     x0: f32,
                     y0: f32,
                     x1: f32,
                     y1: f32,
-                    color_top: &ColorF,
-                    color_bottom: &ColorF) {
-        let color_top = PackedColor::from_color(color_top);
-        let color_bottom = PackedColor::from_color(color_bottom);
+                    color_top: ColorU,
+                    color_bottom: ColorU) {
         let vertex_count = self.tri_vertices.len() as u32;
 
         self.tri_vertices.push(DebugColorVertex::new(x0, y0, color_top));
         self.tri_vertices.push(DebugColorVertex::new(x1, y0, color_top));
         self.tri_vertices.push(DebugColorVertex::new(x0, y1, color_bottom));
         self.tri_vertices.push(DebugColorVertex::new(x1, y1, color_bottom));
 
         self.tri_indices.push(vertex_count + 0);
@@ -143,22 +140,20 @@ impl DebugRenderer {
         self.tri_indices.push(vertex_count + 1);
         self.tri_indices.push(vertex_count + 3);
     }
 
     #[allow(dead_code)]
     pub fn add_line(&mut self,
                     x0: i32,
                     y0: i32,
-                    color0: &ColorF,
+                    color0: ColorU,
                     x1: i32,
                     y1: i32,
-                    color1: &ColorF) {
-        let color0 = PackedColor::from_color(color0);
-        let color1 = PackedColor::from_color(color1);
+                    color1: ColorU) {
         self.line_vertices.push(DebugColorVertex::new(x0 as f32, y0 as f32, color0));
         self.line_vertices.push(DebugColorVertex::new(x1 as f32, y1 as f32, color1));
     }
 
     pub fn render(&mut self,
                   device: &mut Device,
                   viewport_size: &DeviceUintSize) {
         let _gm = GpuMarker::new(device.rc_gl(), "debug");
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -1,25 +1,23 @@
 /* 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::{BuiltDisplayList, BuiltDisplayListIter, ClipAndScrollInfo, ClipId, ColorF};
 use api::{ComplexClipRegion, DeviceUintRect, DeviceUintSize, DisplayItemRef, Epoch, FilterOp};
 use api::{ImageDisplayItem, ItemRange, LayerPoint, LayerRect, LayerSize, LayerToScrollTransform};
 use api::{LayerVector2D, LayoutSize, LayoutTransform, LocalClip, MixBlendMode, PipelineId};
-use api::{ScrollClamping, ScrollEventPhase, ScrollLayerState, ScrollLocation, ScrollPolicy};
-use api::{SpecificDisplayItem, StackingContext, TileOffset, TransformStyle, WorldPoint};
-use app_units::Au;
+use api::{PropertyBinding, ScrollClamping, ScrollEventPhase, ScrollLayerState, ScrollLocation};
+use api::{ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext, TileOffset};
+use api::{TransformStyle, WorldPoint};
 use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use euclid::rect;
 use fnv::FnvHasher;
 use gpu_cache::GpuCache;
-use internal_types::{ANGLE_FLOAT_TO_FIXED, AxisDirection};
-use internal_types::{LowLevelFilterOp};
 use internal_types::{RendererFrame};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use mask_cache::ClipRegion;
 use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
 use resource_cache::ResourceCache;
 use scene::{Scene, SceneProperties};
 use std::cmp;
 use std::collections::HashMap;
@@ -185,69 +183,41 @@ pub struct Frame {
     frame_builder: Option<FrameBuilder>,
 }
 
 trait StackingContextHelpers {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode>;
     fn filter_ops_for_compositing(&self,
                                   display_list: &BuiltDisplayList,
                                   input_filters: ItemRange<FilterOp>,
-                                  properties: &SceneProperties) -> Vec<LowLevelFilterOp>;
+                                  properties: &SceneProperties) -> Vec<FilterOp>;
 }
 
 impl StackingContextHelpers for StackingContext {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode> {
         match self.mix_blend_mode {
             MixBlendMode::Normal => None,
             _ => Some(self.mix_blend_mode),
         }
     }
 
     fn filter_ops_for_compositing(&self,
                                   display_list: &BuiltDisplayList,
                                   input_filters: ItemRange<FilterOp>,
-                                  properties: &SceneProperties) -> Vec<LowLevelFilterOp> {
+                                  properties: &SceneProperties) -> Vec<FilterOp> {
         let mut filters = vec![];
         for filter in display_list.get(input_filters) {
             if filter.is_noop() {
                 continue;
             }
-
-            match filter {
-                FilterOp::Blur(radius) => {
-                    filters.push(LowLevelFilterOp::Blur(radius, AxisDirection::Horizontal));
-                    filters.push(LowLevelFilterOp::Blur(radius, AxisDirection::Vertical));
-                }
-                FilterOp::Brightness(amount) => {
-                    filters.push(LowLevelFilterOp::Brightness(Au::from_f32_px(amount)));
-                }
-                FilterOp::Contrast(amount) => {
-                    filters.push(LowLevelFilterOp::Contrast(Au::from_f32_px(amount)));
-                }
-                FilterOp::Grayscale(amount) => {
-                    filters.push(LowLevelFilterOp::Grayscale(Au::from_f32_px(amount)));
-                }
-                FilterOp::HueRotate(angle) => {
-                    filters.push(
-                            LowLevelFilterOp::HueRotate(f32::round(
-                                    angle * ANGLE_FLOAT_TO_FIXED) as i32));
-                }
-                FilterOp::Invert(amount) => {
-                    filters.push(LowLevelFilterOp::Invert(Au::from_f32_px(amount)));
-                }
-                FilterOp::Opacity(ref value) => {
-                    let amount = properties.resolve_float(value, 1.0);
-                    filters.push(LowLevelFilterOp::Opacity(Au::from_f32_px(amount)));
-                }
-                FilterOp::Saturate(amount) => {
-                    filters.push(LowLevelFilterOp::Saturate(Au::from_f32_px(amount)));
-                }
-                FilterOp::Sepia(amount) => {
-                    filters.push(LowLevelFilterOp::Sepia(Au::from_f32_px(amount)));
-                }
+            if let FilterOp::Opacity(ref value) = filter {
+                let amount = properties.resolve_float(value, 1.0);
+                filters.push(FilterOp::Opacity(PropertyBinding::Value(amount)));
+            } else {
+                filters.push(filter);
             }
         }
         filters
     }
 }
 
 impl Frame {
     pub fn new(config: FrameBuilderConfig) -> Frame {
@@ -376,30 +346,32 @@ impl Frame {
 
     fn flatten_scroll_frame<'a>(&mut self,
                                 context: &mut FlattenContext,
                                 pipeline_id: PipelineId,
                                 parent_id: &ClipId,
                                 new_scroll_frame_id: &ClipId,
                                 frame_rect: &LayerRect,
                                 content_rect: &LayerRect,
-                                clip_region: ClipRegion) {
+                                clip_region: ClipRegion,
+                                scroll_sensitivity: ScrollSensitivity) {
         let clip_id = self.clip_scroll_tree.generate_new_clip_id(pipeline_id);
         context.builder.add_clip_node(clip_id,
                                       *parent_id,
                                       pipeline_id,
                                       clip_region,
                                       &mut self.clip_scroll_tree);
 
         let new_scroll_frame_id = context.convert_new_id_to_nested(new_scroll_frame_id);
         context.builder.add_scroll_frame(new_scroll_frame_id,
                                          clip_id,
                                          pipeline_id,
                                          &frame_rect,
                                          &content_rect.size,
+                                         scroll_sensitivity,
                                          &mut self.clip_scroll_tree);
     }
 
     fn flatten_stacking_context<'a>(&mut self,
                                     traversal: &mut BuiltDisplayListIter<'a>,
                                     pipeline_id: PipelineId,
                                     context: &mut FlattenContext,
                                     context_scroll_node_id: ClipId,
@@ -530,16 +502,17 @@ impl Frame {
                                                  &mut self.clip_scroll_tree);
 
         context.builder.add_scroll_frame(
             ClipId::root_scroll_node(pipeline_id),
             iframe_reference_frame_id,
             pipeline_id,
             &iframe_rect,
             &pipeline.content_size,
+            ScrollSensitivity::ScriptAndInputEvents,
             &mut self.clip_scroll_tree);
 
         self.flatten_root(&mut display_list.iter(), pipeline_id, context, &pipeline.content_size);
 
         context.builder.pop_reference_frame();
     }
 
     fn flatten_item<'a, 'b>(&mut self,
@@ -550,124 +523,140 @@ impl Frame {
                             -> Option<BuiltDisplayListIter<'a>> {
         let mut clip_and_scroll = item.clip_and_scroll();
         context.convert_clip_scroll_info_to_nested(&mut clip_and_scroll);
 
         let unreplaced_scroll_id = clip_and_scroll.scroll_node_id;
         clip_and_scroll.scroll_node_id =
             context.apply_scroll_frame_id_replacement(clip_and_scroll.scroll_node_id);
 
+
+        let item_rect_with_offset = item.rect().translate(&reference_frame_relative_offset);
+        let clip_with_offset = item.local_clip_with_offset(&reference_frame_relative_offset);
         match *item.item() {
             SpecificDisplayItem::WebGL(ref info) => {
                 context.builder.add_webgl_rectangle(clip_and_scroll,
-                                                    item.rect(),
-                                                    item.local_clip(),
+                                                    item_rect_with_offset,
+                                                    &clip_with_offset,
                                                     info.context_id);
             }
             SpecificDisplayItem::Image(ref info) => {
                 let image = context.resource_cache.get_image_properties(info.image_key);
                 if let Some(tile_size) = image.tiling {
                     // The image resource is tiled. We have to generate an image primitive
                     // for each tile.
                     let image_size = DeviceUintSize::new(image.descriptor.width,
                                                          image.descriptor.height);
                     self.decompose_image(clip_and_scroll,
                                          context,
-                                         &item.rect(),
-                                         item.local_clip(),
+                                         &item_rect_with_offset,
+                                         &clip_with_offset,
                                          info,
                                          image_size,
                                          tile_size as u32);
                 } else {
                     context.builder.add_image(clip_and_scroll,
-                                              item.rect(),
-                                              item.local_clip(),
+                                              item_rect_with_offset,
+                                              &clip_with_offset,
                                               &info.stretch_size,
                                               &info.tile_spacing,
                                               None,
                                               info.image_key,
                                               info.image_rendering,
                                               None);
                 }
             }
             SpecificDisplayItem::YuvImage(ref info) => {
                 context.builder.add_yuv_image(clip_and_scroll,
-                                              item.rect(),
-                                              item.local_clip(),
+                                              item_rect_with_offset,
+                                              &clip_with_offset,
                                               info.yuv_data,
                                               info.color_space,
                                               info.image_rendering);
             }
             SpecificDisplayItem::Text(ref text_info) => {
                 context.builder.add_text(clip_and_scroll,
-                                         item.rect(),
-                                         item.local_clip(),
+                                         reference_frame_relative_offset,
+                                         item_rect_with_offset,
+                                         &clip_with_offset,
                                          text_info.font_key,
                                          text_info.size,
                                          &text_info.color,
                                          item.glyphs(),
                                          item.display_list().get(item.glyphs()).count(),
                                          text_info.glyph_options);
             }
             SpecificDisplayItem::Rectangle(ref info) => {
                 if !self.try_to_add_rectangle_splitting_on_clip(context,
-                                                                &item.rect(),
-                                                                item.local_clip(),
+                                                                &item_rect_with_offset,
+                                                                &clip_with_offset,
                                                                 &info.color,
                                                                 &clip_and_scroll) {
                     context.builder.add_solid_rectangle(clip_and_scroll,
-                                                        &item.rect(),
-                                                        item.local_clip(),
+                                                        &item_rect_with_offset,
+                                                        &clip_with_offset,
                                                         &info.color,
                                                         PrimitiveFlags::None);
 
                 }
             }
+            SpecificDisplayItem::Line(ref info) => {
+                context.builder.add_line(clip_and_scroll,
+                                         item.local_clip(),
+                                         info.baseline,
+                                         info.start,
+                                         info.end,
+                                         info.orientation,
+                                         info.width,
+                                         &info.color,
+                                         info.style);
+            }
             SpecificDisplayItem::Gradient(ref info) => {
                 context.builder.add_gradient(clip_and_scroll,
-                                             item.rect(),
-                                             item.local_clip(),
+                                             item_rect_with_offset,
+                                             &clip_with_offset,
                                              info.gradient.start_point,
                                              info.gradient.end_point,
                                              item.gradient_stops(),
                                              item.display_list()
                                                  .get(item.gradient_stops()).count(),
                                              info.gradient.extend_mode,
                                              info.tile_size,
                                              info.tile_spacing);
             }
             SpecificDisplayItem::RadialGradient(ref info) => {
                 context.builder.add_radial_gradient(clip_and_scroll,
-                                                    item.rect(),
-                                                    item.local_clip(),
+                                                    item_rect_with_offset,
+                                                    &clip_with_offset,
                                                     info.gradient.start_center,
                                                     info.gradient.start_radius,
                                                     info.gradient.end_center,
                                                     info.gradient.end_radius,
                                                     info.gradient.ratio_xy,
                                                     item.gradient_stops(),
                                                     info.gradient.extend_mode,
                                                     info.tile_size,
                                                     info.tile_spacing);
             }
             SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
+                let bounds = box_shadow_info.box_bounds.translate(&reference_frame_relative_offset);
                 context.builder.add_box_shadow(clip_and_scroll,
-                                               &box_shadow_info.box_bounds,
-                                               item.local_clip(),
+                                               &bounds,
+                                               &clip_with_offset,
                                                &box_shadow_info.offset,
                                                &box_shadow_info.color,
                                                box_shadow_info.blur_radius,
                                                box_shadow_info.spread_radius,
                                                box_shadow_info.border_radius,
                                                box_shadow_info.clip_mode);
             }
             SpecificDisplayItem::Border(ref info) => {
                 context.builder.add_border(clip_and_scroll,
-                                           item.rect(),
-                                           item.local_clip(),
+                                           item_rect_with_offset,
+                                           &clip_with_offset,
                                            info,
                                            item.gradient_stops(),
                                            item.display_list()
                                                .get(item.gradient_stops()).count());
             }
             SpecificDisplayItem::PushStackingContext(ref info) => {
                 let mut subtraversal = item.sub_iter();
                 self.flatten_stacking_context(&mut subtraversal,
@@ -718,17 +707,18 @@ impl Frame {
                                      .translate(&reference_frame_relative_offset);
                 let content_rect = item.rect().translate(&reference_frame_relative_offset);
                 self.flatten_scroll_frame(context,
                                           pipeline_id,
                                           &clip_and_scroll.scroll_node_id,
                                           &info.id,
                                           &frame_rect,
                                           &content_rect,
-                                          clip_region);
+                                          clip_region,
+                                          info.scroll_sensitivity);
             }
             SpecificDisplayItem::PushNestedDisplayList => {
                 // Using the clip and scroll already processed for nesting here
                 // means that in the case of multiple nested display lists, we
                 // will enter the outermost ids into the table and avoid having
                 // to do a replacement for every level of nesting.
                 context.push_nested_display_list_ids(clip_and_scroll);
             }
@@ -737,17 +727,17 @@ impl Frame {
             // Do nothing; these are dummy items for the display list parser
             SpecificDisplayItem::SetGradientStops => { }
 
             SpecificDisplayItem::PopStackingContext =>
                 unreachable!("Should have returned in parent method."),
             SpecificDisplayItem::PushTextShadow(shadow) => {
                 context.builder.push_text_shadow(shadow,
                                                  clip_and_scroll,
-                                                 item.local_clip());
+                                                 &clip_with_offset);
             }
             SpecificDisplayItem::PopTextShadow => {
                 context.builder.pop_text_shadow();
             }
         }
         None
     }
 
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,37 +1,40 @@
 /* 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, BoxShadowClipMode, ClipAndScrollInfo, ClipId, ColorF};
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
 use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
-use api::{LayerToScrollTransform, LayerVector2D, LocalClip, PipelineId, RepeatMode, TextShadow};
-use api::{TileOffset, TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
+use api::{LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation, LineStyle};
+use api::{LocalClip, PipelineId, RepeatMode, ScrollSensitivity, TextShadow, TileOffset};
+use api::{TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
 use app_units::Au;
+use fnv::FnvHasher;
 use frame::FrameId;
 use gpu_cache::GpuCache;
 use internal_types::HardwareCompositeOp;
 use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo};
 use plane_split::{BspSplitter, Polygon, Splitter};
-use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, PrimitiveKind};
+use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, LinePrimitive, PrimitiveKind};
 use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu, TextRunMode};
 use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextShadowPrimitiveCpu};
 use prim_store::{BoxShadowPrimitiveCpu, TexelRect, YuvImagePrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_task::{AlphaRenderItem, ClipWorkItem, MaskCacheKey, RenderTask, RenderTaskIndex};
 use render_task::{RenderTaskId, RenderTaskLocation};
 use resource_cache::ResourceCache;
 use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use std::{cmp, f32, i32, mem, usize};
 use std::collections::HashMap;
+use std::hash::BuildHasherDefault;
 use euclid::{SideOffsets2D, vec2, vec3};
 use tiling::{ContextIsolation, StackingContextIndex};
 use tiling::{ClipScrollGroup, ClipScrollGroupIndex, CompositeOps, DisplayListMap, Frame};
 use tiling::{PackedLayer, PackedLayerIndex, PrimitiveFlags, PrimitiveRunCmd, RenderPass};
 use tiling::{RenderTargetContext, RenderTaskCollection, ScrollbarPrimitive, StackingContext};
 use util::{self, pack_as_float, subtract_rect, recycle_vec};
 use util::{MatrixHelpers, RectHelpers};
 
@@ -110,16 +113,19 @@ pub struct FrameBuilder {
     screen_size: DeviceUintSize,
     background_color: Option<ColorF>,
     prim_store: PrimitiveStore,
     cmds: Vec<PrimitiveRunCmd>,
     config: FrameBuilderConfig,
 
     stacking_context_store: Vec<StackingContext>,
     clip_scroll_group_store: Vec<ClipScrollGroup>,
+    clip_scroll_group_indices: HashMap<ClipAndScrollInfo,
+                                       ClipScrollGroupIndex,
+                                       BuildHasherDefault<FnvHasher>>,
     packed_layers: Vec<PackedLayer>,
 
     // A stack of the current text-shadow primitives.
     shadow_prim_stack: Vec<PrimitiveIndex>,
 
     scrollbar_prims: Vec<ScrollbarPrimitive>,
 
     /// A stack of scroll nodes used during display list processing to properly
@@ -140,16 +146,17 @@ impl 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: HashMap::default(),
                     cmds: recycle_vec(prev.cmds),
                     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(),
                     screen_size,
@@ -157,56 +164,52 @@ 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: HashMap::default(),
                     cmds: 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(),
                     screen_size,
                     background_color,
                     config,
                     has_root_stacking_context: false,
                 }
             }
         }
     }
 
-    pub fn create_clip_scroll_group_if_necessary(&mut self,
-                                                 stacking_context_index: StackingContextIndex,
-                                                 info: ClipAndScrollInfo) {
-        if self.stacking_context_store[stacking_context_index.0].has_clip_scroll_group(info) {
+    pub fn create_clip_scroll_group_if_necessary(&mut self, info: ClipAndScrollInfo) {
+        if self.clip_scroll_group_indices.contains_key(&info) {
             return;
         }
 
-        let group_index = self.create_clip_scroll_group(stacking_context_index, info);
-        let stacking_context = &mut self.stacking_context_store[stacking_context_index.0];
-        stacking_context.clip_scroll_groups.push(group_index);
+        let group_index = self.create_clip_scroll_group(info);
+        self.clip_scroll_group_indices.insert(info, group_index);
     }
 
     /// Create a primitive and add it to the prim store. This method doesn't
     /// add the primitive to the draw list, so can be used for creating
     /// sub-primitives.
     fn create_primitive(&mut self,
                         clip_and_scroll: ClipAndScrollInfo,
                         rect: &LayerRect,
                         local_clip: &LocalClip,
                         extra_clips: &[ClipSource],
                         container: PrimitiveContainer) -> PrimitiveIndex {
-        let stacking_context_index = *self.stacking_context_stack.last().unwrap();
-
-        self.create_clip_scroll_group_if_necessary(stacking_context_index, clip_and_scroll);
+        self.create_clip_scroll_group_if_necessary(clip_and_scroll);
 
         let mut clip_sources = extra_clips.to_vec();
         if let &LocalClip::RoundedRect(_, _) = local_clip {
             clip_sources.push(ClipSource::Region(ClipRegion::create_for_local_clip(local_clip)))
         }
 
         let clip_info = if !clip_sources.is_empty() {
             Some(MaskCacheInfo::new(&clip_sources))
@@ -256,25 +259,21 @@ impl FrameBuilder {
                                                extra_clips,
                                                container);
 
         self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
 
         prim_index
     }
 
-    pub fn create_clip_scroll_group(&mut self,
-                                    stacking_context_index: StackingContextIndex,
-                                    info: ClipAndScrollInfo)
-                                    -> ClipScrollGroupIndex {
+    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 {
-            stacking_context_index,
             scroll_node_id: info.scroll_node_id,
             clip_node_id: info.clip_node_id(),
             packed_layer_index,
             screen_bounding_rect: None,
          });
 
         ClipScrollGroupIndex(self.clip_scroll_group_store.len() - 1, info)
     }
@@ -385,16 +384,17 @@ impl FrameBuilder {
         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,
                               clip_scroll_tree.root_reference_frame_id,
                               pipeline_id,
                               &viewport_rect,
                               content_size,
+                              ScrollSensitivity::ScriptAndInputEvents,
                               clip_scroll_tree);
 
         topmost_scrolling_node_id
     }
 
     pub fn add_clip_node(&mut self,
                          new_node_id: ClipId,
                          parent_id: ClipId,
@@ -408,21 +408,23 @@ impl FrameBuilder {
     }
 
     pub fn add_scroll_frame(&mut self,
                             new_node_id: ClipId,
                             parent_id: ClipId,
                             pipeline_id: PipelineId,
                             frame_rect: &LayerRect,
                             content_size: &LayerSize,
+                            scroll_sensitivity: ScrollSensitivity,
                             clip_scroll_tree: &mut ClipScrollTree) {
         let node = ClipScrollNode::new_scroll_frame(pipeline_id,
                                                     parent_id,
                                                     frame_rect,
-                                                    content_size);
+                                                    content_size,
+                                                    scroll_sensitivity);
 
         clip_scroll_tree.add_node(node, new_node_id);
     }
 
     pub fn pop_reference_frame(&mut self) {
         self.reference_frame_stack.pop();
     }
 
@@ -464,62 +466,104 @@ impl FrameBuilder {
     }
 
     pub fn add_solid_rectangle(&mut self,
                                clip_and_scroll: ClipAndScrollInfo,
                                rect: &LayerRect,
                                local_clip: &LocalClip,
                                color: &ColorF,
                                flags: PrimitiveFlags) {
-        // TODO(gw): This is here as a temporary measure to allow
-        //           solid rectangles to be drawn into an
-        //           (unblurred) text-shadow. Supporting this allows
-        //           a WR update in Servo, since the tests rely
-        //           on this functionality. Once the complete
-        //           text decoration support is added (via the
-        //           Line display item) this can be removed, so that
-        //           rectangles don't participate in text shadows.
-        let mut trivial_shadows = Vec::new();
+        let prim = RectanglePrimitive {
+            color: *color,
+        };
+
+        let prim_index = self.add_primitive(clip_and_scroll,
+                                            rect,
+                                            local_clip,
+                                            &[],
+                                            PrimitiveContainer::Rectangle(prim));
+
+        match flags {
+            PrimitiveFlags::None => {}
+            PrimitiveFlags::Scrollbar(clip_id, border_radius) => {
+                self.scrollbar_prims.push(ScrollbarPrimitive {
+                    prim_index,
+                    clip_id,
+                    border_radius,
+                });
+            }
+        }
+    }
+
+    pub fn add_line(&mut self,
+                    clip_and_scroll: ClipAndScrollInfo,
+                    local_clip: &LocalClip,
+                    baseline: f32,
+                    start: f32,
+                    end: f32,
+                    orientation: LineOrientation,
+                    width: f32,
+                    color: &ColorF,
+                    style: LineStyle) {
+        let new_rect = match orientation {
+            LineOrientation::Horizontal => {
+                LayerRect::new(LayerPoint::new(start, baseline),
+                               LayerSize::new(end - start, width))
+            }
+            LineOrientation::Vertical => {
+                LayerRect::new(LayerPoint::new(baseline, start),
+                               LayerSize::new(width, end - start))
+            }
+        };
+
+        let line = LinePrimitive {
+            color: *color,
+            style: style,
+            orientation: orientation,
+        };
+
+        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 {
-                trivial_shadows.push(shadow_prim.shadow);
+                fast_text_shadow_prims.push(shadow_prim.shadow);
             }
         }
-        for shadow in trivial_shadows {
+        for shadow in fast_text_shadow_prims {
+            let mut line = line.clone();
+            line.color = shadow.color;
             self.add_primitive(clip_and_scroll,
-                               &rect.translate(&shadow.offset),
+                               &new_rect.translate(&shadow.offset),
                                local_clip,
                                &[],
-                               PrimitiveContainer::Rectangle(RectanglePrimitive {
-                                   color: shadow.color,
-                               }));
+                               PrimitiveContainer::Line(line));
         }
 
+        let prim_index = self.create_primitive(clip_and_scroll,
+                                               &new_rect,
+                                               local_clip,
+                                               &[],
+                                               PrimitiveContainer::Line(line));
+
         if color.a > 0.0 {
-            let prim = RectanglePrimitive {
-                color: *color,
-            };
-
-            let prim_index = self.add_primitive(clip_and_scroll,
-                                                rect,
-                                                local_clip,
-                                                &[],
-                                                PrimitiveContainer::Rectangle(prim));
+            self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
+        }
 
-            match flags {
-                PrimitiveFlags::None => {}
-                PrimitiveFlags::Scrollbar(clip_id, border_radius) => {
-                    self.scrollbar_prims.push(ScrollbarPrimitive {
-                        prim_index,
-                        clip_id,
-                        border_radius,
-                    });
-                }
+        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];
+
+            // Only run real blurs here (fast path zero blurs are handled above).
+            if shadow_prim.shadow.blur_radius > 0.0 {
+                let shadow_rect = new_rect.inflate(shadow_prim.shadow.blur_radius,
+                                                   shadow_prim.shadow.blur_radius);
+                shadow_metadata.local_rect = shadow_metadata.local_rect.union(&shadow_rect);
+                shadow_prim.primitives.push(prim_index);
             }
         }
     }
 
     pub fn add_border(&mut self,
                       clip_and_scroll: ClipAndScrollInfo,
                       rect: LayerRect,
                       local_clip: &LocalClip,
@@ -818,16 +862,17 @@ impl FrameBuilder {
                            &rect,
                            local_clip,
                            &[],
                            PrimitiveContainer::RadialGradient(radial_gradient_cpu));
     }
 
     pub fn add_text(&mut self,
                     clip_and_scroll: ClipAndScrollInfo,
+                    run_offset: LayoutVector2D,
                     rect: LayerRect,
                     local_clip: &LocalClip,
                     font_key: FontKey,
                     size: Au,
                     color: &ColorF,
                     glyph_range: ItemRange<GlyphInstance>,
                     glyph_count: usize,
                     glyph_options: Option<GlyphOptions>) {
@@ -872,17 +917,17 @@ impl FrameBuilder {
             font_key,
             logical_font_size: size,
             glyph_range,
             glyph_count,
             glyph_instances: Vec::new(),
             glyph_options,
             normal_render_mode,
             shadow_render_mode,
-            offset: LayerVector2D::zero(),
+            offset: run_offset,
             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
@@ -891,17 +936,17 @@ impl FrameBuilder {
         // 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.color = shadow_prim.shadow.color;
-                text_prim.offset = shadow_prim.shadow.offset;
+                text_prim.offset += shadow_prim.shadow.offset;
                 fast_text_shadow_prims.push(text_prim);
             }
         }
         for text_prim in fast_text_shadow_prims {
             self.add_primitive(clip_and_scroll,
                                &rect.translate(&text_prim.offset),
                                local_clip,
                                &[],
@@ -1201,21 +1246,21 @@ impl FrameBuilder {
                          clip_and_scroll: ClipAndScrollInfo,
                          rect: LayerRect,
                          clip_rect: &LocalClip,
                          yuv_data: YuvData,
                          color_space: YuvColorSpace,
                          image_rendering: ImageRendering) {
         let format = yuv_data.get_format();
         let yuv_key = match yuv_data {
-            YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::new(0, 0)],
+            YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::dummy()],
             YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) =>
                 [plane_0, plane_1, plane_2],
             YuvData::InterleavedYCbCr(plane_0) =>
-                [plane_0, ImageKey::new(0, 0), ImageKey::new(0, 0)],
+                [plane_0, ImageKey::dummy(), ImageKey::dummy()],
         };
 
         let prim_cpu = YuvImagePrimitiveCpu {
             yuv_key,
             format,
             color_space,
             image_rendering,
             gpu_block: [rect.size.width, rect.size.height, 0.0, 0.0].into(),
@@ -1460,25 +1505,21 @@ impl FrameBuilder {
                             current_task.as_alpha_batch().items.push(item);
                         }
                         preserve_3d_map.clear();
                         next_z += 1;
                     }
                 }
                 PrimitiveRunCmd::PrimitiveRun(first_prim_index, prim_count, clip_and_scroll) => {
                     let stacking_context_index = *sc_stack.last().unwrap();
-                    let stacking_context = &self.stacking_context_store[stacking_context_index.0];
-
-                    if !stacking_context.is_visible {
+                    if !self.stacking_context_store[stacking_context_index.0].is_visible {
                         continue;
                     }
 
-                    let stacking_context_index = *sc_stack.last().unwrap();
-                    let group_index = self.stacking_context_store[stacking_context_index.0]
-                                          .clip_scroll_group(clip_and_scroll);
+                    let group_index = *self.clip_scroll_group_indices.get(&clip_and_scroll).unwrap();
                     if self.clip_scroll_group_store[group_index.0].screen_bounding_rect.is_none() {
                         debug!("\tcs-group {:?} screen rect is None", group_index);
                         continue
                     }
 
                     debug!("\trun of {} items into {:?}", prim_count, current_task.id);
 
                     for i in 0..prim_count {
@@ -1659,17 +1700,16 @@ impl<'a> LayerRectCalculationAndCullingP
             current_clip_info: None,
         };
         pass.run();
     }
 
     fn run(&mut self) {
         self.recalculate_clip_scroll_nodes();
         self.recalculate_clip_scroll_groups();
-        self.compute_stacking_context_visibility();
 
         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) =>
@@ -1733,63 +1773,43 @@ impl<'a> LayerRectCalculationAndCullingP
                 }
             }
         }
     }
 
     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 stacking_context_index = group.stacking_context_index;
-            let stacking_context = &mut self.frame_builder
-                                            .stacking_context_store[stacking_context_index.0];
-
             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];
 
-            // The world content transform is relative to the containing reference frame,
-            // so we translate into the origin of the stacking context itself.
-            let transform = scroll_node.world_content_transform
-                .pre_translate(stacking_context.reference_frame_offset.to_3d());
+            debug!("\tProcessing group scroll={:?}, clip={:?}",
+                   group.scroll_node_id, group.clip_node_id);
 
-            if !packed_layer.set_transform(transform) || !stacking_context.can_contribute_to_scene() {
-                debug!("\t{:?} unable to set transform or contribute with {:?}",
-                    stacking_context_index, transform);
+            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(&-stacking_context.reference_frame_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{:?} local viewport {:?} screen bound {:?}",
-                stacking_context_index, local_viewport_rect, group.screen_bounding_rect);
-        }
-    }
-
-    fn compute_stacking_context_visibility(&mut self) {
-        for context_index in 0..self.frame_builder.stacking_context_store.len() {
-            let is_visible = {
-                // We don't take into account visibility of children here, so we must
-                // do that later.
-                let stacking_context = &self.frame_builder.stacking_context_store[context_index];
-                stacking_context.clip_scroll_groups.iter().any(|group_index| {
-                    self.frame_builder.clip_scroll_group_store[group_index.0].is_visible()
-                })
-            };
-            self.frame_builder.stacking_context_store[context_index].is_visible = is_visible;
+            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_frame_id, reference_frame_bounds) = {
             let stacking_context =
@@ -1808,18 +1828,18 @@ impl<'a> LayerRectCalculationAndCullingP
         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_frame_id {
                 let child_bounds = reference_frame_bounds.translate(&-parent.reference_frame_offset);
                 parent.isolated_items_bounds = parent.isolated_items_bounds.union(&child_bounds);
             }
-            // The previous compute_stacking_context_visibility pass did not take into
-            // account visibility of children, so we do that now.
+            // 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
@@ -1899,29 +1919,37 @@ impl<'a> LayerRectCalculationAndCullingP
 
     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 =
-                &self.frame_builder.stacking_context_store[stacking_context_index.0];
+                &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
+            if !stacking_context.can_contribute_to_scene() {
+                return;
+            }
 
-            if !stacking_context.is_visible {
+            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;
             }
 
-            let group_index = stacking_context.clip_scroll_group(clip_and_scroll);
-            let clip_scroll_group = &self.frame_builder.clip_scroll_group_store[group_index.0];
-            (clip_scroll_group.packed_layer_index,
-             stacking_context.pipeline_id)
+            // 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];
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -1,32 +1,35 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#[cfg(test)]
 use app_units::Au;
 use device::TextureFilter;
 use fnv::FnvHasher;
 use frame::FrameId;
 use platform::font::{FontContext, RasterizedGlyph};
 use profiler::TextureCacheProfileCounters;
 use rayon::ThreadPool;
 use rayon::prelude::*;
 use resource_cache::ResourceClassCache;
 use std::hash::BuildHasherDefault;
 use std::sync::{Arc, Mutex, MutexGuard};
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::collections::hash_map::Entry;
 use std::collections::HashSet;
 use std::mem;
 use texture_cache::{TextureCacheItemId, TextureCache};
-use api::FontTemplate;
-use api::{FontKey, FontRenderMode, ImageData, ImageFormat};
-use api::{ImageDescriptor, ColorF, LayoutPoint};
-use api::{GlyphKey, GlyphOptions, GlyphInstance, GlyphDimensions};
+#[cfg(test)]
+use api::{ColorF, FontRenderMode, IdNamespace};
+use api::{FontInstanceKey, LayoutPoint};
+use api::{FontKey, FontTemplate};
+use api::{ImageData, ImageDescriptor, ImageFormat};
+use api::{GlyphKey, GlyphInstance, GlyphDimensions};
 
 pub type GlyphCache = ResourceClassCache<GlyphRequest, Option<TextureCacheItemId>>;
 
 pub struct FontContexts {
     // These worker are mostly accessed from their corresponding worker threads.
     // The goal is that there should be no noticeable contention on the muteces.
     worker_contexts: Vec<Mutex<FontContext>>,
 
@@ -142,50 +145,40 @@ impl GlyphRasterizer {
     pub fn delete_font(&mut self, font_key: FontKey) {
         self.fonts_to_remove.push(font_key);
     }
 
     pub fn request_glyphs(
         &mut self,
         glyph_cache: &mut GlyphCache,
         current_frame_id: FrameId,
-        font_key: FontKey,
-        size: Au,
-        color: ColorF,
+        font: FontInstanceKey,
         glyph_instances: &[GlyphInstance],
-        render_mode: FontRenderMode,
-        glyph_options: Option<GlyphOptions>,
         requested_items: &mut HashSet<TextureCacheItemId, BuildHasherDefault<FnvHasher>>,
     ) {
-        assert!(self.font_contexts.lock_shared_context().has_font(&font_key));
+        assert!(self.font_contexts.lock_shared_context().has_font(&font.font_key));
 
         let mut glyphs = Vec::with_capacity(glyph_instances.len());
 
         // select glyphs that have not been requested yet.
         for glyph in glyph_instances {
-            let glyph_request = GlyphRequest::new(
-                font_key,
-                size,
-                color,
-                glyph.index,
-                glyph.point,
-                render_mode,
-                glyph_options,
-            );
+            let glyph_request = GlyphRequest::new(font.clone(),
+                                                  glyph.index,
+                                                  glyph.point);
 
             match glyph_cache.entry(glyph_request.clone(), current_frame_id) {
                 Entry::Occupied(entry) => {
                     if let &Some(texture_cache_item_id) = entry.get() {
                         requested_items.insert(texture_cache_item_id);
                     }
                 }
                 Entry::Vacant(..) => {
                     if !self.pending_glyphs.contains(&glyph_request) {
                         self.pending_glyphs.insert(glyph_request.clone());
-                        glyphs.push(glyph_request);
+                        glyphs.push(glyph_request.clone());
                     }
                 }
             }
         }
 
         if glyphs.is_empty() {
             return;
         }
@@ -196,38 +189,40 @@ impl GlyphRasterizer {
         // possible and in that task use rayon's fork join dispatch to rasterize the
         // glyphs in the thread pool.
         self.workers.spawn(move || {
             let jobs = glyphs.par_iter().map(|request: &GlyphRequest| {
                 profile_scope!("glyph-raster");
                 let mut context = font_contexts.lock_current_context();
                 let job = GlyphRasterJob {
                     request: request.clone(),
-                    result: context.rasterize_glyph(
-                        &request.key,
-                        request.render_mode,
-                        request.glyph_options
-                    ),
+                    result: context.rasterize_glyph(&request.font, &request.key),
                 };
 
                 // Sanity check.
                 if let Some(ref glyph) = job.result {
                     let bpp = 4; // We always render glyphs in 32 bits RGBA format.
                     assert_eq!(glyph.bytes.len(), bpp * (glyph.width * glyph.height) as usize);
                 }
 
                 job
             }).collect();
 
             glyph_tx.send(jobs).unwrap();
         });
     }
 
-    pub fn get_glyph_dimensions(&mut self, glyph_key: &GlyphKey) -> Option<GlyphDimensions> {
-        self.font_contexts.lock_shared_context().get_glyph_dimensions(glyph_key)
+    pub fn get_glyph_dimensions(&mut self,
+                                font: &FontInstanceKey,
+                                glyph_key: &GlyphKey) -> Option<GlyphDimensions> {
+        self.font_contexts.lock_shared_context().get_glyph_dimensions(font, glyph_key)
+    }
+
+    pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+        self.font_contexts.lock_shared_context().get_glyph_index(font_key, ch)
     }
 
     pub fn resolve_glyphs(
         &mut self,
         current_frame_id: FrameId,
         glyph_cache: &mut GlyphCache,
         texture_cache: &mut TextureCache,
         requested_items: &mut HashSet<TextureCacheItemId, BuildHasherDefault<FnvHasher>>,
@@ -317,34 +312,27 @@ impl FontContext {
             }
         }
     }
 }
 
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
 pub struct GlyphRequest {
     pub key: GlyphKey,
-    pub render_mode: FontRenderMode,
-    pub glyph_options: Option<GlyphOptions>,
+    pub font: FontInstanceKey,
 }
 
 impl GlyphRequest {
     pub fn new(
-        font_key: FontKey,
-        size: Au,
-        color: ColorF,
+        font: FontInstanceKey,
         index: u32,
-        point: LayoutPoint,
-        render_mode: FontRenderMode,
-        glyph_options: Option<GlyphOptions>,
-    ) -> Self {
+        point: LayoutPoint) -> Self {
         GlyphRequest {
-            key: GlyphKey::new(font_key, size, color, index, point, render_mode),
-            render_mode,
-            glyph_options,
+            key: GlyphKey::new(index, point, font.render_mode),
+            font,
         }
     }
 }
 
 struct GlyphRasterJob {
     request: GlyphRequest,
     result: Option<RasterizedGlyph>,
 }
@@ -362,39 +350,43 @@ fn raterize_200_glyphs() {
     let mut glyph_rasterizer = GlyphRasterizer::new(workers);
     let mut glyph_cache = GlyphCache::new();
     let mut requested_items = HashSet::default();
 
     let mut font_file = File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file");
     let mut font_data = vec![];
     font_file.read_to_end(&mut font_data).expect("failed to read font file");
 
-    let font_key = FontKey::new(0, 0);
+    let font_key = FontKey::new(IdNamespace(0), 0);
     glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0));
 
     let frame_id = FrameId(1);
 
     let mut glyph_instances = Vec::with_capacity(200);
     for i in 0..200 {
         glyph_instances.push(GlyphInstance {
             index: i, // It doesn't matter which glyphs we are actually rendering.
             point: LayoutPoint::new(0.0, 0.0),
         });
     }
 
+    let font = FontInstanceKey {
+        font_key,
+        color: ColorF::new(0.0, 0.0, 0.0, 1.0).into(),
+        size: Au::from_px(32),
+        render_mode: FontRenderMode::Subpixel,
+        glyph_options: None,
+    };
+
     for i in 0..4 {
         glyph_rasterizer.request_glyphs(
             &mut glyph_cache,
             frame_id,
-            font_key,
-            Au::from_px(32),
-            ColorF::new(0.0, 0.0, 0.0, 1.0),
+            font.clone(),
             &glyph_instances[(50 * i)..(50 * (i + 1))],
-            FontRenderMode::Subpixel,
-            None,
             &mut requested_items,
         );
     }
 
     glyph_rasterizer.delete_font(font_key);
 
     glyph_rasterizer.resolve_glyphs(
         frame_id,
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.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 app_units::Au;
 use device::TextureFilter;
 use fnv::FnvHasher;
 use profiler::BackendProfileCounters;
 use std::collections::{HashMap, HashSet};
 use std::f32;
 use std::hash::BuildHasherDefault;
 use std::{i32, usize};
 use std::path::PathBuf;
 use std::sync::Arc;
 use tiling;
 use renderer::BlendMode;
-use api::{ClipId, ColorF, DeviceUintRect, Epoch, ExternalImageData, ExternalImageId};
+use api::{ClipId, ColorU, DeviceUintRect, Epoch, ExternalImageData, ExternalImageId};
 use api::{DevicePoint, ImageData, ImageFormat, PipelineId};
 
 // An ID for a texture that is owned by the
 // texture cache module. This can include atlases
 // or standalone textures allocated via the
 // texture cache (e.g. if an image is too large
 // to be added to an atlas). The texture cache
 // manages the allocation and freeing of these
@@ -41,19 +40,16 @@ pub enum SourceTexture {
     TextureCache(CacheTextureId),
     External(ExternalImageData),
     #[cfg_attr(not(feature = "webgl"), allow(dead_code))]
     /// This is actually a gl::GLuint, with the shared texture id between the
     /// main context and the WebGL context.
     WebGL(u32),
 }
 
-const COLOR_FLOAT_TO_FIXED: f32 = 255.0;
-pub const ANGLE_FLOAT_TO_FIXED: f32 = 65535.0;
-
 pub const ORTHO_NEAR_PLANE: f32 = -1000000.0;
 pub const ORTHO_FAR_PLANE: f32 = 1000000.0;
 
 #[derive(Debug, PartialEq, Eq)]
 pub enum TextureSampler {
     Color0,
     Color1,
     Color2,
@@ -124,75 +120,53 @@ pub enum ClipAttribute {
     // instance frequency
     RenderTaskIndex,
     LayerIndex,
     DataIndex,
     SegmentIndex,
     ResourceAddress,
 }
 
-// A packed RGBA8 color ordered for vertex data or similar.
-
-#[derive(Debug, Clone, Copy)]
-#[repr(C)]
-pub struct PackedColor {
-    pub r: u8,
-    pub g: u8,
-    pub b: u8,
-    pub a: u8,
-}
-
-impl PackedColor {
-    pub fn from_color(color: &ColorF) -> PackedColor {
-        PackedColor {
-            r: (0.5 + color.r * COLOR_FLOAT_TO_FIXED).floor() as u8,
-            g: (0.5 + color.g * COLOR_FLOAT_TO_FIXED).floor() as u8,
-            b: (0.5 + color.b * COLOR_FLOAT_TO_FIXED).floor() as u8,
-            a: (0.5 + color.a * COLOR_FLOAT_TO_FIXED).floor() as u8,
-        }
-    }
-}
-
 #[derive(Debug, Clone, Copy)]
 #[repr(C)]
 pub struct PackedVertex {
     pub pos: [f32; 2],
 }
 
 #[derive(Debug)]
 #[repr(C)]
 pub struct DebugFontVertex {
     pub x: f32,
     pub y: f32,
-    pub color: PackedColor,
+    pub color: ColorU,
     pub u: f32,
     pub v: f32,
 }
 
 impl DebugFontVertex {
-    pub fn new(x: f32, y: f32, u: f32, v: f32, color: PackedColor) -> DebugFontVertex {
+    pub fn new(x: f32, y: f32, u: f32, v: f32, color: ColorU) -> DebugFontVertex {
         DebugFontVertex {
             x,
             y,
             color,
             u,
             v,
         }
     }
 }
 
 #[repr(C)]
 pub struct DebugColorVertex {
     pub x: f32,
     pub y: f32,
-    pub color: PackedColor,
+    pub color: ColorU,
 }
 
 impl DebugColorVertex {
-    pub fn new(x: f32, y: f32, color: PackedColor) -> DebugColorVertex {
+    pub fn new(x: f32, y: f32, color: ColorU) -> DebugColorVertex {
         DebugColorVertex {
             x,
             y,
             color,
         }
     }
 }
 
@@ -287,47 +261,26 @@ impl RendererFrame {
     }
 }
 
 pub enum ResultMsg {
     RefreshShader(PathBuf),
     NewFrame(RendererFrame, TextureUpdateList, BackendProfileCounters),
 }
 
-#[repr(u32)]
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum AxisDirection {
-    Horizontal,
-    Vertical,
-}
-
 #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
 pub struct StackingContextIndex(pub usize);
 
 #[derive(Clone, Copy, Debug)]
 pub struct UvRect {
     pub uv0: DevicePoint,
     pub uv1: DevicePoint,
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-pub enum LowLevelFilterOp {
-    Blur(Au, AxisDirection),
-    Brightness(Au),
-    Contrast(Au),
-    Grayscale(Au),
-    /// Fixed-point in `ANGLE_FLOAT_TO_FIXED` units.
-    HueRotate(i32),
-    Invert(Au),
-    Opacity(Au),
-    Saturate(Au),
-    Sepia(Au),
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 pub enum HardwareCompositeOp {
     PremultipliedAlpha,
 }
 
 impl HardwareCompositeOp {
     pub fn to_blend_mode(&self) -> BlendMode {
         match *self {
             HardwareCompositeOp::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -11,19 +11,20 @@ use core_graphics::data_provider::CGData
 use core_graphics::font::{CGFont, CGGlyph};
 use core_graphics::geometry::{CGPoint, CGSize, CGRect};
 use core_text::font::CTFont;
 use core_text::font_descriptor::kCTFontDefaultOrientation;
 use core_text;
 use std::collections::HashMap;
 use std::collections::hash_map::Entry;
 use api::{ColorU, FontKey, FontRenderMode, GlyphDimensions};
-use api::{GlyphKey, GlyphOptions, SubpixelPoint};
-use api::NativeFontHandle;
+use api::{GlyphKey, SubpixelPoint};
+use api::{FontInstanceKey, NativeFontHandle};
 use gamma_lut::{GammaLut, Color as ColorLut};
+use std::ptr;
 
 pub struct FontContext {
     cg_fonts: HashMap<FontKey, CGFont>,
     ct_fonts: HashMap<(FontKey, Au), CTFont>,
     gamma_lut: GammaLut,
 }
 
 // core text is safe to use on multiple threads and non-shareable resources are
@@ -51,16 +52,17 @@ impl RasterizedGlyph {
 }
 
 struct GlyphMetrics {
     rasterized_left: i32,
     rasterized_descent: i32,
     rasterized_ascent: i32,
     rasterized_width: u32,
     rasterized_height: u32,
+    advance: f32,
 }
 
 // According to the Skia source code, there's no public API to
 // determine if subpixel AA is supported. So jrmuizel ported
 // this function from Skia which is used to check if a glyph
 // can be rendered with subpixel AA.
 fn supports_subpixel_aa() -> bool {
     let mut cg_context = CGContext::create_bitmap_context(None, 1, 1, 8, 4,
@@ -92,16 +94,17 @@ fn get_glyph_metrics(ct_font: &CTFont,
         // Instead we are better off returning zero-sized metrics because this special
         // case is handled by the callers of this method.
         return GlyphMetrics {
             rasterized_left: 0,
             rasterized_width: 0,
             rasterized_height: 0,
             rasterized_ascent: 0,
             rasterized_descent: 0,
+            advance: 0.0,
         };
     }
 
     let (x_offset, y_offset) = subpixel_point.to_f64();
 
     // First round out to pixel boundaries
     // CG Origin is bottom left
     let mut left = bounds.origin.x.floor() as i32;
@@ -120,22 +123,28 @@ fn get_glyph_metrics(ct_font: &CTFont,
     left -= 1;
     bottom -= 1;
     right += 1;
     top += 1;
 
     let width = right - left;
     let height = top - bottom;
 
+    let advance = ct_font.get_advances_for_glyphs(kCTFontDefaultOrientation,
+                                                  &glyph,
+                                                  ptr::null_mut(),
+                                                  1);
+
     let metrics = GlyphMetrics {
         rasterized_left: left,
         rasterized_width: width as u32,
         rasterized_height: height as u32,
         rasterized_ascent: top,
         rasterized_descent: -bottom,
+        advance: advance as f32,
     };
 
     metrics
 }
 
 impl FontContext {
     pub fn new() -> FontContext {
         debug!("Test for subpixel AA support: {}", supports_subpixel_aa());
@@ -205,29 +214,49 @@ impl FontContext {
                         cg_font,
                         size.to_f64_px());
                 entry.insert(ct_font.clone());
                 Some(ct_font)
             }
         }
     }
 
+    pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+        let character = ch as u16;
+        let mut glyph = 0;
+
+        self.get_ct_font(font_key, Au(16 * 60))
+            .and_then(|ref ct_font| {
+                let result = ct_font.get_glyphs_for_characters(&character,
+                                                               &mut glyph,
+                                                               1);
+
+                if result {
+                    Some(glyph as u32)
+                } else {
+                    None
+                }
+            })
+    }
+
     pub fn get_glyph_dimensions(&mut self,
+                                font: &FontInstanceKey,
                                 key: &GlyphKey) -> Option<GlyphDimensions> {
-        self.get_ct_font(key.font_key, key.size).and_then(|ref ct_font| {
+        self.get_ct_font(font.font_key, font.size).and_then(|ref ct_font| {
             let glyph = key.index as CGGlyph;
             let metrics = get_glyph_metrics(ct_font, glyph, &key.subpixel_point);
             if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
                 None
             } else {
                 Some(GlyphDimensions {
                     left: metrics.rasterized_left,
                     top: metrics.rasterized_ascent,
                     width: metrics.rasterized_width as u32,
                     height: metrics.rasterized_height as u32,
+                    advance: metrics.advance,
                 })
             }
         })
     }
 
     // Assumes the pixels here are linear values from CG
     fn gamma_correct_pixels(&self, pixels: &mut Vec<u8>, width: usize,
                             height: usize, render_mode: FontRenderMode,
@@ -263,33 +292,32 @@ impl FontContext {
                 let a = pixel[3];
                 print!("({}, {}, {}, {}) ", r, g, b, a);
             }
             println!("");
         }
     }
 
     pub fn rasterize_glyph(&mut self,
-                           key: &GlyphKey,
-                           render_mode: FontRenderMode,
-                           _glyph_options: Option<GlyphOptions>)
+                           font: &FontInstanceKey,
+                           key: &GlyphKey)
                            -> Option<RasterizedGlyph> {
 
-        let ct_font = match self.get_ct_font(key.font_key, key.size) {
+        let ct_font = match self.get_ct_font(font.font_key, font.size) {
             Some(font) => font,
             None => return Some(RasterizedGlyph::blank())
         };
 
         let glyph = key.index as CGGlyph;
         let metrics = get_glyph_metrics(&ct_font, glyph, &key.subpixel_point);
         if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
             return Some(RasterizedGlyph::blank())
         }
 
-        let context_flags = match render_mode {
+        let context_flags = match font.render_mode {
             FontRenderMode::Subpixel => kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
             FontRenderMode::Alpha | FontRenderMode::Mono => kCGImageAlphaPremultipliedLast,
         };
 
         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,
@@ -313,17 +341,17 @@ 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 render_mode {
+        let (antialias, smooth) = match font.render_mode {
             FontRenderMode::Subpixel => (true, true),
             FontRenderMode::Alpha => (true, false),
             FontRenderMode::Mono => (false, false),
         };
 
         // 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);
@@ -364,49 +392,49 @@ impl FontContext {
         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 render_mode == FontRenderMode::Subpixel {
+        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 mut 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 render_mode {
+                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,
-                                  render_mode,
-                                  key.color);
+                                  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
@@ -1,24 +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 app_units::Au;
-use api::{FontKey, FontRenderMode, GlyphDimensions};
-use api::{NativeFontHandle, GlyphOptions};
+use api::{FontInstanceKey, FontKey, FontRenderMode, GlyphDimensions};
+use api::{NativeFontHandle};
 use api::{GlyphKey};
 
 use freetype::freetype::{FT_Render_Mode, FT_Pixel_Mode};
 use freetype::freetype::{FT_Done_FreeType, FT_Library_SetLcdFilter};
 use freetype::freetype::{FT_Library, FT_Set_Char_Size};
 use freetype::freetype::{FT_Face, FT_Long, FT_UInt, FT_F26Dot6};
 use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph};
 use freetype::freetype::{FT_New_Memory_Face, FT_GlyphSlot, FT_LcdFilter};
-use freetype::freetype::{FT_Done_Face, FT_Error, FT_Int32};
+use freetype::freetype::{FT_Done_Face, FT_Error, FT_Int32, FT_Get_Char_Index};
 
 use std::{cmp, mem, ptr, slice};
 use std::collections::HashMap;
 
 // This constant is not present in the freetype
 // bindings due to bindgen not handling the way
 // the macro is defined.
 const FT_LOAD_TARGET_LIGHT: FT_Int32 = 1 << 16;
@@ -150,37 +150,56 @@ impl FontContext {
             let top = metrics.horiBearingY >> 6;
             let right = (metrics.horiBearingX + metrics.width + 0x3f) >> 6;
             let bottom = (metrics.horiBearingY + metrics.height + 0x3f) >> 6;
             Some(GlyphDimensions {
                 left: left as i32,
                 top: top as i32,
                 width: (right - left) as u32,
                 height: (bottom - top) as u32,
+                advance: metrics.horiAdvance as f32 / 64.0,
             })
         }
     }
 
+    pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+        let face = self.faces
+                       .get(&font_key)
+                       .expect("Unknown font key!");
+        unsafe {
+            let idx = FT_Get_Char_Index(face.face, ch as _);
+            if idx != 0 {
+                Some(idx)
+            } else {
+                None
+            }
+        }
+    }
+
     pub fn get_glyph_dimensions(&mut self,
+                                font: &FontInstanceKey,
                                 key: &GlyphKey) -> Option<GlyphDimensions> {
-        self.load_glyph(key.font_key, key.size, key.index)
+        self.load_glyph(font.font_key,
+                        font.size,
+                        key.index)
             .and_then(Self::get_glyph_dimensions_impl)
     }
 
     pub fn rasterize_glyph(&mut self,
-                           key: &GlyphKey,
-                           render_mode: FontRenderMode,
-                           _glyph_options: Option<GlyphOptions>)
+                           font: &FontInstanceKey,
+                           key: &GlyphKey)
                            -> Option<RasterizedGlyph> {
 
-        let slot = match self.load_glyph(key.font_key, key.size, key.index) {
+        let slot = match self.load_glyph(font.font_key,
+                                         font.size,
+                                         key.index) {
             Some(slot) => slot,
             None => return None,
         };
-        let render_mode = match render_mode {
+        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,
         };
 
         let result = unsafe { FT_Render_Glyph(slot, render_mode) };
         if result != SUCCESS {
             error!("Unable to rasterize {:?} with {:?}, {:?}", key, render_mode, result);
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use std::collections::HashMap;
 use api::{FontKey, FontRenderMode, GlyphDimensions};
-use api::{GlyphKey, GlyphOptions};
+use api::{FontInstanceKey, GlyphKey, GlyphOptions};
 use gamma_lut::{GammaLut, Color as ColorLut};
 
 use dwrote;
 
 lazy_static! {
     static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor {
         family_name: "Arial".to_owned(),
         weight: dwrote::FontWeight::Regular,
@@ -81,38 +81,16 @@ fn dwrite_render_mode(font_face: &dwrote
     if dwrite_render_mode  == dwrote::DWRITE_RENDERING_MODE_OUTLINE {
         // Outline mode is not supported
         return dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC;
     }
 
     dwrite_render_mode
 }
 
-fn get_glyph_dimensions_with_analysis(analysis: dwrote::GlyphRunAnalysis,
-                                      texture_type: dwrote::DWRITE_TEXTURE_TYPE)
-                                      -> Option<GlyphDimensions> {
-    let bounds = analysis.get_alpha_texture_bounds(texture_type);
-
-    let width = (bounds.right - bounds.left) as u32;
-    let height = (bounds.bottom - bounds.top) as u32;
-
-    // Alpha texture bounds can sometimes return an empty rect
-    // Such as for spaces
-    if width == 0 || height == 0 {
-        return None
-    }
-
-    Some(GlyphDimensions {
-        left: bounds.left,
-        top: -bounds.top,
-        width,
-        height,
-    })
-}
-
 impl FontContext {
     pub fn new() -> FontContext {
         // These are the default values we use in Gecko.
         // We use a gamma value of 2.3 for gdi fonts
         // TODO: Fetch this data from Gecko itself.
         let contrast = 1.0;
         let gamma = 1.8;
         let gdi_gamma = 2.3;
@@ -170,65 +148,101 @@ impl FontContext {
                 let g = pixel[1];
                 let b = pixel[2];
                 print!("({}, {}, {}) ", r, g, b, );
             }
             println!("");
         }
     }
 
-    fn create_glyph_analysis(&self, key: &GlyphKey,
-                            render_mode: FontRenderMode,
-                            options: Option<GlyphOptions>) ->
+    fn create_glyph_analysis(&self,
+                             font: &FontInstanceKey,
+                             key: &GlyphKey) ->
                             dwrote::GlyphRunAnalysis {
-        let face = self.fonts.get(&key.font_key).unwrap();
+        let face = self.fonts.get(&font.font_key).unwrap();
         let glyph = key.index as u16;
         let advance = 0.0f32;
         let offset = dwrote::GlyphOffset { advanceOffset: 0.0, ascenderOffset: 0.0 };
 
         let glyph_run = dwrote::DWRITE_GLYPH_RUN {
             fontFace: unsafe { face.as_ptr() },
-            fontEmSize: key.size.to_f32_px(), // size in DIPs (1/96", same as CSS pixels)
+            fontEmSize: font.size.to_f32_px(), // size in DIPs (1/96", same as CSS pixels)
             glyphCount: 1,
             glyphIndices: &glyph,
             glyphAdvances: &advance,
             glyphOffsets: &offset,
             isSideways: 0,
             bidiLevel: 0,
         };
 
-        let dwrite_measure_mode = dwrite_measure_mode(render_mode, options);
+        let dwrite_measure_mode = dwrite_measure_mode(font.render_mode,
+                                                      font.glyph_options);
         let dwrite_render_mode = dwrite_render_mode(face,
-                                                    render_mode,
-                                                    key.size.to_f32_px(),
+                                                    font.render_mode,
+                                                    font.size.to_f32_px(),
                                                     dwrite_measure_mode,
-                                                    options);
+                                                    font.glyph_options);
 
         let (x_offset, y_offset) = key.subpixel_point.to_f64();
         let transform = Some(
                         dwrote::DWRITE_MATRIX { m11: 1.0, m12: 0.0, m21: 0.0, m22: 1.0,
                                                 dx: x_offset as f32, dy: y_offset as f32 }
                         );
 
         dwrote::GlyphRunAnalysis::create(&glyph_run, 1.0, transform,
                                          dwrite_render_mode,
                                          dwrite_measure_mode,
                                          0.0, 0.0)
     }
 
+    pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+        let face = self.fonts.get(&font_key).unwrap();
+        let indices = face.get_glyph_indices(&[ch as u32]);
+        indices.first().map(|idx| *idx as u32)
+    }
+
     // TODO: Pipe GlyphOptions into glyph_dimensions too
     pub fn get_glyph_dimensions(&self,
+                                font: &FontInstanceKey,
                                 key: &GlyphKey)
                                 -> Option<GlyphDimensions> {
         // Probably have to default to something else here.
         let render_mode = FontRenderMode::Subpixel;
-        let analysis = self.create_glyph_analysis(key, render_mode, None);
+        let analysis = self.create_glyph_analysis(font, key);
 
         let texture_type = dwrite_texture_type(render_mode);
-        get_glyph_dimensions_with_analysis(analysis, texture_type)
+
+        let bounds = analysis.get_alpha_texture_bounds(texture_type);
+
+        let width = (bounds.right - bounds.left) as u32;
+        let height = (bounds.bottom - bounds.top) as u32;
+
+        // Alpha texture bounds can sometimes return an empty rect
+        // Such as for spaces
+        if width == 0 || height == 0 {
+            return None
+        }
+
+        let face = self.fonts.get(&font.font_key).unwrap();
+        face.get_design_glyph_metrics(&[key.index as u16], false)
+            .first()
+            .map(|metrics| {
+                let em_size = font.size.to_f32_px() / 16.;
+                let design_units_per_pixel = face.metrics().designUnitsPerEm as f32 / 16. as f32;
+                let scaled_design_units_to_pixels = em_size / design_units_per_pixel;
+                let advance = metrics.advanceWidth as f32 * scaled_design_units_to_pixels;
+
+                GlyphDimensions {
+                    left: bounds.left,
+                    top: -bounds.top,
+                    width,
+                    height,
+                    advance: advance,
+                }
+            })
     }
 
     // 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::Mono => {
                 let mut rgba_pixels: Vec<u8> = vec![0; pixels.len() * 4];
@@ -263,57 +277,54 @@ impl FontContext {
                     rgba_pixels[i*4+3] = 0xff;
                 }
                 rgba_pixels
             }
         }
     }
 
     pub fn rasterize_glyph(&mut self,
-                           key: &GlyphKey,
-                           render_mode: FontRenderMode,
-                           glyph_options: Option<GlyphOptions>)
+                           font: &FontInstanceKey,
+                           key: &GlyphKey)
                            -> Option<RasterizedGlyph> {
-        let analysis = self.create_glyph_analysis(key,
-                                                  render_mode,
-                                                  glyph_options);
-        let texture_type = dwrite_texture_type(render_mode);
+        let analysis = self.create_glyph_analysis(font, key);
+        let texture_type = dwrite_texture_type(font.render_mode);
 
         let bounds = analysis.get_alpha_texture_bounds(texture_type);
         let width = (bounds.right - bounds.left) as usize;
         let height = (bounds.bottom - bounds.top) as usize;
 
         // Alpha texture bounds can sometimes return an empty rect
         // Such as for spaces
         if width == 0 || height == 0 {
             return None;
         }
 
         let mut pixels = analysis.create_alpha_texture(texture_type, bounds);
 
-        if render_mode != FontRenderMode::Mono {
-            let lut_correction = match glyph_options {
+        if font.render_mode != FontRenderMode::Mono {
+            let lut_correction = match font.glyph_options {
                 Some(option) => {
                     if option.force_gdi_rendering {
                         &self.gdi_gamma_lut
                     } else {
                         &self.gamma_lut
                     }
                 },
                 None => &self.gamma_lut
             };
 
             lut_correction.preblend_rgb(&mut pixels, width, height,
-                                        ColorLut::new(key.color.r,
-                                                      key.color.g,
-                                                      key.color.b,
-                                                      key.color.a));
+                                        ColorLut::new(font.color.r,
+                                                      font.color.g,
+                                                      font.color.b,
+                                                      font.color.a));
         }
 
-        let rgba_pixels = self.convert_to_rgba(&mut pixels, render_mode);
+        let rgba_pixels = self.convert_to_rgba(&mut pixels, font.render_mode);
 
         Some(RasterizedGlyph {
             left: bounds.left as f32,
             top: -bounds.top as f32,
             width: width as u32,
             height: height as u32,
             bytes: rgba_pixels,
         })
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,27 +1,27 @@
 /* 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::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint};
 use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow};
 use api::{LayerToWorldTransform, TileOffset, WebGLContextId, YuvColorSpace, YuvFormat};
-use api::{device_length, LayerVector2D};
+use api::{device_length, FontInstanceKey, LayerVector2D, LineOrientation, LineStyle};
 use app_units::Au;
 use border::BorderCornerInstance;
 use euclid::{Size2D};
 use gpu_cache::{GpuCacheAddress, GpuBlockData, GpuCache, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo};
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 use render_task::{RenderTask, RenderTaskLocation};
 use resource_cache::{ImageProperties, ResourceCache};
 use std::{mem, usize};
-use util::{TransformedRect, recycle_vec};
+use util::{pack_as_float, TransformedRect, recycle_vec};
 
 
 pub const CLIP_DATA_GPU_BLOCKS: usize = 10;
 
 #[derive(Debug, Copy, Clone)]
 pub struct PrimitiveOpacity {
     pub is_opaque: bool,
 }
@@ -111,16 +111,17 @@ pub enum PrimitiveKind {
     Image,
     YuvImage,
     Border,
     AlignedGradient,
     AngleGradient,
     RadialGradient,
     BoxShadow,
     TextShadow,
+    Line,
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum PrimitiveCacheKey {
     BoxShadow(BoxShadowPrimitiveCacheKey),
     TextShadow(PrimitiveIndex),
 }
 
@@ -175,16 +176,34 @@ pub struct RectanglePrimitive {
 }
 
 impl ToGpuBlocks for RectanglePrimitive {
     fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
         request.push(self.color);
     }
 }
 
+#[derive(Debug, Clone)]
+#[repr(C)]
+pub struct LinePrimitive {
+    pub color: ColorF,
+    pub style: LineStyle,
+    pub orientation: LineOrientation,
+}
+
+impl ToGpuBlocks for LinePrimitive {
+    fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
+        request.push(self.color);
+        request.push([pack_as_float(self.style as u32),
+                      pack_as_float(self.orientation as u32),
+                      0.0,
+                      0.0]);
+    }
+}
+
 #[derive(Debug)]
 pub enum ImagePrimitiveKind {
     Image(ImageKey, ImageRendering, Option<TileOffset>, LayerSize),
     WebGL(WebGLContextId),
 }
 
 #[derive(Debug)]
 pub struct ImagePrimitiveCpu {
@@ -534,22 +553,23 @@ impl TextRunPrimitiveCpu {
         }
 
         let font_size_dp = self.logical_font_size.scale_by(device_pixel_ratio);
         let render_mode = match run_mode {
             TextRunMode::Normal => self.normal_render_mode,
             TextRunMode::Shadow => self.shadow_render_mode,
         };
 
-        resource_cache.request_glyphs(self.font_key,
-                                      font_size_dp,
-                                      self.color,
-                                      &self.glyph_instances,
-                                      render_mode,
-                                      self.glyph_options);
+        let font = FontInstanceKey::new(self.font_key,
+                                        font_size_dp,
+                                        self.color,
+                                        render_mode,
+                                        self.glyph_options);
+
+        resource_cache.request_glyphs(font, &self.glyph_instances);
     }
 
     fn write_gpu_blocks(&self,
                         request: &mut GpuDataRequest) {
         request.push(self.color);
         request.push([self.offset.x, self.offset.y, 0.0, 0.0]);
 
         // Two glyphs are packed per GPU block.
@@ -730,63 +750,67 @@ pub enum PrimitiveContainer {
     Image(ImagePrimitiveCpu),
     YuvImage(YuvImagePrimitiveCpu),
     Border(BorderPrimitiveCpu),
     AlignedGradient(GradientPrimitiveCpu),
     AngleGradient(GradientPrimitiveCpu),
     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>,
     pub cpu_borders: Vec<BorderPrimitiveCpu>,
     pub cpu_box_shadows: Vec<BoxShadowPrimitiveCpu>,
+    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),
+            cpu_lines: recycle_vec(self.cpu_lines),
         }
     }
 
     pub fn add_primitive(&mut self,
                          local_rect: &LayerRect,
                          local_clip_rect: &LayerRect,
                          clips: Vec<ClipSource>,
                          clip_info: Option<MaskCacheInfo>,
@@ -808,16 +832,33 @@ impl PrimitiveStore {
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_rectangles.push(rect);
 
                 metadata
             }
+            PrimitiveContainer::Line(line) => {
+                let metadata = PrimitiveMetadata {
+                    opacity: PrimitiveOpacity::translucent(),
+                    clips,
+                    clip_cache_info: clip_info,
+                    prim_kind: PrimitiveKind::Line,
+                    cpu_prim_index: SpecificPrimitiveIndex(self.cpu_lines.len()),
+                    gpu_location: GpuCacheHandle::new(),
+                    render_task: None,
+                    clip_task: None,
+                    local_rect: *local_rect,
+                    local_clip_rect: *local_clip_rect,
+                };
+
+                self.cpu_lines.push(line);
+                metadata
+            }
             PrimitiveContainer::TextRun(text_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
                     clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::TextRun,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_runs.len()),
                     gpu_location: GpuCacheHandle::new(),
@@ -1074,17 +1115,18 @@ impl PrimitiveStore {
                 if let ClipSource::Region(ClipRegion{ image_mask: Some(ref mask), .. }, ..) = *clip {
                     resource_cache.request_image(mask.image, ImageRendering::Auto, None);
                 }
             }
         }
 
         match metadata.prim_kind {
             PrimitiveKind::Rectangle |
-            PrimitiveKind::Border  => {}
+            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.
@@ -1158,16 +1200,20 @@ impl PrimitiveStore {
             request.push(metadata.local_rect);
             request.push(metadata.local_clip_rect);
 
             match metadata.prim_kind {
                 PrimitiveKind::Rectangle => {
                     let rect = &self.cpu_rectangles[cpu_prim_index.0];
                     rect.write_gpu_blocks(request);
                 }
+                PrimitiveKind::Line => {
+                    let line = &self.cpu_lines[metadata.cpu_prim_index.0];
+                    line.write_gpu_blocks(request);
+                }
                 PrimitiveKind::Border => {
                     let border = &self.cpu_borders[cpu_prim_index.0];
                     border.write_gpu_blocks(request);
                 }
                 PrimitiveKind::BoxShadow => {
                     let box_shadow = &self.cpu_box_shadows[cpu_prim_index.0];
                     box_shadow.write_gpu_blocks(request);
                 }
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use debug_render::DebugRenderer;
 use device::{Device, GpuMarker, GpuSample, NamedTag};
 use euclid::{Point2D, Size2D, Rect, vec2};
 use std::collections::vec_deque::VecDeque;
 use std::f32;
 use std::mem;
-use api::ColorF;
+use api::{ColorF, ColorU};
 use time::precise_time_ns;
 
 const GRAPH_WIDTH: f32 = 1024.0;
 const GRAPH_HEIGHT: f32 = 320.0;
 const GRAPH_PADDING: f32 = 8.0;
 const GRAPH_FRAME_HEIGHT: f32 = 16.0;
 const PROFILE_PADDING: f32 = 10.0;
 
@@ -324,23 +324,30 @@ pub struct IpcProfileCounters {
     pub build_time: TimeProfileCounter,
     pub consume_time: TimeProfileCounter,
     pub send_time: TimeProfileCounter,
     pub total_time: TimeProfileCounter,
     pub display_lists: ResourceProfileCounter,
 }
 
 impl IpcProfileCounters {
-    pub fn set(&mut self, build_start: u64, build_end: u64, 
-                              consume_start: u64, consume_end: u64,
-                              display_len: usize) {
-        self.build_time.inc(build_end - build_start);
-        self.consume_time.inc(consume_end - consume_start);
-        self.send_time.inc(consume_start - build_end);
-        self.total_time.inc(consume_end - build_start);
+    pub fn set(&mut self,
+               build_start: u64,
+               build_end: u64,
+               send_start: u64,
+               consume_start: u64,
+               consume_end: u64,
+               display_len: usize) {
+        let build_time = build_end - build_start;
+        let consume_time = consume_end - consume_start;
+        let send_time = consume_start - send_start;
+        self.build_time.inc(build_time);
+        self.consume_time.inc(consume_time);
+        self.send_time.inc(send_time);
+        self.total_time.inc(build_time + consume_time + send_time);
         self.display_lists.inc(display_len);
     }
 }
 
 impl BackendProfileCounters {
     pub fn new() -> BackendProfileCounters {
         BackendProfileCounters {
             total_time: TimeProfileCounter::new("Backend CPU Time", false),
@@ -463,74 +470,74 @@ impl ProfileGraph {
                   y: f32,
                   description: &'static str,
                   debug_renderer: &mut DebugRenderer) -> Rect<f32> {
         let size = Size2D::new(600.0, 120.0);
         let line_height = debug_renderer.line_height();
         let mut rect = Rect::new(Point2D::new(x, y), size);
         let stats = self.stats();
 
-        let text_color = ColorF::new(1.0, 1.0, 0.0, 1.0);
+        let text_color = ColorU::new(255, 255, 0, 255);
         let text_origin = rect.origin + vec2(rect.size.width, 20.0);
         debug_renderer.add_text(text_origin.x,
                                 text_origin.y,
                                 description,
-                                &ColorF::new(0.0, 1.0, 0.0, 1.0));
+                                ColorU::new(0, 255, 0, 255));
         debug_renderer.add_text(text_origin.x,
                                 text_origin.y + line_height,
                                 &format!("Min: {:.2} ms", stats.min_value),
-                                &text_color);
+                                text_color);
         debug_renderer.add_text(text_origin.x,
                                 text_origin.y + line_height * 2.0,
                                 &format!("Mean: {:.2} ms", stats.mean_value),
-                                &text_color);
+                                text_color);
         debug_renderer.add_text(text_origin.x,
                                 text_origin.y + line_height * 3.0,
                                 &format!("Max: {:.2} ms", stats.max_value),
-                                &text_color);
+                                text_color);
 
         rect.size.width += 140.0;
         debug_renderer.add_quad(rect.origin.x,
                                 rect.origin.y,
                                 rect.origin.x + rect.size.width + 10.0,
                                 rect.origin.y + rect.size.height,
-                                &ColorF::new(0.1, 0.1, 0.1, 0.8),
-                                &ColorF::new(0.2, 0.2, 0.2, 0.8));
+                                ColorF::new(0.1, 0.1, 0.1, 0.8).into(),
+                                ColorF::new(0.2, 0.2, 0.2, 0.8).into());
 
         let bx0 = x + 10.0;
         let by0 = y + 10.0;
         let bx1 = bx0 + size.width - 20.0;
         let by1 = by0 + size.height - 20.0;
 
         let w = (bx1 - bx0) / self.max_samples as f32;
         let h = by1 - by0;
 
-        let color_t0 = ColorF::new(0.0, 1.0, 0.0, 1.0);
-        let color_b0 = ColorF::new(0.0, 0.7, 0.0, 1.0);
+        let color_t0 = ColorU::new(0, 255, 0, 255);
+        let color_b0 = ColorU::new(0, 180, 0, 255);
 
-        let color_t1 = ColorF::new(0.0, 1.0, 0.0, 1.0);
-        let color_b1 = ColorF::new(0.0, 0.7, 0.0, 1.0);
+        let color_t1 = ColorU::new(0, 255, 0, 255);
+        let color_b1 = ColorU::new(0, 180, 0, 255);
 
-        let color_t2 = ColorF::new(1.0, 0.0, 0.0, 1.0);
-        let color_b2 = ColorF::new(0.7, 0.0, 0.0, 1.0);
+        let color_t2 = ColorU::new(255, 0, 0, 255);
+        let color_b2 = ColorU::new(180, 0, 0, 255);
 
         for (index, sample) in self.values.iter().enumerate() {
             let sample = *sample;
             let x1 = bx1 - index as f32 * w;
             let x0 = x1 - w;
 
             let y0 = by1 - (sample / stats.max_value) as f32 * h;
             let y1 = by1;
 
             let (color_top, color_bottom) = if sample < 1000.0 / 60.0 {
-                (&color_t0, &color_b0)
+                (color_t0, color_b0)
             } else if sample < 1000.0 / 30.0 {
-                (&color_t1, &color_b1)
+                (color_t1, color_b1)
             } else {
-                (&color_t2, &color_b2)
+                (color_t2, color_b2)
             };
 
             debug_renderer.add_quad(x0, y0, x1, y1, color_top, color_bottom);
         }
 
         rect
     }
 }
@@ -571,18 +578,18 @@ impl GpuFrameCollection {
                                       Size2D::new(GRAPH_WIDTH + 2.0 * GRAPH_PADDING,
                                                   GRAPH_HEIGHT + 2.0 * GRAPH_PADDING));
         let graph_rect = bounding_rect.inflate(-GRAPH_PADDING, -GRAPH_PADDING);
 
         debug_renderer.add_quad(bounding_rect.origin.x,
                                 bounding_rect.origin.y,
                                 bounding_rect.origin.x + bounding_rect.size.width,
                                 bounding_rect.origin.y + bounding_rect.size.height,
-                                &ColorF::new(0.1, 0.1, 0.1, 0.8),
-                                &ColorF::new(0.2, 0.2, 0.2, 0.8));
+                                ColorF::new(0.1, 0.1, 0.1, 0.8).into(),
+                                ColorF::new(0.2, 0.2, 0.2, 0.8).into());
 
         let w = graph_rect.size.width;
         let mut y0 = graph_rect.origin.y;
 
         let max_time = self.frames
                            .iter()
                            .max_by_key(|f| f.total_time)
                            .unwrap()
@@ -599,18 +606,18 @@ impl GpuFrameCollection {
 
                 let mut bottom_color = sample.tag.color;
                 bottom_color.a *= 0.5;
 
                 debug_renderer.add_quad(x0,
                                         y0,
                                         x1,
                                         y1,
-                                        &sample.tag.color,
-                                        &bottom_color);
+                                        sample.tag.color.into(),
+                                        bottom_color.into());
             }
 
             y0 = y1;
         }
 
         bounding_rect
     }
 }
@@ -652,25 +659,25 @@ impl Profiler {
             (self.x_left, self.y_left)
         } else {
             (self.x_right, self.y_right)
         };
         let mut color_index = 0;
         let line_height = debug_renderer.line_height();
 
         let colors = [
-            ColorF::new(1.0, 1.0, 1.0, 1.0),
-            ColorF::new(1.0, 1.0, 0.0, 1.0),
+            ColorU::new(255, 255, 255, 255),
+            ColorU::new(255, 255, 0, 255),
         ];
 
         for counter in counters {
             let rect = debug_renderer.add_text(current_x,
                                                current_y,
                                                counter.description(),
-                                               &colors[color_index]);
+                                               colors[color_index]);
             color_index = (color_index+1) % colors.len();
 
             label_rect = label_rect.union(&rect);
             current_y += line_height;
         }
 
         color_index = 0;
         current_x = label_rect.origin.x + label_rect.size.width + 60.0;
@@ -679,30 +686,30 @@ impl Profiler {
         } else {
             self.y_right
         };
 
         for counter in counters {
             let rect = debug_renderer.add_text(current_x,
                                                     current_y,
                                                     &counter.value(),
-                                                    &colors[color_index]);
+                                                    colors[color_index]);
             color_index = (color_index+1) % colors.len();
 
             value_rect = value_rect.union(&rect);
             current_y += line_height;
         }
 
         let total_rect = label_rect.union(&value_rect).inflate(10.0, 10.0);
         debug_renderer.add_quad(total_rect.origin.x,
                                 total_rect.origin.y,
                                 total_rect.origin.x + total_rect.size.width,
                                 total_rect.origin.y + total_rect.size.height,
-                                &ColorF::new(0.1, 0.1, 0.1, 0.8),
-                                &ColorF::new(0.2, 0.2, 0.2, 0.8));
+                                ColorF::new(0.1, 0.1, 0.1, 0.8).into(),
+                                ColorF::new(0.2, 0.2, 0.2, 0.8).into());
         let new_y = total_rect.origin.y + total_rect.size.height + 30.0;
         if left {
             self.y_left = new_y;
         } else {
             self.y_right = new_y;
         }
     }
 
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -63,16 +63,18 @@ pub struct RenderBackend {
     current_bound_webgl_context_id: Option<WebGLContextId>,
     recorder: Option<Box<ApiRecordingReceiver>>,
     main_thread_dispatcher: Arc<Mutex<Option<Box<RenderDispatcher>>>>,
 
     next_webgl_id: usize,
 
     vr_compositor_handler: Arc<Mutex<Option<Box<VRCompositorHandler>>>>,
 
+    enable_render_on_scroll: bool,
+
     // A helper switch to prevent any frames rendering triggered by scrolling
     // messages between `SetDisplayList` and `GenerateFrame`.
     // If we allow them, then a reftest that scrolls a few layers before generating
     // the first frame would produce inconsistent rendering results, because
     // scroll events are not necessarily received in deterministic order.
     render_on_scroll: bool,
 }
 
@@ -86,17 +88,18 @@ impl RenderBackend {
                workers: Arc<ThreadPool>,
                notifier: Arc<Mutex<Option<Box<RenderNotifier>>>>,
                webrender_context_handle: Option<GLContextHandleWrapper>,
                config: FrameBuilderConfig,
                recorder: Option<Box<ApiRecordingReceiver>>,
                main_thread_dispatcher: Arc<Mutex<Option<Box<RenderDispatcher>>>>,
                blob_image_renderer: Option<Box<BlobImageRenderer>>,
                vr_compositor_handler: Arc<Mutex<Option<Box<VRCompositorHandler>>>>,
-               initial_window_size: DeviceUintSize) -> RenderBackend {
+               initial_window_size: DeviceUintSize,
+               enable_render_on_scroll: bool) -> RenderBackend {
 
         let resource_cache = ResourceCache::new(texture_cache, workers, blob_image_renderer);
 
         register_thread_with_profiler("Backend".to_string());
 
         RenderBackend {
             api_rx,
             payload_rx,
@@ -116,16 +119,17 @@ impl RenderBackend {
             webgl_contexts: HashMap::new(),
             current_bound_webgl_context_id: None,
             recorder,
             main_thread_dispatcher,
             next_webgl_id: 0,
             vr_compositor_handler,
             window_size: initial_window_size,
             inner_rect: DeviceUintRect::new(DeviceUintPoint::zero(), initial_window_size),
+            enable_render_on_scroll,
             render_on_scroll: false,
         }
     }
 
     fn scroll_frame(&mut self, frame_maybe: Option<RendererFrame>,
                     profile_counters: &mut BackendProfileCounters) {
         match frame_maybe {
             Some(frame) => {
@@ -155,24 +159,32 @@ impl RenderBackend {
                         }
                         ApiMsg::AddNativeFont(id, native_font_handle) => {
                             self.resource_cache
                                 .add_font_template(id, FontTemplate::Native(native_font_handle));
                         }
                         ApiMsg::DeleteFont(id) => {
                             self.resource_cache.delete_font_template(id);
                         }
-                        ApiMsg::GetGlyphDimensions(glyph_keys, tx) => {
+                        ApiMsg::GetGlyphDimensions(font, glyph_keys, tx) => {
                             let mut glyph_dimensions = Vec::with_capacity(glyph_keys.len());
                             for glyph_key in &glyph_keys {
-                                let glyph_dim = self.resource_cache.get_glyph_dimensions(glyph_key);
+                                let glyph_dim = self.resource_cache.get_glyph_dimensions(&font, glyph_key);
                                 glyph_dimensions.push(glyph_dim);
                             };
                             tx.send(glyph_dimensions).unwrap();
                         }
+                        ApiMsg::GetGlyphIndices(font_key, text, tx) => {
+                            let mut glyph_indices = Vec::new();
+                            for ch in text.chars() {
+                                let index = self.resource_cache.get_glyph_index(font_key, ch);
+                                glyph_indices.push(index);
+                            };
+                            tx.send(glyph_indices).unwrap();
+                        }
                         ApiMsg::AddImage(id, descriptor, data, tiling) => {
                             if let ImageData::Raw(ref bytes) = data {
                                 profile_counters.resources.image_templates.inc(bytes.len());
                             }
                             self.resource_cache.add_image_template(id, descriptor, data, tiling);
                         }
                         ApiMsg::UpdateImage(id, descriptor, bytes, dirty_rect) => {
                             self.resource_cache.update_image_template(id, descriptor, bytes, dirty_rect);
@@ -232,17 +244,17 @@ impl RenderBackend {
                                 BuiltDisplayList::from_data(data.display_list_data,
                                                             display_list_descriptor);
 
                             if !preserve_frame_state {
                                 self.discard_frame_state_for_pipeline(pipeline_id);
                             }
 
                             let display_list_len = built_display_list.data().len();
-                            let (builder_start_time, builder_finish_time) = built_display_list.times();
+                            let (builder_start_time, builder_finish_time, send_start_time) = built_display_list.times();
 
                             let display_list_received_time = precise_time_ns();
 
                             profile_counters.total_time.profile(|| {
                                 self.scene.set_display_list(pipeline_id,
                                                             epoch,
                                                             built_display_list,
                                                             background_color,
@@ -253,18 +265,21 @@ impl RenderBackend {
 
                             self.render_on_scroll = false; //wait for `GenerateFrame`
 
                             // Note: this isn't quite right as auxiliary values will be
                             // pulled out somewhere in the prim_store, but aux values are
                             // really simple and cheap to access, so it's not a big deal.
                             let display_list_consumed_time = precise_time_ns();
 
-                            profile_counters.ipc.set(builder_start_time, builder_finish_time,
-                                                     display_list_received_time, display_list_consumed_time,
+                            profile_counters.ipc.set(builder_start_time,
+                                                     builder_finish_time,
+                                                     send_start_time,
+                                                     display_list_received_time,
+                                                     display_list_consumed_time,
                                                      display_list_len);
                         }
                         ApiMsg::SetRootPipeline(pipeline_id) => {
                             profile_scope!("SetRootPipeline");
                             self.scene.set_root_pipeline_id(pipeline_id);
 
                             if self.scene.display_lists.get(&pipeline_id).is_none() {
                                 continue;
@@ -420,17 +435,17 @@ impl RenderBackend {
                             //           rebuild of the frame!
                             if let Some(property_bindings) = property_bindings {
                                 self.scene.properties.set_properties(property_bindings);
                                 profile_counters.total_time.profile(|| {
                                     self.build_scene();
                                 });
                             }
 
-                            self.render_on_scroll = true;
+                            self.render_on_scroll = self.enable_render_on_scroll;
 
                             let frame = {
                                 let counters = &mut profile_counters.resources.texture_cache;
                                 let gpu_cache_counters = &mut profile_counters.resources.gpu_cache;
                                 profile_counters.total_time.profile(|| {
                                     self.render(counters, gpu_cache_counters)
                                 })
                             };
@@ -441,16 +456,19 @@ impl RenderBackend {
                         }
                         ApiMsg::ExternalEvent(evt) => {
                             let notifier = self.notifier.lock();
                             notifier.unwrap()
                                     .as_mut()
                                     .unwrap()
                                     .external_event(evt);
                         }
+                        ApiMsg::ClearNamespace(namespace) => {
+                            self.resource_cache.clear_namespace(namespace);
+                        }
                         ApiMsg::ShutDown => {
                             let notifier = self.notifier.lock();
                             notifier.unwrap()
                                     .as_mut()
                                     .unwrap()
                                     .shut_down();
                             break;
                         }
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,21 +1,21 @@
 /* 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 gpu_cache::GpuCacheHandle;
-use internal_types::{HardwareCompositeOp, LowLevelFilterOp};
+use internal_types::HardwareCompositeOp;
 use mask_cache::MaskCacheInfo;
 use prim_store::{PrimitiveCacheKey, PrimitiveIndex};
 use std::{cmp, f32, i32, mem, usize};
 use tiling::{ClipScrollGroupIndex, PackedLayerIndex, RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind, StackingContextIndex};
 use api::{ClipId, DeviceIntLength, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
-use api::{MixBlendMode};
+use api::{FilterOp, MixBlendMode};
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
 
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
 pub struct RenderTaskIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum RenderTaskKey {
@@ -48,17 +48,17 @@ pub enum RenderTaskId {
 pub enum RenderTaskLocation {
     Fixed,
     Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
 }
 
 #[derive(Debug, Clone)]
 pub enum AlphaRenderItem {
     Primitive(Option<ClipScrollGroupIndex>, PrimitiveIndex, i32),
-    Blend(StackingContextIndex, RenderTaskId, LowLevelFilterOp, i32),
+    Blend(StackingContextIndex, RenderTaskId, FilterOp, i32),
     Composite(StackingContextIndex, RenderTaskId, RenderTaskId, MixBlendMode, i32),
     SplitComposite(StackingContextIndex, RenderTaskId, GpuCacheHandle, i32),
     HardwareComposite(StackingContextIndex, RenderTaskId, HardwareCompositeOp, i32),
 }
 
 #[derive(Debug, Clone)]
 pub struct AlphaRenderTask {
     screen_origin: DeviceIntPoint,
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -58,19 +58,21 @@ use api::{YuvColorSpace, YuvFormat};
 use api::{YUV_COLOR_SPACES, YUV_FORMATS};
 
 pub const GPU_DATA_TEXTURE_POOL: usize = 5;
 pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024;
 
 const GPU_TAG_CACHE_BOX_SHADOW: GpuProfileTag = GpuProfileTag { label: "C_BoxShadow", color: debug_colors::BLACK };
 const GPU_TAG_CACHE_CLIP: GpuProfileTag = GpuProfileTag { label: "C_Clip", color: debug_colors::PURPLE };
 const GPU_TAG_CACHE_TEXT_RUN: GpuProfileTag = GpuProfileTag { label: "C_TextRun", color: debug_colors::MISTYROSE };
+const GPU_TAG_CACHE_LINE: GpuProfileTag = GpuProfileTag { label: "C_Line", color: debug_colors::BROWN };
 const GPU_TAG_SETUP_TARGET: GpuProfileTag = GpuProfileTag { label: "target", color: debug_colors::SLATEGREY };
 const GPU_TAG_SETUP_DATA: GpuProfileTag = GpuProfileTag { label: "data init", color: debug_colors::LIGHTGREY };
 const GPU_TAG_PRIM_RECT: GpuProfileTag = GpuProfileTag { label: "Rect", color: debug_colors::RED };
+const GPU_TAG_PRIM_LINE: GpuProfileTag = GpuProfileTag { label: "Line", color: debug_colors::DARKRED };
 const GPU_TAG_PRIM_IMAGE: GpuProfileTag = GpuProfileTag { label: "Image", color: debug_colors::GREEN };
 const GPU_TAG_PRIM_YUV_IMAGE: GpuProfileTag = GpuProfileTag { label: "YuvImage", color: debug_colors::DARKGREEN };
 const GPU_TAG_PRIM_BLEND: GpuProfileTag = GpuProfileTag { label: "Blend", color: debug_colors::LIGHTBLUE };
 const GPU_TAG_PRIM_HW_COMPOSITE: GpuProfileTag = GpuProfileTag { label: "HwComposite", color: debug_colors::DODGERBLUE };
 const GPU_TAG_PRIM_SPLIT_COMPOSITE: GpuProfileTag = GpuProfileTag { label: "SplitComposite", color: debug_colors::DARKBLUE };
 const GPU_TAG_PRIM_COMPOSITE: GpuProfileTag = GpuProfileTag { label: "Composite", color: debug_colors::MAGENTA };
 const GPU_TAG_PRIM_TEXT_RUN: GpuProfileTag = GpuProfileTag { label: "TextRun", color: debug_colors::BLUE };
 const GPU_TAG_PRIM_GRADIENT: GpuProfileTag = GpuProfileTag { label: "Gradient", color: debug_colors::YELLOW };
@@ -158,26 +160,29 @@ impl GpuProfile {
             paint_time_ns,
         }
     }
 }
 
 #[derive(Debug)]
 pub struct CpuProfile {
     pub frame_id: FrameId,
+    pub backend_time_ns: u64,
     pub composite_time_ns: u64,
     pub draw_calls: usize,
 }
 
 impl CpuProfile {
     fn new(frame_id: FrameId,
+           backend_time_ns: u64,
            composite_time_ns: u64,
            draw_calls: usize) -> CpuProfile {
         CpuProfile {
             frame_id,
+            backend_time_ns,
             composite_time_ns,
             draw_calls,
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum BlendMode {
@@ -618,17 +623,19 @@ pub struct Renderer {
     pending_shader_updates: Vec<PathBuf>,
     current_frame: Option<RendererFrame>,
 
     // These are "cache shaders". These shaders are used to
     // draw intermediate results to cache targets. The results
     // of these shaders are then used by the primitive shaders.
     cs_box_shadow: LazilyCompiledShader,
     cs_text_run: LazilyCompiledShader,
+    cs_line: LazilyCompiledShader,
     cs_blur: LazilyCompiledShader,
+
     /// These are "cache clip shaders". These shaders are used to
     /// draw clip instances into the cached clip mask. The results
     /// of these shaders are also used by the primitive shaders.
     cs_clip_rectangle: LazilyCompiledShader,
     cs_clip_image: LazilyCompiledShader,
     cs_clip_border: LazilyCompiledShader,
 
     // The are "primitive shaders". These shaders draw and blend
@@ -646,16 +653,17 @@ pub struct Renderer {
     ps_yuv_image: Vec<Option<PrimitiveShader>>,
     ps_border_corner: PrimitiveShader,
     ps_border_edge: PrimitiveShader,
     ps_gradient: PrimitiveShader,
     ps_angle_gradient: PrimitiveShader,
     ps_radial_gradient: PrimitiveShader,
     ps_box_shadow: PrimitiveShader,
     ps_cache_image: PrimitiveShader,
+    ps_line: PrimitiveShader,
 
     ps_blend: LazilyCompiledShader,
     ps_hw_composite: LazilyCompiledShader,
     ps_split_composite: LazilyCompiledShader,
     ps_composite: LazilyCompiledShader,
 
     notifier: Arc<Mutex<Option<Box<RenderNotifier>>>>,
 
@@ -791,16 +799,24 @@ impl Renderer {
         let cs_text_run = try!{
             LazilyCompiledShader::new(ShaderKind::Cache(VertexFormat::Triangles),
                                       "cs_text_run",
                                       &[],
                                       &mut device,
                                       options.precache_shaders)
         };
 
+        let cs_line = try!{
+            LazilyCompiledShader::new(ShaderKind::Cache(VertexFormat::Triangles),
+                                      "ps_line",
+                                      &["CACHE"],
+                                      &mut device,
+                                      options.precache_shaders)
+        };
+
         let cs_blur = try!{
             LazilyCompiledShader::new(ShaderKind::Cache(VertexFormat::Blur),
                                      "cs_blur",
                                       &[],
                                       &mut device,
                                       options.precache_shaders)
         };
 
@@ -837,16 +853,23 @@ impl Renderer {
 
         let ps_rectangle_clip = try!{
             PrimitiveShader::new("ps_rectangle",
                                  &mut device,
                                  &[ CLIP_FEATURE ],
                                  options.precache_shaders)
         };
 
+        let ps_line = try!{
+            PrimitiveShader::new("ps_line",
+                                 &mut device,
+                                 &[],
+                                 options.precache_shaders)
+        };
+
         let ps_text_run = try!{
             PrimitiveShader::new("ps_text_run",
                                  &mut device,
                                  &[],
                                  options.precache_shaders)
         };
 
         let ps_text_run_subpixel = try!{
@@ -1132,16 +1155,17 @@ impl Renderer {
         let payload_tx_for_backend = payload_tx.clone();
         let recorder = options.recorder;
         let worker_config = ThreadPoolConfig::new()
             .thread_name(|idx|{ format!("WebRender:Worker#{}", idx) })
             .start_handler(|idx| { register_thread_with_profiler(format!("WebRender:Worker#{}", idx)); });
         let workers = options.workers.take().unwrap_or_else(||{
             Arc::new(ThreadPool::new(worker_config).unwrap())
         });
+        let enable_render_on_scroll = options.enable_render_on_scroll;
 
         let blob_image_renderer = options.blob_image_renderer.take();
         try!{ thread::Builder::new().name("RenderBackend".to_string()).spawn(move || {
             let mut backend = RenderBackend::new(api_rx,
                                                  payload_rx,
                                                  payload_tx_for_backend,
                                                  result_tx,
                                                  device_pixel_ratio,
@@ -1149,33 +1173,35 @@ impl Renderer {
                                                  workers,
                                                  backend_notifier,
                                                  context_handle,
                                                  config,
                                                  recorder,
                                                  backend_main_thread_dispatcher,
                                                  blob_image_renderer,
                                                  backend_vr_compositor,
-                                                 initial_window_size);
+                                                 initial_window_size,
+                                                 enable_render_on_scroll);
             backend.run(backend_profile_counters);
         })};
 
         let gpu_cache_texture = CacheTexture::new(&mut device);
 
         let gpu_profile = GpuProfiler::new(device.rc_gl());
 
         let renderer = Renderer {
             result_rx,
             device,
             current_frame: None,
             pending_texture_updates: Vec::new(),
             pending_gpu_cache_updates: Vec::new(),
             pending_shader_updates: Vec::new(),
             cs_box_shadow,
             cs_text_run,
+            cs_line,
             cs_blur,
             cs_clip_rectangle,
             cs_clip_border,
             cs_clip_image,
             ps_rectangle,
             ps_rectangle_clip,
             ps_text_run,
             ps_text_run_subpixel,
@@ -1187,16 +1213,17 @@ impl Renderer {
             ps_gradient,
             ps_angle_gradient,
             ps_radial_gradient,
             ps_cache_image,
             ps_blend,
             ps_hw_composite,
             ps_split_composite,
             ps_composite,
+            ps_line,
             notifier,
             debug: debug_renderer,
             render_target_debug,
             enable_batcher: options.enable_batcher,
             backend_profile_counters: BackendProfileCounters::new(),
             profile_counters: RendererProfileCounters::new(),
             profiler: Profiler::new(),
             enable_profiler: options.enable_profiler,
@@ -1404,16 +1431,17 @@ impl Renderer {
                 let ns = current_time - self.last_time;
                 self.profile_counters.frame_time.set(ns);
 
                 if self.max_recorded_profiles > 0 {
                     while self.cpu_profiles.len() >= self.max_recorded_profiles {
                         self.cpu_profiles.pop_front();
                     }
                     let cpu_profile = CpuProfile::new(cpu_frame_id,
+                                                      self.backend_profile_counters.total_time.get(),
                                                       profile_timers.cpu_time.get(),
                                                       self.profile_counters.draw_calls.get());
                     self.cpu_profiles.push_back(cpu_profile);
                 }
 
                 if self.enable_profiler {
                     self.profiler.draw_profile(&mut self.device,
                                                &frame.profile_counters,
@@ -1659,16 +1687,20 @@ impl Renderer {
             AlphaBatchKind::Rectangle => {
                 let shader = if needs_clipping {
                     self.ps_rectangle_clip.get(&mut self.device, transform_kind)
                 } else {
                     self.ps_rectangle.get(&mut self.device, transform_kind)
                 };
                 (GPU_TAG_PRIM_RECT, shader)
             }
+            AlphaBatchKind::Line => {
+                let shader = self.ps_line.get(&mut self.device, transform_kind);
+                (GPU_TAG_PRIM_LINE, shader)
+            }
             AlphaBatchKind::TextRun => {
                 let shader = match batch.key.blend_mode {
                     BlendMode::Subpixel(..) => self.ps_text_run_subpixel.get(&mut self.device, transform_kind),
                     BlendMode::Alpha | BlendMode::PremultipliedAlpha | BlendMode::None => self.ps_text_run.get(&mut self.device, transform_kind),
                 };
                 (GPU_TAG_PRIM_TEXT_RUN, shader)
             }
             AlphaBatchKind::Image(image_buffer_kind) => {
@@ -1873,16 +1905,32 @@ impl Renderer {
             let shader = self.cs_text_run.get(&mut self.device).unwrap();
 
             self.draw_instanced_batch(&target.text_run_cache_prims,
                                       vao,
                                       shader,
                                       &target.text_run_textures,
                                       &projection);
         }
+        if !target.line_cache_prims.is_empty() {
+            // TODO(gw): Technically, we don't need blend for solid
+            //           lines. We could check that here?
+            self.device.set_blend(true);
+            self.device.set_blend_mode_alpha();
+
+            let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_LINE);
+            let vao = self.prim_vao_id;
+            let shader = self.cs_line.get(&mut self.device).unwrap();
+
+            self.draw_instanced_batch(&target.line_cache_prims,
+                                      vao,
+                                      shader,
+                                      &BatchTextures::no_texture(),
+                                      &projection);
+        }
 
         if !target.alpha_batcher.is_empty() {
             let _gm2 = GpuMarker::new(self.device.rc_gl(), "alpha batches");
             self.device.set_blend(false);
             let mut prev_blend_mode = BlendMode::None;
 
             //Note: depth equality is needed for split planes
             self.device.set_depth_func(DepthFunction::LessEqual);
@@ -2387,16 +2435,17 @@ pub struct RendererOptions {
     pub enable_clear_scissor: bool,
     pub enable_batcher: bool,
     pub render_target_debug: bool,
     pub max_texture_size: Option<u32>,
     pub cache_expiry_frames: u32,
     pub workers: Option<Arc<ThreadPool>>,
     pub blob_image_renderer: Option<Box<BlobImageRenderer>>,
     pub recorder: Option<Box<ApiRecordingReceiver>>,
+    pub enable_render_on_scroll: bool,
 }
 
 impl Default for RendererOptions {
     fn default() -> RendererOptions {
         RendererOptions {
             device_pixel_ratio: 1.0,
             resource_override_path: None,
             enable_aa: true,
@@ -2413,11 +2462,12 @@ impl Default for RendererOptions {
             enable_clear_scissor: true,
             enable_batcher: true,
             render_target_debug: false,
             max_texture_size: None,
             cache_expiry_frames: 600, // roughly, 10 seconds
             workers: None,
             blob_image_renderer: None,
             recorder: None,
+            enable_render_on_scroll: true,
         }
     }
 }
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -1,39 +1,37 @@
 /* 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 app_units::Au;
 use device::TextureFilter;
 use fnv::FnvHasher;
 use frame::FrameId;
 use gpu_cache::{GpuCache, GpuCacheHandle};
 use internal_types::{SourceTexture, TextureUpdateList};
 use profiler::TextureCacheProfileCounters;
 use std::collections::{HashMap, HashSet};
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::fmt::Debug;
 use std::hash::BuildHasherDefault;
 use std::hash::Hash;
 use std::mem;
 use std::sync::Arc;
 use texture_cache::{TextureCache, TextureCacheItemId};
-use api::{Epoch, FontKey, FontTemplate, GlyphKey, ImageKey, ImageRendering};
-use api::{FontRenderMode, ImageData, GlyphDimensions, WebGLContextId};
-use api::{DevicePoint, DeviceIntSize, DeviceUintRect, ImageDescriptor, ColorF};
-use api::{GlyphOptions, GlyphInstance, SubpixelPoint, TileOffset, TileSize};
+use api::{Epoch, FontInstanceKey, FontKey, FontTemplate, GlyphKey, ImageKey, ImageRendering};
+use api::{ImageData, GlyphDimensions, WebGLContextId, IdNamespace};
+use api::{DevicePoint, DeviceIntSize, DeviceUintRect, ImageDescriptor};
+use api::{GlyphInstance, SubpixelPoint, TileOffset, TileSize};
 use api::{BlobImageRenderer, BlobImageDescriptor, BlobImageError, BlobImageRequest, BlobImageData};
 use api::BlobImageResources;
 use api::{ExternalImageData, ExternalImageType, LayoutPoint};
 use rayon::ThreadPool;
 use glyph_rasterizer::{GlyphRasterizer, GlyphCache, GlyphRequest};
 
 const DEFAULT_TILE_SIZE: TileSize = 512;
-const BLACK: ColorF = ColorF { r: 0.0, b: 0.0, g: 0.0, a: 1.0 };
 
 // These coordinates are always in texels.
 // They are converted to normalized ST
 // values in the vertex shader. The reason
 // for this is that the texture may change
 // dimensions (e.g. the pages in a texture
 // atlas can grow). When this happens, by
 // storing the coordinates as texel values
@@ -133,33 +131,52 @@ impl<K,V> ResourceClassCache<K,V> where 
         self.resources.entry(key)
     }
 
     pub fn mark_as_needed(&mut self, key: &K, frame: FrameId) {
         self.last_access_times.insert((*key).clone(), frame);
     }
 
     fn expire_old_resources(&mut self, texture_cache: &mut TextureCache, frame_id: FrameId) {
-        let mut resources_to_destroy = vec![];
-        for (key, this_frame_id) in &self.last_access_times {
-            if *this_frame_id < frame_id {
-                resources_to_destroy.push((*key).clone())
-            }
-        }
+        //TODO: use retain when available
+        let resources_to_destroy = self.last_access_times.iter()
+            .filter_map(|(key, this_frame_id)| {
+                if *this_frame_id < frame_id {
+                    Some(key.clone())
+                } else {
+                    None
+                }
+            }).collect::<Vec<_>>();
+
         for key in resources_to_destroy {
             let resource =
                 self.resources
                     .remove(&key)
                     .expect("Resource was in `last_access_times` but not in `resources`!");
             self.last_access_times.remove(&key);
             if let Some(texture_cache_item_id) = resource.texture_cache_item_id() {
                 texture_cache.free(texture_cache_item_id)
             }
         }
     }
+
+    fn clear_keys<F>(&mut self, texture_cache: &mut TextureCache, key_fun: F)
+    where for<'r> F: Fn(&'r &K) -> bool
+    {
+        let resources_to_destroy = self.resources.keys()
+            .filter(&key_fun)
+            .cloned()
+            .collect::<Vec<_>>();
+        for key in resources_to_destroy {
+            let resource = self.resources.remove(&key).unwrap();
+            if let Some(texture_cache_item_id) = resource.texture_cache_item_id() {
+                texture_cache.free(texture_cache_item_id)
+            }
+        }
+    }
 }
 
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 struct ImageRequest {
     key: ImageKey,
     rendering: ImageRendering,
     tile: Option<TileOffset>,
@@ -202,17 +219,17 @@ pub struct ResourceCache {
 
     resources: Resources,
     state: State,
     current_frame_id: FrameId,
 
     texture_cache: TextureCache,
 
     // TODO(gw): We should expire (parts of) this cache semi-regularly!
-    cached_glyph_dimensions: HashMap<GlyphKey, Option<GlyphDimensions>, BuildHasherDefault<FnvHasher>>,
+    cached_glyph_dimensions: HashMap<GlyphRequest, Option<GlyphDimensions>, BuildHasherDefault<FnvHasher>>,
     pending_image_requests: Vec<ImageRequest>,
     glyph_rasterizer: GlyphRasterizer,
 
     blob_image_renderer: Option<Box<BlobImageRenderer>>,
     blob_image_requests: HashSet<ImageRequest>,
 
     requested_glyphs: HashSet<TextureCacheItemId, BuildHasherDefault<FnvHasher>>,
     requested_images: HashSet<TextureCacheItemId, BuildHasherDefault<FnvHasher>>,
@@ -441,99 +458,82 @@ impl ResourceCache {
                 }
             }
         } else {
             self.pending_image_requests.push(request);
         }
     }
 
     pub fn request_glyphs(&mut self,
-                          key: FontKey,
-                          size: Au,
-                          mut color: ColorF,
-                          glyph_instances: &[GlyphInstance],
-                          render_mode: FontRenderMode,
-                          glyph_options: Option<GlyphOptions>) {
+                          font: FontInstanceKey,
+                          glyph_instances: &[GlyphInstance]) {
         debug_assert_eq!(self.state, State::AddResources);
 
-        // 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 = BLACK;
-        }
-
         self.glyph_rasterizer.request_glyphs(
             &mut self.cached_glyphs,
             self.current_frame_id,
-            key,
-            size,
-            color,
+            font,
             glyph_instances,
-            render_mode,
-            glyph_options,
             &mut self.requested_glyphs,
         );
     }
 
     pub fn pending_updates(&mut self) -> TextureUpdateList {
         self.texture_cache.pending_updates()
     }
 
     pub fn get_glyphs<F>(&self,
-                         font_key: FontKey,
-                         size: Au,
-                         mut color: ColorF,
+                         font: FontInstanceKey,
                          glyph_instances: &[GlyphInstance],
-                         render_mode: FontRenderMode,
-                         glyph_options: Option<GlyphOptions>,
                          mut f: F) -> SourceTexture where F: FnMut(usize, &GpuCacheHandle) {
-        // Color when retrieving glyphs must match that of the request,
-        // otherwise the hash keys won't match.
-        if render_mode != FontRenderMode::Subpixel {
-            color = BLACK;
-        }
-
         debug_assert_eq!(self.state, State::QueryResources);
         let mut glyph_request = GlyphRequest::new(
-            font_key,
-            size,
-            color,
+            font,
             0,
             LayoutPoint::zero(),
-            render_mode,
-            glyph_options
         );
         let mut texture_id = None;
         for (loop_index, glyph_instance) in glyph_instances.iter().enumerate() {
             glyph_request.key.index = glyph_instance.index;
-            glyph_request.key.subpixel_point = SubpixelPoint::new(glyph_instance.point, render_mode);
+            glyph_request.key.subpixel_point = SubpixelPoint::new(glyph_instance.point,
+                                                                  glyph_request.font.render_mode);
 
             let image_id = self.cached_glyphs.get(&glyph_request, self.current_frame_id);
             let cache_item = image_id.map(|image_id| self.texture_cache.get(image_id));
             if let Some(cache_item) = cache_item {
                 f(loop_index, &cache_item.uv_rect_handle);
                 debug_assert!(texture_id == None ||
                               texture_id == Some(cache_item.texture_id));
                 texture_id = Some(cache_item.texture_id);
             }
         }
 
         texture_id.map_or(SourceTexture::Invalid, SourceTexture::TextureCache)
     }
 
-    pub fn get_glyph_dimensions(&mut self, glyph_key: &GlyphKey) -> Option<GlyphDimensions> {
-        match self.cached_glyph_dimensions.entry(glyph_key.clone()) {
+    pub fn get_glyph_dimensions(&mut self,
+                                font: &FontInstanceKey,
+                                glyph_key: &GlyphKey) -> Option<GlyphDimensions> {
+        let key = GlyphRequest {
+            font: font.clone(),
+            key: glyph_key.clone(),
+        };
+
+        match self.cached_glyph_dimensions.entry(key.clone()) {
             Occupied(entry) => *entry.get(),
             Vacant(entry) => {
-                *entry.insert(self.glyph_rasterizer.get_glyph_dimensions(glyph_key))
+                *entry.insert(self.glyph_rasterizer.get_glyph_dimensions(&key.font, &key.key))
             }
         }
     }
 
+    pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+        self.glyph_rasterizer.get_glyph_index(font_key, ch)
+    }
+
     #[inline]
     pub fn get_cached_image(&self,
                             image_key: ImageKey,
                             image_rendering: ImageRendering,
                             tile: Option<TileOffset>) -> CacheItem {
         debug_assert_eq!(self.state, State::QueryResources);
         let key = ImageRequest {
             key: image_key,
@@ -778,16 +778,38 @@ impl ResourceCache {
             }
         }
     }
 
     pub fn end_frame(&mut self) {
         debug_assert_eq!(self.state, State::QueryResources);
         self.state = State::Idle;
     }
+
+    pub fn clear_namespace(&mut self, namespace: IdNamespace) {
+        //TODO: use `retain` when we are on Rust-1.18
+        let image_keys: Vec<_> = self.resources.image_templates.images.keys()
+                                                                      .filter(|&key| key.0 == namespace)
+                                                                      .cloned()
+                                                                      .collect();
+        for key in &image_keys {
+            self.resources.image_templates.images.remove(key);
+        }
+
+        let font_keys: Vec<_> = self.resources.font_templates.keys()
+                                                             .filter(|&key| key.0 == namespace)
+                                                             .cloned()
+                                                             .collect();
+        for key in &font_keys {
+            self.resources.font_templates.remove(key);
+        }
+
+        self.cached_images.clear_keys(&mut self.texture_cache, |request| request.key.0 == namespace);
+        self.cached_glyphs.clear_keys(&mut self.texture_cache, |request| request.font.font_key.0 == namespace);
+    }
 }
 
 pub trait Resource {
     fn texture_cache_item_id(&self) -> Option<TextureCacheItemId>;
 }
 
 impl Resource for TextureCacheItemId {
     fn texture_cache_item_id(&self) -> Option<TextureCacheItemId> {
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.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 app_units::Au;
 use border::{BorderCornerInstance, BorderCornerSide};
 use device::TextureId;
 use fnv::FnvHasher;
 use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheUpdateList};
-use internal_types::{ANGLE_FLOAT_TO_FIXED, BatchTextures, CacheTextureId, LowLevelFilterOp};
+use internal_types::{BatchTextures, CacheTextureId};
 use internal_types::SourceTexture;
 use mask_cache::MaskCacheInfo;
 use prim_store::{CLIP_DATA_GPU_BLOCKS, DeferredResolve, ImagePrimitiveKind, PrimitiveCacheKey};
 use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
 use profiler::FrameProfileCounters;
 use render_task::{AlphaRenderItem, MaskGeometryKind, MaskSegment, RenderTask, RenderTaskData};
 use render_task::{RenderTaskId, RenderTaskIndex, RenderTaskKey, RenderTaskKind};
 use render_task::RenderTaskLocation;
@@ -20,19 +19,19 @@ use renderer::BlendMode;
 use renderer::ImageBufferKind;
 use resource_cache::ResourceCache;
 use std::{f32, i32, mem, usize};
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
 use texture_cache::TexturePage;
 use util::{TransformedRect, TransformedRectKind};
 use api::{BuiltDisplayList, ClipAndScrollInfo, ClipId, ColorF, DeviceIntPoint, ImageKey};
-use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize};
-use api::{ExternalImageType, FontRenderMode, ImageRendering, LayerRect};
-use api::{LayerToWorldTransform, MixBlendMode, PipelineId, TransformStyle};
+use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize, FontInstanceKey};
+use api::{ExternalImageType, FilterOp, FontRenderMode, ImageRendering, LayerRect};
+use api::{LayerToWorldTransform, MixBlendMode, PipelineId, PropertyBinding, TransformStyle};
 use api::{TileOffset, WorldToLayerTransform, YuvColorSpace, YuvFormat, LayerVector2D};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_INDEX: RenderTaskIndex = RenderTaskIndex(i32::MAX as usize);
 
 
 pub type DisplayListMap = HashMap<PipelineId,
@@ -279,25 +278,27 @@ impl AlphaRenderItem {
                 let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
                 let key = AlphaBatchKey::new(AlphaBatchKind::Blend,
                                              AlphaBatchKeyFlags::empty(),
                                              BlendMode::PremultipliedAlpha,
                                              BatchTextures::no_texture());
                 let src_task_index = render_tasks.get_static_task_index(&src_id);
 
                 let (filter_mode, amount) = match filter {
-                    LowLevelFilterOp::Blur(..) => (0, 0.0),
-                    LowLevelFilterOp::Contrast(amount) => (1, amount.to_f32_px()),
-                    LowLevelFilterOp::Grayscale(amount) => (2, amount.to_f32_px()),
-                    LowLevelFilterOp::HueRotate(angle) => (3, (angle as f32) / ANGLE_FLOAT_TO_FIXED),
-                    LowLevelFilterOp::Invert(amount) => (4, amount.to_f32_px()),
-                    LowLevelFilterOp::Saturate(amount) => (5, amount.to_f32_px()),
-                    LowLevelFilterOp::Sepia(amount) => (6, amount.to_f32_px()),
-                    LowLevelFilterOp::Brightness(amount) => (7, amount.to_f32_px()),
-                    LowLevelFilterOp::Opacity(amount) => (8, amount.to_f32_px()),
+                    // TODO: Implement blur filter #1351
+                    FilterOp::Blur(..) => (0, 0.0),
+                    FilterOp::Contrast(amount) => (1, amount),
+                    FilterOp::Grayscale(amount) => (2, amount),
+                    FilterOp::HueRotate(angle) => (3, angle),
+                    FilterOp::Invert(amount) => (4, amount),
+                    FilterOp::Saturate(amount) => (5, amount),
+                    FilterOp::Sepia(amount) => (6, amount),
+                    FilterOp::Brightness(amount) => (7, amount),
+                    FilterOp::Opacity(PropertyBinding::Value(amount)) => (8, amount),
+                    FilterOp::Opacity(_) => unreachable!(),
                 };
 
                 let amount = (amount * 65535.0).round() as i32;
                 let batch = batch_list.get_suitable_batch(&key, &stacking_context.screen_bounds);
 
                 let instance = CompositePrimitiveInstance::new(task_index,
                                                                src_task_index,
                                                                RenderTaskIndex(0),
@@ -422,16 +423,21 @@ impl AlphaRenderItem {
                             }
                         });
                     }
                     PrimitiveKind::Rectangle => {
                         let key = AlphaBatchKey::new(AlphaBatchKind::Rectangle, flags, blend_mode, no_textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         batch.add_instance(base_instance.build(0, 0, 0));
                     }
+                    PrimitiveKind::Line => {
+                        let key = AlphaBatchKey::new(AlphaBatchKind::Line, flags, blend_mode, no_textures);
+                        let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
+                        batch.add_instance(base_instance.build(0, 0, 0));
+                    }
                     PrimitiveKind::Image => {
                         let image_cpu = &ctx.prim_store.cpu_images[prim_metadata.cpu_prim_index.0];
 
                         let (color_texture_id, uv_address) = match image_cpu.kind {
                             ImagePrimitiveKind::Image(image_key, image_rendering, tile_offset, _) => {
                                 resolve_image(image_key,
                                               image_rendering,
                                               tile_offset,
@@ -478,22 +484,25 @@ impl AlphaRenderItem {
                     }
                     PrimitiveKind::TextRun => {
                         let text_cpu = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
                         let font_size_dp = text_cpu.logical_font_size.scale_by(ctx.device_pixel_ratio);
 
                         // TODO(gw): avoid / recycle this allocation in the future.
                         let mut instances = Vec::new();
 
-                        let texture_id = ctx.resource_cache.get_glyphs(text_cpu.font_key,
-                                                                       font_size_dp,
-                                                                       text_cpu.color,
+                        let font = FontInstanceKey::new(text_cpu.font_key,
+                                                        font_size_dp,
+                                                        text_cpu.color,
+                                                        text_cpu.normal_render_mode,
+                                                        text_cpu.glyph_options);
+
+                        let texture_id = ctx.resource_cache.get_glyphs(font,
                                                                        &text_cpu.glyph_instances,
-                                                                       text_cpu.normal_render_mode,
-                                                                       text_cpu.glyph_options, |index, handle| {
+                                                                       |index, handle| {
                             let uv_address = handle.as_int(gpu_cache);
                             instances.push(base_instance.build(index as i32,
                                                                text_cpu.normal_render_mode as i32,
                                                                uv_address));
                         });
 
                         if texture_id != SourceTexture::Invalid {
                             let textures = BatchTextures {
@@ -927,16 +936,17 @@ pub struct ColorRenderTarget {
     // TODO(gw): For now, assume that these all come from
     //           the same source texture id. This is almost
     //           always true except for pathological test
     //           cases with more than 4k x 4k of unique
     //           glyphs visible. Once the future glyph / texture
     //           cache changes land, this restriction will
     //           be removed anyway.
     pub text_run_cache_prims: Vec<PrimitiveInstance>,
+    pub line_cache_prims: Vec<PrimitiveInstance>,
     pub text_run_textures: BatchTextures,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurCommand>,
     pub horizontal_blurs: Vec<BlurCommand>,
     pub readbacks: Vec<DeviceIntRect>,
     allocator: TextureAllocator,
 }
 
@@ -945,16 +955,17 @@ impl RenderTarget for ColorRenderTarget 
         self.allocator.allocate(&size)
     }
 
     fn new(size: DeviceUintSize) -> ColorRenderTarget {
         ColorRenderTarget {
             alpha_batcher: AlphaBatcher::new(),
             box_shadow_cache_prims: Vec::new(),
             text_run_cache_prims: Vec::new(),
+            line_cache_prims: Vec::new(),
             text_run_textures: BatchTextures::no_texture(),
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
             readbacks: Vec::new(),
             allocator: TextureAllocator::new(size),
         }
     }
 
@@ -1035,38 +1046,40 @@ impl RenderTarget for ColorRenderTarget 
 
                         // todo(gw): avoid / recycle this allocation...
                         let mut instances = Vec::new();
 
                         let task_index = render_tasks.get_task_index(&task.id, pass_index);
 
                         for sub_prim_index in &prim.primitives {
                             let sub_metadata = ctx.prim_store.get_metadata(*sub_prim_index);
+                            let sub_prim_address = sub_metadata.gpu_location.as_int(gpu_cache);
+                            let instance = SimplePrimitiveInstance::new(sub_prim_address,
+                                                                        task_index,
+                                                                        RenderTaskIndex(0),
+                                                                        PackedLayerIndex(0),
+                                                                        0);     // z is disabled for rendering cache primitives
+
                             match sub_metadata.prim_kind {
                                 PrimitiveKind::TextRun => {
                                     // Add instances that reference the text run GPU location. Also supply
                                     // the parent text-shadow prim address as a user data field, allowing
                                     // the shader to fetch the text-shadow parameters.
-                                    let sub_prim_address = sub_metadata.gpu_location.as_int(gpu_cache);
                                     let text = &ctx.prim_store.cpu_text_runs[sub_metadata.cpu_prim_index.0];
-
-                                    let instance = SimplePrimitiveInstance::new(sub_prim_address,
-                                                                                task_index,
-                                                                                RenderTaskIndex(0),
-                                                                                PackedLayerIndex(0),
-                                                                                0);     // z is disabled for rendering cache primitives
-
                                     let font_size_dp = text.logical_font_size.scale_by(ctx.device_pixel_ratio);
 
-                                    let texture_id = ctx.resource_cache.get_glyphs(text.font_key,
-                                                                                   font_size_dp,
-                                                                                   text.color,
+                                    let font = FontInstanceKey::new(text.font_key,
+                                                                    font_size_dp,
+                                                                    text.color,
+                                                                    text.shadow_render_mode,
+                                                                    text.glyph_options);
+
+                                    let texture_id = ctx.resource_cache.get_glyphs(font,
                                                                                    &text.glyph_instances,
-                                                                                   text.shadow_render_mode,
-                                                                                   text.glyph_options, |index, handle| {
+                                                                                   |index, handle| {
                                         let uv_address = handle.as_int(gpu_cache);
                                         instances.push(instance.build(index as i32,
                                                                       uv_address,
                                                                       prim_address));
                                     });
 
                                     if texture_id != SourceTexture::Invalid {
                                         let textures = BatchTextures {
@@ -1077,16 +1090,19 @@ impl RenderTarget for ColorRenderTarget 
                                         instances.clear();
 
                                         debug_assert!(textures.colors[0] != SourceTexture::Invalid);
                                         debug_assert!(self.text_run_textures.colors[0] == SourceTexture::Invalid ||
                                                       self.text_run_textures.colors[0] == textures.colors[0]);
                                         self.text_run_textures = textures;
                                     }
                                 }
+                                PrimitiveKind::Line => {
+                                    self.line_cache_prims.push(instance.build(prim_address, 0, 0));
+                                }
                                 _ => {
                                     unreachable!("Unexpected sub primitive type");
                                 }
                             }
                         }
                     }
                     _ => {
                         // No other primitives make use of primitive caching yet!
@@ -1282,16 +1298,17 @@ pub enum AlphaBatchKind {
     YuvImage(ImageBufferKind, YuvFormat, YuvColorSpace),
     AlignedGradient,
     AngleGradient,
     RadialGradient,
     BoxShadow,
     CacheImage,
     BorderCorner,
     BorderEdge,
+    Line,
 }
 
 bitflags! {
     pub struct AlphaBatchKeyFlags: u8 {
         const NEEDS_CLIPPING  = 0b00000001;
         const AXIS_ALIGNED    = 0b00000010;
     }
 }
@@ -1540,17 +1557,16 @@ pub struct StackingContext {
     pub screen_bounds: DeviceIntRect,
 
     /// Local bounding rectangle of this stacking context,
     /// computed as the union of all contained items that are not
     /// `ContextIsolation::Items` on their own
     pub isolated_items_bounds: LayerRect,
 
     pub composite_ops: CompositeOps,
-    pub clip_scroll_groups: Vec<ClipScrollGroupIndex>,
 
     /// Type of the isolation of the content.
     pub isolation: ContextIsolation,
 
     /// Set for the root stacking context of a display list or an iframe. Used for determining
     /// when to isolate a mix-blend-mode composite.
     pub is_page_root: bool,
 
@@ -1573,50 +1589,32 @@ impl StackingContext {
         };
         StackingContext {
             pipeline_id,
             reference_frame_offset,
             reference_frame_id,
             screen_bounds: DeviceIntRect::zero(),
             isolated_items_bounds: LayerRect::zero(),
             composite_ops,
-            clip_scroll_groups: Vec::new(),
             isolation,
             is_page_root,
             is_visible: false,
         }
     }
 
-    pub fn clip_scroll_group(&self, clip_and_scroll: ClipAndScrollInfo) -> ClipScrollGroupIndex {
-        // Currently there is only one scrolled stacking context per context,
-        // but eventually this will be selected from the vector based on the
-        // scroll layer of this primitive.
-        for group in &self.clip_scroll_groups {
-            if group.1 == clip_and_scroll {
-                return *group;
-            }
-        }
-        unreachable!("Looking for non-existent ClipScrollGroup");
-    }
-
     pub fn can_contribute_to_scene(&self) -> bool {
         !self.composite_ops.will_make_invisible()
     }
-
-    pub fn has_clip_scroll_group(&self, clip_and_scroll: ClipAndScrollInfo) -> bool {
-        self.clip_scroll_groups.iter().rev().any(|index| index.1 == clip_and_scroll)
-    }
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub struct ClipScrollGroupIndex(pub usize, pub ClipAndScrollInfo);
 
 #[derive(Debug)]
 pub struct ClipScrollGroup {
-    pub stacking_context_index: StackingContextIndex,
     pub scroll_node_id: ClipId,
     pub clip_node_id: ClipId,
     pub packed_layer_index: PackedLayerIndex,
     pub screen_bounding_rect: Option<(TransformedRectKind, DeviceIntRect)>,
 }
 
 impl ClipScrollGroup {
     pub fn is_visible(&self) -> bool {
@@ -1668,37 +1666,37 @@ impl PackedLayer {
         xf_rect.bounding_rect.intersection(screen_rect)
                              .map(|rect| (xf_rect.kind, rect))
     }
 }
 
 #[derive(Debug, Clone, Default)]
 pub struct CompositeOps {
     // Requires only a single texture as input (e.g. most filters)
-    pub filters: Vec<LowLevelFilterOp>,
+    pub filters: Vec<FilterOp>,
 
     // Requires two source textures (e.g. mix-blend-mode)
     pub mix_blend_mode: Option<MixBlendMode>,
 }
 
 impl CompositeOps {
-    pub fn new(filters: Vec<LowLevelFilterOp>, mix_blend_mode: Option<MixBlendMode>) -> CompositeOps {
+    pub fn new(filters: Vec<FilterOp>, mix_blend_mode: Option<MixBlendMode>) -> CompositeOps {
         CompositeOps {
             filters,
             mix_blend_mode: mix_blend_mode
         }
     }
 
     pub fn count(&self) -> usize {
         self.filters.len() + if self.mix_blend_mode.is_some() { 1 } else { 0 }
     }
 
     pub fn will_make_invisible(&self) -> bool {
         for op in &self.filters {
-            if op == &LowLevelFilterOp::Opacity(Au(0)) {
+            if op == &FilterOp::Opacity(PropertyBinding::Value(0.0)) {
                 return true;
             }
         }
         false
     }
 }
 
 /// A rendering-oriented representation of frame::Frame built by the render backend
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -6,29 +6,31 @@ use channel::{self, MsgSender, Payload, 
 #[cfg(feature = "webgl")]
 use offscreen_gl_context::{GLContextAttributes, GLLimits};
 use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
 use {BuiltDisplayList, BuiltDisplayListDescriptor, ClipId, ColorF, DeviceIntPoint, DeviceIntSize};
 use {DeviceUintRect, DeviceUintSize, FontKey, GlyphDimensions, GlyphKey};
 use {ImageData, ImageDescriptor, ImageKey, LayoutPoint, LayoutVector2D, LayoutSize, LayoutTransform};
-use {NativeFontHandle, WorldPoint};
+use {FontInstanceKey, NativeFontHandle, WorldPoint};
 #[cfg(feature = "webgl")]
 use {WebGLCommand, WebGLContextId};
 
 pub type TileSize = u16;
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ApiMsg {
     AddRawFont(FontKey, Vec<u8>, u32),
     AddNativeFont(FontKey, NativeFontHandle),
     DeleteFont(FontKey),
     /// Gets the glyph dimensions
-    GetGlyphDimensions(Vec<GlyphKey>, MsgSender<Vec<Option<GlyphDimensions>>>),
+    GetGlyphDimensions(FontInstanceKey, Vec<GlyphKey>, MsgSender<Vec<Option<GlyphDimensions>>>),
+    /// Gets the glyph indices from a string
+    GetGlyphIndices(FontKey, String, MsgSender<Vec<Option<u32>>>),
     /// Adds an image from the resource cache.
     AddImage(ImageKey, ImageDescriptor, ImageData, Option<TileSize>),
     /// Updates the the resource cache with the new image data.
     UpdateImage(ImageKey, ImageDescriptor, ImageData, Option<DeviceUintRect>),
     /// Drops an image from the resource cache.
     DeleteImage(ImageKey),
     CloneApi(MsgSender<IdNamespace>),
     /// Supplies a new frame to WebRender.
@@ -57,26 +59,29 @@ pub enum ApiMsg {
     WebGLCommand(WebGLContextId, WebGLCommand),
     GenerateFrame(Option<DynamicProperties>),
     // WebVR commands that must be called in the WebGL render thread.
     VRCompositorCommand(WebGLContextId, VRCompositorCommand),
     /// An opaque handle that must be passed to the render notifier. It is used by Gecko
     /// to forward gecko-specific messages to the render thread preserving the ordering
     /// within the other messages.
     ExternalEvent(ExternalEvent),
+    /// Remove all resources associated with this namespace.
+    ClearNamespace(IdNamespace),
     ShutDown,
 }
 
 impl fmt::Debug for ApiMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             ApiMsg::AddRawFont(..) => "ApiMsg::AddRawFont",
             ApiMsg::AddNativeFont(..) => "ApiMsg::AddNativeFont",
             ApiMsg::DeleteFont(..) => "ApiMsg::DeleteFont",
             ApiMsg::GetGlyphDimensions(..) => "ApiMsg::GetGlyphDimensions",
+            ApiMsg::GetGlyphIndices(..) => "ApiMsg::GetGlyphIndices",
             ApiMsg::AddImage(..) => "ApiMsg::AddImage",
             ApiMsg::UpdateImage(..) => "ApiMsg::UpdateImage",
             ApiMsg::DeleteImage(..) => "ApiMsg::DeleteImage",
             ApiMsg::CloneApi(..) => "ApiMsg::CloneApi",
             ApiMsg::SetDisplayList(..) => "ApiMsg::SetDisplayList",
             ApiMsg::SetRootPipeline(..) => "ApiMsg::SetRootPipeline",
             ApiMsg::Scroll(..) => "ApiMsg::Scroll",
             ApiMsg::ScrollNodeWithId(..) => "ApiMsg::ScrollNodeWithId",
@@ -89,16 +94,17 @@ impl fmt::Debug for ApiMsg {
             ApiMsg::GenerateFrame(..) => "ApiMsg::GenerateFrame",
             ApiMsg::VRCompositorCommand(..) => "ApiMsg::VRCompositorCommand",
             ApiMsg::ExternalEvent(..) => "ApiMsg::ExternalEvent",
             ApiMsg::ShutDown => "ApiMsg::ShutDown",
             ApiMsg::SetPageZoom(..) => "ApiMsg::SetPageZoom",
             ApiMsg::SetPinchZoom(..) => "ApiMsg::SetPinchZoom",
             ApiMsg::SetPan(..) => "ApiMsg::SetPan",
             ApiMsg::SetWindowParameters(..) => "ApiMsg::SetWindowParameters",
+            ApiMsg::ClearNamespace(..) => "ApiMsg::ClearNamespace",
         })
     }
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
 pub struct Epoch(pub u32);
 
@@ -120,17 +126,17 @@ pub enum WebGLCommand {
     Flush,
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct PipelineId(pub u32, pub u32);
 
 #[repr(C)]
-#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
 pub struct IdNamespace(pub u32);
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
 pub struct ResourceId(pub u32);
 
 /// An opaque pointer-sized value.
 #[repr(C)]
@@ -218,20 +224,33 @@ impl RenderApi {
         self.api_sender.send(msg).unwrap();
     }
 
     /// Gets the dimensions for the supplied glyph keys
     ///
     /// Note: Internally, the internal texture cache doesn't store
     /// 'empty' textures (height or width = 0)
     /// This means that glyph dimensions e.g. for spaces (' ') will mostly be None.
-    pub fn get_glyph_dimensions(&self, glyph_keys: Vec<GlyphKey>)
+    pub fn get_glyph_dimensions(&self,
+                                font: FontInstanceKey,
+                                glyph_keys: Vec<GlyphKey>)
                                 -> Vec<Option<GlyphDimensions>> {
         let (tx, rx) = channel::msg_channel().unwrap();
-        let msg = ApiMsg::GetGlyphDimensions(glyph_keys, tx);
+        let msg = ApiMsg::GetGlyphDimensions(font, glyph_keys, tx);
+        self.api_sender.send(msg).unwrap();
+        rx.recv().unwrap()
+    }
+
+    /// Gets the glyph indices for the supplied string. These
+    /// can be used to construct GlyphKeys.
+    pub fn get_glyph_indices(&self,
+                             font_key: FontKey,
+                             text: &str) -> Vec<Option<u32>> {
+        let (tx, rx) = channel::msg_channel().unwrap();
+        let msg = ApiMsg::GetGlyphIndices(font_key, text.to_string(), tx);
         self.api_sender.send(msg).unwrap();
         rx.recv().unwrap()
     }
 
     /// Creates an `ImageKey`.
     pub fn generate_image_key(&self) -> ImageKey {
         let new_id = self.next_unique_id();
         ImageKey::new(new_id.0, new_id.1)
@@ -434,21 +453,26 @@ impl RenderApi {
                 namespace: new_id.0,
                 uid: new_id.1,
             },
             _phantom: PhantomData,
         }
     }
 
     #[inline]
-    fn next_unique_id(&self) -> (u32, u32) {
-        let IdNamespace(namespace) = self.id_namespace;
+    fn next_unique_id(&self) -> (IdNamespace, u32) {
         let ResourceId(id) = self.next_id.get();
         self.next_id.set(ResourceId(id + 1));
-        (namespace, id)
+        (self.id_namespace, id)
+    }
+}
+
+impl Drop for RenderApi {
+    fn drop(&mut self) {
+        let _ = self.api_sender.send(ApiMsg::ClearNamespace(self.id_namespace));
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum ScrollEventPhase {
     /// The user started scrolling.
     Start,
     /// The user performed a scroll. The Boolean flag indicates whether the user's fingers are
@@ -487,24 +511,24 @@ impl ZoomFactor {
     /// Get the zoom factor as an untyped float.
     pub fn get(&self) -> f32 {
         self.0
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, Eq, Hash)]
 pub struct PropertyBindingId {
-    namespace: u32,
+    namespace: IdNamespace,
     uid: u32,
 }
 
 impl PropertyBindingId {
     pub fn new(value: u64) -> Self {
         PropertyBindingId {
-            namespace: (value>>32) as u32,
+            namespace: IdNamespace((value>>32) as u32),
             uid: value as u32,
         }
     }
 }
 
 /// A unique key that is used for connecting animated property
 /// values to bindings in the display list.
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
--- a/gfx/webrender_api/src/color.rs
+++ b/gfx/webrender_api/src/color.rs
@@ -1,74 +1,105 @@
 /* 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/. */
 
+/// Represents RGBA screen colors with floating point numbers.
+///
+/// All components must be between 0.0 and 1.0.
+/// An alpha value of 1.0 is opaque while 0.0 is fully transparent.
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ColorF {
     pub r: f32,
     pub g: f32,
     pub b: f32,
     pub a: f32,
 }
 known_heap_size!(0, ColorF);
 
 impl ColorF {
-    pub fn premultiplied(&self) -> ColorF {
+    /// Constructs a new `ColorF` from its components.
+    pub fn new(r: f32, g: f32, b: f32, a: f32) -> ColorF {
         ColorF {
-            r: self.r * self.a,
-            g: self.g * self.a,
-            b: self.b * self.a,
+            r,
+            g,
+            b,
+            a,
+        }
+    }
+
+    /// Multiply the RGB channels (but not alpha) with a given factor.
+    pub fn scale_rgb(&self, scale: f32) -> ColorF {
+        ColorF {
+            r: self.r * scale,
+            g: self.g * scale,
+            b: self.b * scale,
             a: self.a,
         }
     }
+
+    pub fn to_array(&self) -> [f32; 4] {
+        [self.r, self.g, self.b, self.a]
+    }
+
+    /// Multiply the RGB components with the alpha channel.
+    ///
+    /// In premultiplied colors transistions to transparent always look "nice"
+    /// therefore they are used in CSS gradients.
+    pub fn premultiplied(&self) -> ColorF {
+        self.scale_rgb(self.a)
+    }
 }
 
+/// Represents RGBA screen colors with one byte per channel.
+///
+/// If the alpha value `a` is 255 the color is opaque.
 #[repr(C)]
 #[derive(Clone, Copy, Hash, Eq, Debug, Deserialize, PartialEq, PartialOrd, Ord, Serialize)]
 pub struct ColorU {
     pub r: u8,
     pub g: u8,
     pub b: u8,
     pub a: u8,
 }
 
-impl From<ColorF> for ColorU {
-    fn from(color: ColorF) -> ColorU {
-        ColorU {
-            r: ColorU::round_to_int(color.r),
-            g: ColorU::round_to_int(color.g),
-            b: ColorU::round_to_int(color.b),
-            a: ColorU::round_to_int(color.a),
-        }
-    }
-}
-
-impl Into<ColorF> for ColorU {
-    fn into(self) -> ColorF {
-        ColorF {
-            r: self.r as f32 / 255.0,
-            g: self.g as f32 / 255.0,
-            b: self.b as f32 / 255.0,
-            a: self.a as f32 / 255.0,
-        }
-    }
-}
-
 impl ColorU {
-    fn round_to_int(x: f32) -> u8 {
-        debug_assert!((0.0 <= x) && (x <= 1.0));
-        let f = (255.0 * x) + 0.5;
-        let val = f.floor();
-        debug_assert!(val <= 255.0);
-        val as u8
-    }
-
+    /// Constructs a new additive `ColorU` from its components.
     pub fn new(r: u8, g: u8, b: u8, a: u8) -> ColorU {
         ColorU {
             r,
             g,
             b,
             a,
         }
     }
 }
+
+fn round_to_int(x: f32) -> u8 {
+    debug_assert!((0.0 <= x) && (x <= 1.0));
+    let f = (255.0 * x) + 0.5;
+    let val = f.floor();
+    debug_assert!(val <= 255.0);
+    val as u8
+}
+
+impl From<ColorF> for ColorU {
+    fn from(color: ColorF) -> ColorU {
+        ColorU {
+            r: round_to_int(color.r),
+            g: round_to_int(color.g),
+            b: round_to_int(color.b),
+            a: round_to_int(color.a),
+        }
+    }
+}
+
+impl From<ColorU> for ColorF {
+    fn from(color: ColorU) -> ColorF {
+        ColorF {
+            r: color.r as f32 / 255.0,
+            g: color.g as f32 / 255.0,
+            b: color.b as f32 / 255.0,
+            a: color.a as f32 / 255.0,
+        }
+    }
+}
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -1,16 +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 app_units::Au;
 use euclid::SideOffsets2D;
 use {ColorF, FontKey, ImageKey, LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
-use {LayoutVector2D, PipelineId, PropertyBinding, WebGLContextId};
+use {GlyphOptions, LayoutVector2D, PipelineId, PropertyBinding, WebGLContextId};
 
 // NOTE: some of these structs have an "IMPLICIT" comment.
 // This indicates that the BuiltDisplayList will have serialized
 // a list of values nearby that this item consumes. The traversal
 // iterator should handle finding these.
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ClipAndScrollInfo {
@@ -44,18 +44,19 @@ pub struct DisplayItem {
     pub rect: LayoutRect,
     pub local_clip: LocalClip,
     pub clip_and_scroll: ClipAndScrollInfo,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum SpecificDisplayItem {
     Clip(ClipDisplayItem),
-    ScrollFrame(ClipDisplayItem),
+    ScrollFrame(ScrollFrameDisplayItem),
     Rectangle(RectangleDisplayItem),
+    Line(LineDisplayItem),
     Text(TextDisplayItem),
     Image(ImageDisplayItem),
     YuvImage(YuvImageDisplayItem),
     WebGL(WebGLDisplayItem),
     Border(BorderDisplayItem),
     BoxShadow(BoxShadowDisplayItem),
     Gradient(GradientDisplayItem),
     RadialGradient(RadialGradientDisplayItem),
@@ -72,41 +73,74 @@ pub enum SpecificDisplayItem {
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ClipDisplayItem {
     pub id: ClipId,
     pub parent_id: ClipId,
     pub image_mask: Option<ImageMask>,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub enum ScrollSensitivity {
+    ScriptAndInputEvents,
+    Script,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct ScrollFrameDisplayItem {
+    pub id: ClipId,
+    pub parent_id: ClipId,
+    pub image_mask: Option<ImageMask>,
+    pub scroll_sensitivity: ScrollSensitivity,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct RectangleDisplayItem {
     pub color: ColorF,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct LineDisplayItem {
+    pub baseline: f32, // LayerPixel
+    pub start: f32,
+    pub end: f32,
+    pub orientation: LineOrientation, // toggles whether above values are interpreted as x/y values
+    pub width: f32,
+    pub color: ColorF,
+    pub style: LineStyle,
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub enum LineOrientation {
+    Vertical,
+    Horizontal,
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub enum LineStyle {
+    Solid,
+    Dotted,
+    Dashed,
+    Wavy,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct TextDisplayItem {
     pub font_key: FontKey,
     pub size: Au,
     pub color: ColorF,
     pub glyph_options: Option<GlyphOptions>,
 } // IMPLICIT: glyphs: Vec<GlyphInstance>
 
-#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)]
-pub struct GlyphOptions {
-    // These are currently only used on windows for dwrite fonts.
-    pub use_embedded_bitmap: bool,
-    pub force_gdi_rendering: bool,
-}
-
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct WebGLDisplayItem {
     pub context_id: WebGLContextId,
 }
 
-
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct NormalBorder {
     pub left: BorderSide,
     pub right: BorderSide,
     pub top: BorderSide,
     pub bottom: BorderSide,
     pub radius: BorderRadius,
 }
@@ -325,33 +359,33 @@ pub enum MixBlendMode {
     Difference  = 10,
     Exclusion   = 11,
     Hue         = 12,
     Saturation  = 13,
     Color       = 14,
     Luminosity  = 15,
 }
 
-#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
+#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
 pub enum FilterOp {
-    Blur(Au),
+    Blur(f32),
     Brightness(f32),
     Contrast(f32),
     Grayscale(f32),
     HueRotate(f32),
     Invert(f32),
     Opacity(PropertyBinding<f32>),
     Saturate(f32),
     Sepia(f32),
 }
 
 impl FilterOp {
     pub fn is_noop(&self) -> bool {
         match *self {
-            FilterOp::Blur(length) if length == Au(0) => true,
+            FilterOp::Blur(length) if length == 0.0 => true,
             FilterOp::Brightness(amount) if amount == 1.0 => true,
             FilterOp::Contrast(amount) if amount == 1.0 => true,
             FilterOp::Grayscale(amount) if amount == 0.0 => true,
             FilterOp::HueRotate(amount) if amount == 0.0 => true,
             FilterOp::Invert(amount) if amount == 0.0 => true,
             FilterOp::Opacity(amount) if amount == PropertyBinding::Value(1.0) => true,
             FilterOp::Saturate(amount) if amount == 1.0 => true,
             FilterOp::Sepia(amount) if amount == 0.0 => true,
@@ -470,16 +504,29 @@ impl From<LayoutRect> for LocalClip {
 
 impl LocalClip {
     pub fn clip_rect(&self) -> &LayoutRect {
         match *self {
             LocalClip::Rect(ref rect) => rect,
             LocalClip::RoundedRect(ref rect, _) => &rect,
         }
     }
+
+    pub fn create_with_offset(&self, offset: &LayoutVector2D) -> LocalClip {
+        match *self {
+            LocalClip::Rect(rect) => LocalClip::from(rect.translate(offset)),
+            LocalClip::RoundedRect(rect, complex) => {
+                LocalClip::RoundedRect(rect.translate(offset),
+                                       ComplexClipRegion {
+                                            rect: complex.rect.translate(offset),
+                                            radii: complex.radii,
+                                        })
+            }
+        }
+    }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ComplexClipRegion {
     /// The boundaries of the rectangle.
     pub rect: LayoutRect,
     /// Border radii of this rectangle.
     pub radii: BorderRadius,
@@ -535,40 +582,16 @@ impl BorderRadius {
         if let Some(radius) = self.is_uniform() {
             radius == 0.0
         } else {
             false
         }
     }
 }
 
-impl ColorF {
-    pub fn new(r: f32, g: f32, b: f32, a: f32) -> ColorF {
-        ColorF {
-            r,
-            g,
-            b,
-            a,
-        }
-    }
-
-    pub fn scale_rgb(&self, scale: f32) -> ColorF {
-        ColorF {
-            r: self.r * scale,
-            g: self.g * scale,
-            b: self.b * scale,
-            a: self.a,
-        }
-    }
-
-    pub fn to_array(&self) -> [f32; 4] {
-        [self.r, self.g, self.b, self.a]
-    }
-}
-
 impl ComplexClipRegion {
     /// Create a new complex clip region.
     pub fn new(rect: LayoutRect, radii: BorderRadius) -> ComplexClipRegion {
         ComplexClipRegion {
             rect,
             radii,
         }
     }
@@ -641,8 +664,9 @@ macro_rules! define_empty_heap_size_of {
 }
 
 define_empty_heap_size_of!(ClipId);
 define_empty_heap_size_of!(RepeatMode);
 define_empty_heap_size_of!(ImageKey);
 define_empty_heap_size_of!(MixBlendMode);
 define_empty_heap_size_of!(TransformStyle);
 define_empty_heap_size_of!(LocalClip);
+define_empty_heap_size_of!(ScrollSensitivity);
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -6,21 +6,22 @@ use app_units::Au;
 use bincode;
 use serde::{Deserialize, Serialize, Serializer};
 use serde::ser::{SerializeSeq, SerializeMap};
 use time::precise_time_ns;
 use {BorderDetails, BorderDisplayItem, BorderWidths, BoxShadowClipMode, BoxShadowDisplayItem};
 use {ClipAndScrollInfo, ClipDisplayItem, ClipId, ColorF, ComplexClipRegion, DisplayItem};
 use {ExtendMode, FilterOp, FontKey, GlyphInstance, GlyphOptions, Gradient, GradientDisplayItem};
 use {GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask, ImageRendering};
-use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D, LocalClip};
-use {MixBlendMode, PipelineId, PropertyBinding, PushStackingContextDisplayItem, RadialGradient};
-use {RadialGradientDisplayItem, RectangleDisplayItem, ScrollPolicy, SpecificDisplayItem};
-use {StackingContext, TextDisplayItem, TextShadow, TransformStyle, WebGLContextId, WebGLDisplayItem};
-use {YuvColorSpace, YuvData, YuvImageDisplayItem};
+use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D, LineDisplayItem};
+use {LineOrientation, LineStyle, LocalClip, MixBlendMode, PipelineId, PropertyBinding};
+use {PushStackingContextDisplayItem, RadialGradient, RadialGradientDisplayItem};
+use {RectangleDisplayItem, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity};
+use {SpecificDisplayItem, StackingContext, TextDisplayItem, TextShadow, TransformStyle};
+use {WebGLContextId, WebGLDisplayItem, YuvColorSpace, YuvData, YuvImageDisplayItem};
 use std::marker::PhantomData;
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ItemRange<T> {
     start: usize,
     length: usize,
     _boo: PhantomData<T>,
@@ -53,16 +54,18 @@ pub struct BuiltDisplayList {
 /// items.
 #[repr(C)]
 #[derive(Copy, Clone, Default, Deserialize, Serialize)]
 pub struct BuiltDisplayListDescriptor {
     /// The first IPC time stamp: before any work has been done
     builder_start_time: u64,
     /// The second IPC time stamp: after serialization
     builder_finish_time: u64,
+    /// The third IPC time stamp: just before sending
+    send_start_time: u64,
 }
 
 pub struct BuiltDisplayListIter<'a> {
     list: &'a BuiltDisplayList,
     data: &'a [u8],
     cur_item: DisplayItem,
     cur_stops: ItemRange<GradientStop>,
     cur_glyphs: ItemRange<GlyphInstance>,
@@ -95,30 +98,33 @@ impl BuiltDisplayListDescriptor {
 impl BuiltDisplayList {
     pub fn from_data(data: Vec<u8>, descriptor: BuiltDisplayListDescriptor) -> BuiltDisplayList {
         BuiltDisplayList {
             data,
             descriptor,
         }
     }
 
-    pub fn into_data(self) -> (Vec<u8>, BuiltDisplayListDescriptor) {
+    pub fn into_data(mut self) -> (Vec<u8>, BuiltDisplayListDescriptor) {
+        self.descriptor.send_start_time = precise_time_ns();
         (self.data, self.descriptor)
     }
 
     pub fn data(&self) -> &[u8] {
         &self.data[..]
     }
 
     pub fn descriptor(&self) -> &BuiltDisplayListDescriptor {
         &self.descriptor
     }
 
-    pub fn times(&self) -> (u64, u64) {
-      (self.descriptor.builder_start_time, self.descriptor.builder_finish_time)
+    pub fn times(&self) -> (u64, u64, u64) {
+      (self.descriptor.builder_start_time,
+       self.descriptor.builder_finish_time,
+       self.descriptor.send_start_time)
     }
 
     pub fn iter(&self) -> BuiltDisplayListIter {
         BuiltDisplayListIter::new(self)
     }
 
     pub fn get<'de, T: Deserialize<'de>>(&self, range: ItemRange<T>) -> AuxIter<T> {
         AuxIter::new(&self.data[range.start .. range.start + range.length])
@@ -271,16 +277,20 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> {
     pub fn rect(&self) -> LayoutRect {
         self.iter.cur_item.rect
     }
 
     pub fn local_clip(&self) -> &LocalClip {
         &self.iter.cur_item.local_clip
     }
 
+    pub fn local_clip_with_offset(&self, offset: &LayoutVector2D) -> LocalClip {
+        self.iter.cur_item.local_clip.create_with_offset(offset)
+    }
+
     pub fn clip_and_scroll(&self) -> ClipAndScrollInfo {
         self.iter.cur_item.clip_and_scroll
     }
 
     pub fn item(&self) -> &SpecificDisplayItem {
         &self.iter.cur_item.item
     }
 
@@ -489,16 +499,33 @@ impl DisplayListBuilder {
     pub fn push_rect(&mut self, rect: LayoutRect, local_clip: Option<LocalClip>, color: ColorF) {
         let item = SpecificDisplayItem::Rectangle(RectangleDisplayItem {
             color,
         });
 
         self.push_item(item, rect, local_clip);
     }
 
+    pub fn push_line(&mut self,
+                     local_clip: Option<LocalClip>,
+                     baseline: f32,
+                     start: f32,
+                     end: f32,
+                     orientation: LineOrientation,
+                     width: f32,
+                     color: ColorF,
+                     style: LineStyle) {
+        let item = SpecificDisplayItem::Line(LineDisplayItem {
+            baseline, start, end, orientation,
+            width, color, style,
+        });
+
+        self.push_item(item, LayoutRect::zero(), local_clip);
+    }
+
     pub fn push_image(&mut self,
                       rect: LayoutRect,
                       local_clip: Option<LocalClip>,
                       stretch_size: LayoutSize,
                       tile_spacing: LayoutSize,
                       image_rendering: ImageRendering,
                       key: ImageKey) {
         let item = SpecificDisplayItem::Image(ImageDisplayItem {
@@ -841,25 +868,27 @@ impl DisplayListBuilder {
         })
     }
 
     pub fn define_scroll_frame<I>(&mut self,
                                   id: Option<ClipId>,
                                   content_rect: LayoutRect,
                                   clip_rect: LayoutRect,
                                   complex_clips: I,
-                                  image_mask: Option<ImageMask>)
+                                  image_mask: Option<ImageMask>,
+                                  scroll_sensitivity: ScrollSensitivity)
                                   -> ClipId
                                   where I: IntoIterator<Item = ComplexClipRegion>,
                                         I::IntoIter: ExactSizeIterator {
         let id = self.generate_clip_id(id);
-        let item = SpecificDisplayItem::ScrollFrame(ClipDisplayItem {
+        let item = SpecificDisplayItem::ScrollFrame(ScrollFrameDisplayItem {
             id: id,
             parent_id: self.clip_stack.last().unwrap().scroll_node_id,
             image_mask: image_mask,
+            scroll_sensitivity,
         });
 
         self.push_item(item, content_rect, Some(LocalClip::from(clip_rect)));
         self.push_iter(complex_clips);
         id
     }
 
     pub fn define_clip<I>(&mut self,
@@ -932,13 +961,14 @@ impl DisplayListBuilder {
         let end_time = precise_time_ns();
 
         (self.pipeline_id,
          self.content_size,
          BuiltDisplayList {
             descriptor: BuiltDisplayListDescriptor {
                 builder_start_time: self.builder_start_time,
                 builder_finish_time: end_time,
+                send_start_time: 0,
             },
             data: self.data,
          })
     }
 }
--- a/gfx/webrender_api/src/font.rs
+++ b/gfx/webrender_api/src/font.rs
@@ -1,14 +1,14 @@
 /* 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 app_units::Au;
-use {ColorU, ColorF, LayoutPoint};
+use {ColorU, ColorF, IdNamespace, LayoutPoint};
 use std::sync::Arc;
 
 #[cfg(target_os = "macos")] use core_foundation::string::CFString;
 #[cfg(target_os = "macos")] use core_graphics::font::CGFont;
 #[cfg(target_os = "macos")] use serde::de::{self, Deserialize, Deserializer};
 #[cfg(target_os = "macos")] use serde::ser::{Serialize, Serializer};
 #[cfg(target_os = "windows")] use dwrote::FontDescriptor;
 
@@ -47,25 +47,26 @@ pub type NativeFontHandle = FontDescript
 
 #[repr(C)]
 #[derive(Copy, Clone, Deserialize, Serialize, Debug)]
 pub struct GlyphDimensions {
     pub left: i32,
     pub top: i32,
     pub width: u32,
     pub height: u32,
+    pub advance: f32,
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Ord, PartialOrd)]
-pub struct FontKey(pub u32, pub u32);
+pub struct FontKey(pub IdNamespace, pub u32);
 
 impl FontKey {
-    pub fn new(key0: u32, key1: u32) -> FontKey {
-        FontKey(key0, key1)
+    pub fn new(namespace: IdNamespace, key: u32) -> FontKey {
+        FontKey(namespace, key)
     }
 }
 
 
 #[derive(Clone)]
 pub enum FontTemplate {
     Raw(Arc<Vec<u8>>, u32),
     Native(NativeFontHandle),
@@ -147,41 +148,71 @@ impl SubpixelPoint {
         }
     }
 
     pub fn to_f64(&self) -> (f64, f64) {
         (self.x.into(), self.y.into())
     }
 }
 
+#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)]
+pub struct GlyphOptions {
+    // These are currently only used on windows for dwrite fonts.
+    pub use_embedded_bitmap: bool,
+    pub force_gdi_rendering: bool,
+}
+
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
-pub struct GlyphKey {
+pub struct FontInstanceKey {
     pub font_key: FontKey,
     // The font size is in *device* pixels, not logical pixels.
     // It is stored as an Au since we need sub-pixel sizes, but
     // can't store as a f32 due to use of this type as a hash key.
     // TODO(gw): Perhaps consider having LogicalAu and DeviceAu
     //           or something similar to that.
     pub size: Au,
+    pub color: ColorU,
+    pub render_mode: FontRenderMode,
+    pub glyph_options: Option<GlyphOptions>,
+}
+
+impl FontInstanceKey {
+    pub fn new(font_key: FontKey,
+               size: Au,
+               mut color: ColorF,
+               render_mode: FontRenderMode,
+               glyph_options: Option<GlyphOptions>) -> FontInstanceKey {
+        // 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);
+        }
+
+        FontInstanceKey {
+            font_key,
+            size,
+            color: color.into(),
+            render_mode,
+            glyph_options,
+        }
+    }
+}
+
+#[derive(Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
+pub struct GlyphKey {
     pub index: u32,
-    pub color: ColorU,
     pub subpixel_point: SubpixelPoint,
 }
 
 impl GlyphKey {
-    pub fn new(font_key: FontKey,
-               size: Au,
-               color: ColorF,
-               index: u32,
+    pub fn new(index: u32,
                point: LayoutPoint,
                render_mode: FontRenderMode) -> GlyphKey {
         GlyphKey {
-            font_key,
-            size,
-            color: ColorU::from(color),
             index,
             subpixel_point: SubpixelPoint::new(point, render_mode),
         }
     }
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
--- a/gfx/webrender_api/src/image.rs
+++ b/gfx/webrender_api/src/image.rs
@@ -1,24 +1,29 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use std::sync::Arc;
 use {DeviceUintRect, DevicePoint};
+use {IdNamespace};
 use {TileOffset, TileSize};
 use font::{FontKey, FontTemplate};
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
-pub struct ImageKey(pub u32, pub u32);
+pub struct ImageKey(pub IdNamespace, pub u32);
 
 impl ImageKey {
-    pub fn new(key0: u32, key1: u32) -> ImageKey {
-        ImageKey(key0, key1)
+    pub fn new(namespace: IdNamespace, key: u32) -> ImageKey {
+        ImageKey(namespace, key)
+    }
+
+    pub fn dummy() -> ImageKey {
+        ImageKey(IdNamespace(0), 0)
     }
 }
 
 /// An arbitrary identifier for an external image provided by the
 /// application. It must be a unique identifier for each external
 /// image.
 #[repr(C)]
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]