Bug 1433567 - Update webrender to commit b6e69a8efbcd8dc3e0c0a8a9925e6a9355635de3. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 30 Jan 2018 10:21:30 -0500
changeset 748848 6a4d3d12e4f677ded6084ebb7b8566d07727e6b8
parent 748783 9746e0a0a81cc089ff65e30ae902864846cd1b94
child 748849 f8dc299c7c2b1bfd577249e580ad5f61e1ecfd07
push id97253
push userkgupta@mozilla.com
push dateTue, 30 Jan 2018 15:25:07 +0000
reviewersjrmuizel
bugs1433567
milestone60.0a1
Bug 1433567 - Update webrender to commit b6e69a8efbcd8dc3e0c0a8a9925e6a9355635de3. r?jrmuizel MozReview-Commit-ID: BluxAJeXvrq
gfx/doc/README.webrender
gfx/webrender/Cargo.toml
gfx/webrender/res/Proggy.ttf
gfx/webrender/src/batch.rs
gfx/webrender/src/box_shadow.rs
gfx/webrender/src/capture.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/device.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/freelist.rs
gfx/webrender/src/glyph_cache.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/gpu_cache.rs
gfx/webrender/src/gpu_types.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/platform/macos/font.rs
gfx/webrender/src/platform/unix/font.rs
gfx/webrender/src/platform/windows/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/scene.rs
gfx/webrender/src/texture_allocator.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender/src/util.rs
gfx/webrender_api/Cargo.toml
gfx/webrender_api/api.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_api/src/font.rs
gfx/webrender_bindings/Cargo.toml
gfx/wrench/Cargo.toml
gfx/wrench/src/cgfont_to_data.rs
gfx/wrench/src/rawtest.rs
gfx/wrench/src/wrench.rs
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -172,9 +172,9 @@ 2. Sometimes autoland tip has changed en
    has an env var you can set to do this). In theory you can get the same
    result by resolving the conflict manually but Cargo.lock files are usually not
    trivial to merge by hand. If it's just the third_party/rust dir that has conflicts
    you can delete it and run |mach vendor rust| again to repopulate it.
 
 -------------------------------------------------------------------------------
 
 The version of WebRender currently in the tree is:
-1d8157c71f88d5c673f5d084f02515ab74263814
+b6e69a8efbcd8dc3e0c0a8a9925e6a9355635de3
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -1,22 +1,23 @@
 [package]
 name = "webrender"
-version = "0.56.1"
+version = "0.57.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 build = "build.rs"
 
 [features]
 default = ["freetype-lib"]
 freetype-lib = ["freetype/servo-freetype-sys"]
 profiler = ["thread_profiler/thread_profiler"]
 debugger = ["ws", "serde_json", "serde", "image", "base64"]
-capture = ["webrender_api/debug-serialization", "ron", "serde"]
+capture = ["webrender_api/serialize", "ron", "serde"]
+replay = ["webrender_api/deserialize", "ron", "serde"]
 
 [dependencies]
 app_units = "0.6"
 bincode = "0.9"
 byteorder = "1.0"
 euclid = "0.16"
 fxhash = "0.2.1"
 gleam = "0.4.20"
@@ -37,20 +38,20 @@ serde = { optional = true, version = "1.
 image = { optional = true, version = "0.17" }
 base64 = { optional = true, version = "0.3.0" }
 ron = { optional = true, version = "0.1.7" }
 
 [dev-dependencies]
 angle = {git = "https://github.com/servo/angle", branch = "servo"}
 env_logger = "0.4"
 rand = "0.3"                # for the benchmarks
-servo-glutin = "0.13"     # for the example apps
+servo-glutin = "0.14"     # for the example apps
 
 [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
 freetype = { version = "0.3", default-features = false }
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
-core-foundation = "0.4.6"
-core-graphics = "0.12.3"
-core-text = { version = "8.0", default-features = false }
+core-foundation = "0.5"
+core-graphics = "0.13"
+core-text = { version = "9.0", default-features = false }
new file mode 100644
index 0000000000000000000000000000000000000000..308d3e1ac9b89f4866fdef05289aaf59411c1c17
GIT binary patch
literal 5284
zc%1E4jdN7h6+idhxBK3160;kkEDXZiO~eh=d|3pn!e$92i3l-}6rxBrApvS)BCIwr
zQ8sbOmw*r{iX#$&+K3;eYAsR>!NKTEalmiKI_(zGLZ{WhXeVlt?YZZ@BrBBm4`|<;
zckl0<d%o_u_uO}Z0YCzTKtSO<f9{ek-Wxwe&ZhvjmU;dKb6u}Zst2&I1mFi3EzZcU
z`FWL!3X@Q#q;T$nC6UtlKBR+4UtduZs5`Oi?tdcv9MUhZs$O3O^juaZ+trnUl|yHr
zX*22TS0f`KB=V4c6zSB}HNpBjzpOlp^kD$@U3G0mz|eObM0%hmP+tdwfDtDhK&F0I
zpr$f~`R+jaB#ysaSGz74Er8zxB!}fVaEwF+ySkriny!bMxMuIPSeR8!={aSNj6Iz;
zE3>IxTfK+%CXMi^c>VF5u!C^9rZ?QWDVcDSrVk=t5nNMCI#ml(RrhSah|u8c?cM`9
z$|S>ByW@5=rYh*k>2&rZP}Flyr$beQq7JmSh7j1**4Ea3ipHV)8u^@7*#VP`VQf`u
z6>1r^!|HH?sV8kDnC8jwn3<4DS`sUez)(Iqpjt83_~Y^MiM<4Sdyn@f_FAo&lA+X&
zeBB~Z+_^crW?`C|?L6(7!UR{;2!M51)tJ!KAtAU;eI=x+psC4e-}?9L@Y<ww%GvqJ
zJvy=H6?HYvE9la!$S%*Hu+zI+Fkw?8aZ)ucKQ%8zs4XeK0BAj~T3%!T%p)kAakYe5
z7^VmzS9|RiOs1CWacW_p$buE6ed&Z!yne?XgXi~nrkJR#Ih~wq<sA?9Hx0TY&FR_h
zGkxg;(nhcxQLtisc!8A(t9o4H=(w=NVg)PZMp&6e9Zc+{WXKGSn0F{NyP54Zr7vxp
zOb#-Sb!>122%h)$G`sF6*#@`fPc)f<X4he24!Y0yr;H*84bO7vPb4oS*zDTajF#k{
zI-B~4rXcHXr_=D#oaQ~1x0iIoXnG2T@#fa&mt~h7C4o05uGzWtGlE&@F?ln|eqAxe
zFL$rW+@hEvb)83J5^iwea|c;7_K9YOf<YVX2P{dRO~@uZ^_J4-w|fkc<jrW)6mjN^
zXbw6szB#nG=krq^4$;De9Ze3u<|FRuj{K`bR>^fIcOS#O4C*lFIIztsm%na>>#&V<
zaJ91fi5CoL2Z^Wo2l}$=4c<nRcZ2h^Tl3i@uc5+q*j+cK-D;C|=N9|o6l6IBR!GDg
zov^sW82zBwXH{|8|1ovJu52Hf{<^|-%<xg+I%qRF-#J2KJ~SGBC7>A!4K95^Q*ha^
z7+Z+58Kn%cq{~6}F?A(xg^1VQIxp8YBIT|l-au|{L!1n4g61y^yRC7uTA~qR9o78t
zVRt51!J@94Np(0@j7Kzi_@q2rP<(8xVpVx&G<}<tx~MZWx<ZLzjjVoe<RDn~hAXR%
z$tCF|nft6Y6R#`lvB-TFSYJoQIIX)Yq$o@tS%ynP@a@~Cm-QIOlOj{QX6M)uXpxM+
zr1l$qAi_ENEYoBTa@&%o-dyOT{kZXk$GlzbUOY-HYBVIc0QF;wK$T_bq`78S8AZ_5
zz8~yXW>@WLsh#1SJ^Cndx~QYhE@QB??wdQ&cEug7lmK1J^{11N?u*aAdT+CX2uCP}
z$kJnoG*gVu>pYQ^e<lz%q*6E208M&wVb`kdp80WR%{D0u9e%*gZC7*Hz}%B9y@Z`H
z*waXul3h4Q+u#_m3vO3)gB`aM@Q#2b2yAj4_C47Vp{1*DOWfgo0@8e0Pw%SSnu!xB
za0T*uaSqgD_|&JF{E^6;T}7D|V~2Hzs2ZoIRX)ArIWrq>tm;h>GaE5PmTnM+84W8x
zP)~`~bj9Uu*Br*bAbb-R0rG9?%(ijI@(TCf2Q)W56zR#TC+LJx=o#ZqBS<z1CynpR
zFtXFCTFyP#OSMsb)nHqOQC_mt!#+~WfiuHSqnbT2FeIU2?8+&xr&AF>T<oL10(+Ur
zB`Z4&(1~Dm`}n@Bx?&~|1hk3RNiAB!b{e&&Jy%~k<xii@f$A#mSEZkyG@@ayzLBZL
zz(xrT?G#sEMm%MK#{ChynYF&zVTc<Q(Qc>xRO`t-o0K|(=3g_lq3zlbtwmwW-p0np
zO-6~{3Imxr{Wh6$FON^2r)b8GEqn-%1jhsYh7jw=OMrQHoX68QQ&41GTo!Vv@e?(!
z4?>pWlR{={se)&&S|Sw0`wNAvxZ+MMMvnh=GK;nB2<s+Xv@NWU-kQ)NQ@d$<B^78n
z%gV59@ucuj*V;|Didj<}=f;H;Ingh7Pb&m@55q^^G-sNhDjD`6m6#V|fap+M9Fyda
zNq#!a!u|l<0P@~<41NQ@MRUFI3Y@_HeK-jppp#_y5Wd6Si+5S+QNhm%6X8cN1Lnda
zxDCpo8tNeoTVN;bfo^yej=&r62Y3fQg3sV9_@1fkGB$ywvUE0s<*@l|0V`t5SS71t
z4XlN2VUMuAY(IO6y~>WTH`qJukL+{y75kPeTzcbR!A3)5p<ThW1S1yiMxGV#*;l}1
zxEiwHCdh-uupCyyT4;b4=zzyyAG`nuads!*6F3e17>$)Bu`5{`o6dX~$<J7T)v#Z%
z5Nl?e*)LfqdxpKtUSn^u_t>A<7wjAx#5fYUi@W(Xd<OUNd|t$F<12YBujdc&&HPcm
zhd;|-<iF#G`Rn{R|B#>JU-EAR2&+gE6Ge)cCNjiNM2^T4MWR%ci`611LZU@%5j(`=
z;wkZ>I3nH_ABofAoETQ(l<`WclBvvA3X~G1T&Yp+RW>TE%64V9vU?;S)*eI}ZCz$J
z^I`o0dGT;L-qEkcRh^Hy2*91N9@pj}%*0dhYrMtx;ktYZe}(ff$P^aOE@zY3WW%N=
zvu&mQ$!w-g%`3MNjDst|3)jJH%y2PUts$%1;c<8d_sdau8{WtKM&JwhJ6axqC}uf<
zxmXIjhGnyxST4Jnm9Q0Th1{|H5c^vCW4qWcqcwUs#NmfeG-`YzDyiayH!lPq=+Pl{
zA5XWvBU@w(Q`p?-mY}*1pbTWYG%HK5s=X_yn@<#<xM*}3`9+zTfW}NsLE7=SySuTU
ziW#)v&f19cSd9Clp54zncpv{%Pu3^ulXZ_iRnOFK(4TXAq7)f);*Ksux%*7Hak@*N
zgbt<LtP#0rB)T=)6s?Y~jusDlh7%$mMc$9R7kM{wBGMP>iQMw%k7J7UKR?X)s{dxq
zTakF<H^4MFejsBTz{coE4>0tu<sO-Z7^{fXeM9d;l_f~Iy5}N~j7AR8M=uMSNN&9h
zo)IfNLScVsOkP>eGuWsViAQaWS-apR3oz>Gg3*{5V)=##(~p!PIk?z|!7*dH(CR49
zh(TE{;~$%Gte&#m^fw|;eq(2UQS1=w&v-AM-vu-JZ~B+g9@Bw;mn~x@M)P#B&i|kP
z)kmHJ-~WKR@X?@3o|1|U@mc^AGyM0iH8F?Fpw+|za`84`U<GA9F|h@4zlk+SVpbE|
z;d<tRC1`66tb-u_VeY~+cs|iJ_*Np)AsaIBy$;e5>-Zm9hx}_0tu(cybUECE$l%vN
zEsa&--%8R|NUx=SElO0PokB#lunOPxc$=w4p7dOV{VKFmjnvvp$mp;X^%vpTD#YdJ
zC5Zk4P>0kyq&A?Oj(4Pbyq9EM!bS<rOG?+0#Z0uA2|pz&NWHXP8RKawN>$MKy`(E=
Vy7D6bRU~2l7o+p3x-VMOe*pt#<gx$&
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -30,72 +30,77 @@ use std::{usize, f32, i32};
 use tiling::{RenderTargetContext, RenderTargetKind};
 use util::{MatrixHelpers, TransformedRectKind};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(i32::MAX as u32);
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum TransformBatchKind {
     TextRun(GlyphFormat),
     Image(ImageBufferKind),
     YuvImage(ImageBufferKind, YuvFormat, YuvColorSpace),
     AlignedGradient,
     AngleGradient,
     RadialGradient,
     BorderCorner,
     BorderEdge,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BrushImageSourceKind {
     Alpha,
     Color,
     ColorAlphaMask,
 }
 
 impl BrushImageSourceKind {
     pub fn from_render_target_kind(render_target_kind: RenderTargetKind) -> BrushImageSourceKind {
         match render_target_kind {
             RenderTargetKind::Color => BrushImageSourceKind::Color,
             RenderTargetKind::Alpha => BrushImageSourceKind::Alpha,
         }
     }
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BrushBatchKind {
     Picture(BrushImageSourceKind),
     Solid,
     Line,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BatchKind {
     Composite {
         task_id: RenderTaskId,
         source_id: RenderTaskId,
         backdrop_id: RenderTaskId,
     },
     HardwareComposite,
     SplitComposite,
     Blend,
     Transformable(TransformedRectKind, TransformBatchKind),
     Brush(BrushBatchKind),
 }
 
 /// Optional textures that can be used as a source in the shaders.
 /// Textures that are not used by the batch are equal to TextureId::invalid().
 #[derive(Copy, Clone, Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BatchTextures {
     pub colors: [SourceTexture; 3],
 }
 
 impl BatchTextures {
     pub fn no_texture() -> Self {
         BatchTextures {
             colors: [SourceTexture::Invalid; 3],
@@ -115,17 +120,18 @@ impl BatchTextures {
     pub fn color(texture: SourceTexture) -> Self {
         BatchTextures {
             colors: [texture, texture, SourceTexture::Invalid],
         }
     }
 }
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct AlphaPrimitiveBatch {
     pub key: BatchKey,
     pub instances: Vec<PrimitiveInstance>,
     pub item_rects: Vec<DeviceIntRect>,
 }
 
 impl AlphaPrimitiveBatch {
     pub fn new(key: BatchKey) -> AlphaPrimitiveBatch {
@@ -133,33 +139,35 @@ impl AlphaPrimitiveBatch {
             key,
             instances: Vec::new(),
             item_rects: Vec::new(),
         }
     }
 }
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct OpaquePrimitiveBatch {
     pub key: BatchKey,
     pub instances: Vec<PrimitiveInstance>,
 }
 
 impl OpaquePrimitiveBatch {
     pub fn new(key: BatchKey) -> OpaquePrimitiveBatch {
         OpaquePrimitiveBatch {
             key,
             instances: Vec::new(),
         }
     }
 }
 
 #[derive(Copy, Clone, Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BatchKey {
     pub kind: BatchKind,
     pub blend_mode: BlendMode,
     pub textures: BatchTextures,
 }
 
 impl BatchKey {
     pub fn new(kind: BatchKind, blend_mode: BlendMode, textures: BatchTextures) -> Self {
@@ -178,17 +186,18 @@ impl BatchKey {
     }
 }
 
 #[inline]
 fn textures_compatible(t1: SourceTexture, t2: SourceTexture) -> bool {
     t1 == SourceTexture::Invalid || t2 == SourceTexture::Invalid || t1 == t2
 }
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct AlphaBatchList {
     pub batches: Vec<AlphaPrimitiveBatch>,
 }
 
 impl AlphaBatchList {
     fn new() -> Self {
         AlphaBatchList {
             batches: Vec::new(),
@@ -256,17 +265,18 @@ impl AlphaBatchList {
 
         let batch = &mut self.batches[selected_batch_index.unwrap()];
         batch.item_rects.push(*item_bounding_rect);
 
         &mut batch.instances
     }
 }
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct OpaqueBatchList {
     pub pixel_area_threshold_for_new_batch: i32,
     pub batches: Vec<OpaquePrimitiveBatch>,
 }
 
 impl OpaqueBatchList {
     fn new(pixel_area_threshold_for_new_batch: i32) -> Self {
         OpaqueBatchList {
@@ -322,17 +332,18 @@ impl OpaqueBatchList {
         //           build these in reverse and avoid having
         //           to reverse the instance array here.
         for batch in &mut self.batches {
             batch.instances.reverse();
         }
     }
 }
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BatchList {
     pub alpha_batch_list: AlphaBatchList,
     pub opaque_batch_list: OpaqueBatchList,
 }
 
 impl BatchList {
     pub fn new(screen_size: DeviceIntSize) -> Self {
         // The threshold for creating a new batch is
@@ -369,17 +380,18 @@ impl BatchList {
     }
 
     fn finalize(&mut self) {
         self.opaque_batch_list.finalize()
     }
 }
 
 /// Encapsulates the logic of building batches for items that are blended.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct AlphaBatcher {
     pub batch_list: BatchList,
     pub text_run_cache_prims: FastHashMap<SourceTexture, Vec<PrimitiveInstance>>,
     glyph_fetch_buffer: Vec<GlyphFetchResult>,
 }
 
 impl AlphaBatcher {
     pub fn new(screen_size: DeviceIntSize) -> Self {
@@ -1448,17 +1460,18 @@ fn make_polygon(
         transform.m42 as f64,
         transform.m43 as f64,
         transform.m44 as f64);
     Polygon::from_transformed_rect(rect.cast().unwrap(), mat, anchor)
 }
 
 /// Batcher managing draw calls into the clip mask (in the RT cache).
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipBatcher {
     /// Rectangle draws fill up the rectangles with rounded corners.
     pub rectangles: Vec<ClipMaskInstance>,
     /// Image draws apply the image masking.
     pub images: FastHashMap<SourceTexture, Vec<ClipMaskInstance>>,
     pub border_clears: Vec<ClipMaskInstance>,
     pub borders: Vec<ClipMaskInstance>,
 }
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.rs
@@ -24,17 +24,18 @@ pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
 pub const MAX_BLUR_RADIUS : f32 = 300.;
 
 // The amount of padding added to the border corner drawn in the box shadow
 // mask. This ensures that we get a few pixels past the corner that can be
 // blurred without being affected by the border radius.
 pub const MASK_CORNER_PADDING: f32 = 4.0;
 
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BoxShadowCacheKey {
     pub width: Au,
     pub height: Au,
     pub blur_radius: Au,
     pub spread_radius: Au,
     pub offset_x: Au,
     pub offset_y: Au,
     pub br_top_left_w: Au,
--- a/gfx/webrender/src/capture.rs
+++ b/gfx/webrender/src/capture.rs
@@ -1,71 +1,79 @@
 /* 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::fs::File;
-use std::io::{Read, Write};
 use std::path::{Path, PathBuf};
 
 use api::{CaptureBits, ExternalImageData, ExternalImageId, ImageDescriptor, TexelRect};
 #[cfg(feature = "png")]
 use device::ReadPixelsFormat;
-use ron::{de, ser};
-use serde::{Deserialize, Serialize};
+use ron;
+use serde;
 
 
 pub struct CaptureConfig {
     pub root: PathBuf,
     pub bits: CaptureBits,
-    pretty: ser::PrettyConfig,
+    #[cfg(feature = "capture")]
+    pretty: ron::ser::PrettyConfig,
 }
 
 impl CaptureConfig {
+    #[cfg(feature = "capture")]
     pub fn new(root: PathBuf, bits: CaptureBits) -> Self {
         CaptureConfig {
             root,
             bits,
-            pretty: ser::PrettyConfig {
+            #[cfg(feature = "capture")]
+            pretty: ron::ser::PrettyConfig {
                 enumerate_arrays: true,
-                .. ser::PrettyConfig::default()
+                .. ron::ser::PrettyConfig::default()
             },
         }
     }
 
+    #[cfg(feature = "capture")]
     pub fn serialize<T, P>(&self, data: &T, name: P)
     where
-        T: Serialize,
+        T: serde::Serialize,
         P: AsRef<Path>,
     {
-        let ron = ser::to_string_pretty(data, self.pretty.clone())
+        use std::io::Write;
+
+        let ron = ron::ser::to_string_pretty(data, self.pretty.clone())
             .unwrap();
         let path = self.root
             .join(name)
             .with_extension("ron");
         let mut file = File::create(path)
             .unwrap();
         write!(file, "{}\n", ron)
             .unwrap();
     }
 
+    #[cfg(feature = "replay")]
     pub fn deserialize<T, P>(root: &PathBuf, name: P) -> Option<T>
     where
-        T: for<'a> Deserialize<'a>,
+        T: for<'a> serde::Deserialize<'a>,
         P: AsRef<Path>,
     {
+        use std::io::Read;
+
         let mut string = String::new();
         let path = root
             .join(name)
             .with_extension("ron");
         File::open(path)
             .ok()?
             .read_to_string(&mut string)
             .unwrap();
-        Some(de::from_str(&string)
+        Some(ron::de::from_str(&string)
             .unwrap())
     }
 
     #[cfg(feature = "png")]
     pub fn save_png(
         path: PathBuf, size: (u32, u32), format: ReadPixelsFormat, data: &[u8],
     ) {
         use api::ImageFormat;
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -18,17 +18,18 @@ use util::TransformOrOffset;
 
 pub type ScrollStates = FastHashMap<ClipId, ScrollingState>;
 
 /// An id that identifies coordinate systems in the ClipScrollTree. Each
 /// coordinate system has an id and those ids will be shared when the coordinates
 /// system are the same or are in the same axis-aligned space. This allows
 /// for optimizing mask generation.
 #[derive(Debug, Copy, Clone, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CoordinateSystemId(pub u32);
 
 impl CoordinateSystemId {
     pub fn root() -> Self {
         CoordinateSystemId(0)
     }
 
     pub fn next(&self) -> Self {
@@ -456,21 +457,26 @@ impl ClipScrollTree {
                 gpu_node_data,
                 scene_properties,
             );
         }
     }
 
     pub fn build_clip_chains(&mut self, screen_rect: &DeviceIntRect) {
         for descriptor in &self.clip_chains_descriptors {
+            // A ClipChain is an optional parent (which is another ClipChain) and a list of
+            // ClipScrollNode clipping nodes. Here we start the ClipChain with a clone of the
+            // parent's node, if necessary.
             let mut chain = match descriptor.parent {
                 Some(id) => self.clip_chains[&id].clone(),
                 None => ClipChain::empty(screen_rect),
             };
 
+            // Now we walk through each ClipScrollNode in the vector of clip nodes and
+            // extract their ClipChain nodes to construct the final list.
             for clip_id in &descriptor.clips {
                 if let Some(ref node_chain) = self.nodes[&clip_id].clip_chain {
                     if let Some(ref nodes) = node_chain.nodes {
                         chain.add_node((**nodes).clone());
                     }
                 }
             }
 
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -18,17 +18,18 @@ use std::mem;
 use std::ops::Add;
 use std::path::PathBuf;
 use std::ptr;
 use std::rc::Rc;
 use std::thread;
 
 
 #[derive(Debug, Copy, Clone, PartialEq, Ord, Eq, PartialOrd)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameId(usize);
 
 impl FrameId {
     pub fn new(value: usize) -> Self {
         FrameId(value)
     }
 }
 
@@ -59,17 +60,18 @@ const DEFAULT_TEXTURE: TextureSlot = Tex
 
 #[repr(u32)]
 pub enum DepthFunction {
     Less = gl::LESS,
     LessEqual = gl::LEQUAL,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum TextureFilter {
     Nearest,
     Linear,
 }
 
 #[derive(Debug)]
 pub enum VertexAttributeKind {
     F32,
@@ -399,31 +401,31 @@ impl<V> VBO<V> {
 }
 
 impl<T> Drop for VBO<T> {
     fn drop(&mut self) {
         debug_assert!(thread::panicking() || self.id == 0);
     }
 }
 
-#[cfg_attr(feature = "capture", derive(Clone))]
+#[cfg_attr(feature = "replay", derive(Clone))]
 pub struct ExternalTexture {
     id: gl::GLuint,
     target: gl::GLuint,
 }
 
 impl ExternalTexture {
     pub fn new(id: u32, target: TextureTarget) -> Self {
         ExternalTexture {
             id,
             target: get_gl_target(target),
         }
     }
 
-    #[cfg(feature = "capture")]
+    #[cfg(feature = "replay")]
     pub fn internal_id(&self) -> gl::GLuint {
         self.id
     }
 }
 
 pub struct Texture {
     id: gl::GLuint,
     target: gl::GLuint,
@@ -465,17 +467,17 @@ impl Texture {
     pub fn has_depth(&self) -> bool {
         self.depth_rb.is_some()
     }
 
     pub fn get_rt_info(&self) -> Option<&RenderTargetInfo> {
         self.render_target.as_ref()
     }
 
-    #[cfg(feature = "capture")]
+    #[cfg(feature = "replay")]
     pub fn into_external(mut self) -> ExternalTexture {
         let ext = ExternalTexture {
             id: self.id,
             target: self.target,
         };
         self.id = 0; // don't complain, moved out
         ext
     }
@@ -1260,17 +1262,17 @@ impl Device {
     }
 
     pub fn delete_texture(&mut self, mut texture: Texture) {
         self.free_texture_storage(&mut texture);
         self.gl.delete_textures(&[texture.id]);
         texture.id = 0;
     }
 
-    #[cfg(feature = "capture")]
+    #[cfg(feature = "replay")]
     pub fn delete_external_texture(&mut self, mut external: ExternalTexture) {
         self.bind_external_texture(DEFAULT_TEXTURE, &external);
         //Note: the format descriptor here doesn't really matter
         self.free_texture_storage_impl(external.target, FormatDesc {
             internal: gl::R8 as _,
             external: gl::RED,
             pixel_type: gl::UNSIGNED_BYTE,
         });
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -19,17 +19,18 @@ use frame_builder::{FrameBuilder, FrameB
 use gpu_cache::GpuCache;
 use internal_types::{FastHashMap, FastHashSet, RenderedDocument};
 use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
 use resource_cache::{FontInstanceMap,ResourceCache, TiledImageMap};
 use scene::{Scene, StackingContextHelpers, ScenePipeline, SceneProperties};
 use tiling::{CompositeOps, Frame};
 
 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameId(pub u32);
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
     r: 0.3,
     g: 0.3,
     b: 0.3,
     a: 0.6,
 };
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -15,17 +15,17 @@ use app_units::Au;
 use border::ImageBorderSegment;
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore, Contains};
 use clip_scroll_node::{ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use euclid::{SideOffsets2D, vec2};
 use frame::FrameId;
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCache;
-use gpu_types::{ClipScrollNodeData, PictureType};
+use gpu_types::{ClipChainRectIndex, ClipScrollNodeData, PictureType};
 use internal_types::{FastHashMap, FastHashSet, RenderPassIndex};
 use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive, PictureSurface};
 use prim_store::{BrushKind, BrushPrimitive, ImageCacheKey, YuvImagePrimitiveCpu};
 use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, ImageSource, PrimitiveKind};
 use prim_store::{PrimitiveContainer, PrimitiveIndex, SpecificPrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu};
 use prim_store::{BrushSegmentDescriptor, TextRunPrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
@@ -62,17 +62,18 @@ struct StackingContext {
     transform_style: TransformStyle,
 
     /// The primitive index for the root Picture primitive
     /// that this stacking context is mapped to.
     pic_prim_index: PrimitiveIndex,
 }
 
 #[derive(Clone, Copy)]
-#[cfg_attr(feature = "capture", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameBuilderConfig {
     pub enable_scrollbars: bool,
     pub default_font_render_mode: FontRenderMode,
     pub debug: bool,
     pub dual_source_blending_is_supported: bool,
     pub dual_source_blending_is_enabled: bool,
 }
 
@@ -120,35 +121,53 @@ pub struct FrameBuilder {
     /// A stack of the current pictures, used during scene building.
     pub picture_stack: Vec<PrimitiveIndex>,
 
     /// A temporary stack of stacking context properties, used only
     /// during scene building.
     sc_stack: Vec<StackingContext>,
 }
 
-pub struct PrimitiveContext<'a> {
+pub struct FrameContext<'a> {
     pub device_pixel_scale: DevicePixelScale,
+    pub scene_properties: &'a SceneProperties,
+    pub pipelines: &'a FastHashMap<PipelineId, ScenePipeline>,
+    pub screen_rect: DeviceIntRect,
+    pub clip_scroll_tree: &'a ClipScrollTree,
+    pub node_data: &'a [ClipScrollNodeData],
+}
+
+pub struct FrameState<'a> {
+    pub render_tasks: &'a mut RenderTaskTree,
+    pub profile_counters: &'a mut FrameProfileCounters,
+    pub clip_store: &'a mut ClipStore,
+    pub local_clip_rects: &'a mut Vec<LayerRect>,
+    pub resource_cache: &'a mut ResourceCache,
+    pub gpu_cache: &'a mut GpuCache,
+}
+
+pub struct PrimitiveRunContext<'a> {
     pub display_list: &'a BuiltDisplayList,
     pub clip_chain: Option<&'a ClipChain>,
     pub scroll_node: &'a ClipScrollNode,
+    pub clip_chain_rect_index: ClipChainRectIndex,
 }
 
-impl<'a> PrimitiveContext<'a> {
+impl<'a> PrimitiveRunContext<'a> {
     pub fn new(
-        device_pixel_scale: DevicePixelScale,
         display_list: &'a BuiltDisplayList,
         clip_chain: Option<&'a ClipChain>,
         scroll_node: &'a ClipScrollNode,
+        clip_chain_rect_index: ClipChainRectIndex,
     ) -> Self {
-        PrimitiveContext {
-            device_pixel_scale,
+        PrimitiveRunContext {
             display_list,
             clip_chain,
             scroll_node,
+            clip_chain_rect_index,
         }
     }
 }
 
 impl FrameBuilder {
     pub fn empty() -> Self {
         FrameBuilder {
             hit_testing_runs: Vec::new(),
@@ -1570,87 +1589,96 @@ impl FrameBuilder {
         result.items.dedup();
         result
     }
 
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(
         &mut self,
-        clip_scroll_tree: &mut ClipScrollTree,
+        clip_scroll_tree: &ClipScrollTree,
         pipelines: &FastHashMap<PipelineId, ScenePipeline>,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         profile_counters: &mut FrameProfileCounters,
         device_pixel_scale: DevicePixelScale,
         scene_properties: &SceneProperties,
+        local_clip_rects: &mut Vec<LayerRect>,
         node_data: &[ClipScrollNodeData],
-        local_rects: &mut Vec<LayerRect>,
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
         if self.prim_store.cpu_pictures.is_empty() {
             return None
         }
 
         // The root picture is always the first one added.
         let prim_run_cmds = mem::replace(&mut self.prim_store.cpu_pictures[0].runs, Vec::new());
         let root_clip_scroll_node = &clip_scroll_tree.nodes[&clip_scroll_tree.root_reference_frame_id()];
 
         let display_list = &pipelines
             .get(&root_clip_scroll_node.pipeline_id)
             .expect("No display list?")
             .display_list;
 
-        let root_prim_context = PrimitiveContext::new(
+        let frame_context = FrameContext {
             device_pixel_scale,
+            scene_properties,
+            pipelines,
+            screen_rect: self.screen_rect.to_i32(),
+            clip_scroll_tree,
+            node_data,
+        };
+
+        let mut frame_state = FrameState {
+            render_tasks,
+            profile_counters,
+            clip_store: &mut self.clip_store,
+            local_clip_rects,
+            resource_cache,
+            gpu_cache,
+        };
+
+        let root_prim_run_context = PrimitiveRunContext::new(
             display_list,
             root_clip_scroll_node.clip_chain.as_ref(),
             root_clip_scroll_node,
+            ClipChainRectIndex(0),
         );
 
         let mut child_tasks = Vec::new();
         self.prim_store.reset_prim_visibility();
         self.prim_store.prepare_prim_runs(
             &prim_run_cmds,
             root_clip_scroll_node.pipeline_id,
-            gpu_cache,
-            resource_cache,
-            render_tasks,
-            &mut self.clip_store,
-            clip_scroll_tree,
-            pipelines,
-            &root_prim_context,
+            &root_prim_run_context,
             true,
             &mut child_tasks,
-            profile_counters,
             None,
-            scene_properties,
             SpecificPrimitiveIndex(0),
-            &self.screen_rect.to_i32(),
-            node_data,
-            local_rects,
+            &frame_context,
+            &mut frame_state,
         );
 
         let pic = &mut self.prim_store.cpu_pictures[0];
         pic.runs = prim_run_cmds;
 
         let root_render_task = RenderTask::new_picture(
             None,
             PrimitiveIndex(0),
             RenderTargetKind::Color,
             ContentOrigin::Screen(DeviceIntPoint::zero()),
             PremultipliedColorF::TRANSPARENT,
             ClearMode::Transparent,
             child_tasks,
             PictureType::Image,
         );
 
-        let render_task_id = render_tasks.add(root_render_task);
+        let render_task_id = frame_state.render_tasks.add(root_render_task);
         pic.surface = Some(PictureSurface::RenderTask(render_task_id));
         Some(render_task_id)
     }
 
     fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache) {
         static SCROLLBAR_PADDING: f32 = 8.0;
 
         for scrollbar_prim in &self.scrollbar_prims {
@@ -1733,18 +1761,18 @@ impl FrameBuilder {
             clip_scroll_tree,
             pipelines,
             resource_cache,
             gpu_cache,
             &mut render_tasks,
             &mut profile_counters,
             device_pixel_scale,
             scene_properties,
+            &mut clip_chain_local_clip_rects,
             &node_data,
-            &mut clip_chain_local_clip_rects,
         );
 
         let mut passes = Vec::new();
         resource_cache.block_until_all_resources_added(gpu_cache, texture_cache_profile);
 
         if let Some(main_render_task_id) = main_render_task_id {
             let mut required_pass_count = 0;
             render_tasks.max_depth(main_render_task_id, 0, &mut required_pass_count);
@@ -1769,19 +1797,19 @@ impl FrameBuilder {
         let use_dual_source_blending = self.config.dual_source_blending_is_enabled &&
                                        self.config.dual_source_blending_is_supported;
 
         for (pass_index, pass) in passes.iter_mut().enumerate() {
             let ctx = RenderTargetContext {
                 device_pixel_scale,
                 prim_store: &self.prim_store,
                 resource_cache,
-                node_data: &node_data,
                 clip_scroll_tree,
                 use_dual_source_blending,
+                node_data: &node_data,
             };
 
             pass.build(
                 &ctx,
                 gpu_cache,
                 &mut render_tasks,
                 &mut deferred_resolves,
                 &self.clip_store,
--- a/gfx/webrender/src/freelist.rs
+++ b/gfx/webrender/src/freelist.rs
@@ -5,21 +5,23 @@
 use std::marker::PhantomData;
 use util::recycle_vec;
 
 // TODO(gw): Add an occupied list head, for fast
 //           iteration of the occupied list to implement
 //           retain() style functionality.
 
 #[derive(Debug, Copy, Clone, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct Epoch(u32);
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FreeListHandle<T> {
     index: u32,
     epoch: Epoch,
     _marker: PhantomData<T>,
 }
 
 impl<T> FreeListHandle<T> {
     pub fn weak(&self) -> WeakFreeListHandle<T> {
@@ -37,31 +39,34 @@ impl<T> Clone for WeakFreeListHandle<T> 
             index: self.index,
             epoch: self.epoch,
             _marker: PhantomData,
         }
     }
 }
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct WeakFreeListHandle<T> {
     index: u32,
     epoch: Epoch,
     _marker: PhantomData<T>,
 }
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct Slot<T> {
     next: Option<u32>,
     epoch: Epoch,
     value: Option<T>,
 }
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FreeList<T> {
     slots: Vec<Slot<T>>,
     free_list_head: Option<u32>,
 }
 
 pub enum UpsertResult<T> {
     Updated(T),
     Inserted(FreeListHandle<T>),
--- a/gfx/webrender/src/glyph_cache.rs
+++ b/gfx/webrender/src/glyph_cache.rs
@@ -4,36 +4,37 @@
 
 use api::{DevicePoint, DeviceUintSize, GlyphKey};
 use glyph_rasterizer::{FontInstance, GlyphFormat};
 use internal_types::FastHashMap;
 use resource_cache::ResourceClassCache;
 use std::sync::Arc;
 use texture_cache::TextureCacheHandle;
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GenericCachedGlyphInfo<D> {
     pub texture_cache_handle: TextureCacheHandle,
     pub glyph_bytes: D,
     pub size: DeviceUintSize,
     pub offset: DevicePoint,
     pub scale: f32,
     pub format: GlyphFormat,
 }
 
 pub type CachedGlyphInfo = GenericCachedGlyphInfo<Arc<Vec<u8>>>;
 pub type GlyphKeyCache = ResourceClassCache<GlyphKey, Option<CachedGlyphInfo>>;
 
-#[cfg(feature = "capture")]
+#[cfg(any(feature = "capture", feature = "replay"))]
 pub type PlainCachedGlyphInfo = GenericCachedGlyphInfo<String>;
-#[cfg(feature = "capture")]
+#[cfg(any(feature = "capture", feature = "replay"))]
 pub type PlainGlyphKeyCache = ResourceClassCache<GlyphKey, Option<PlainCachedGlyphInfo>>;
 #[cfg(feature = "capture")]
 pub type PlainGlyphCacheRef<'a> = FastHashMap<&'a FontInstance, PlainGlyphKeyCache>;
-#[cfg(feature = "capture")]
+#[cfg(feature = "replay")]
 pub type PlainGlyphCacheOwn = FastHashMap<FontInstance, PlainGlyphKeyCache>;
 
 pub struct GlyphCache {
     pub glyph_key_caches: FastHashMap<FontInstance, GlyphKeyCache>,
 }
 
 impl GlyphCache {
     pub fn new() -> Self {
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -8,33 +8,34 @@ use api::{ColorF, ColorU, DevicePoint, D
 use api::{FontInstanceFlags, FontInstancePlatformOptions};
 use api::{FontKey, FontRenderMode, FontTemplate, FontVariation};
 use api::{GlyphDimensions, GlyphKey, SubpixelDirection};
 use api::{ImageData, ImageDescriptor, ImageFormat, LayerToWorldTransform};
 use app_units::Au;
 use device::TextureFilter;
 use glyph_cache::{CachedGlyphInfo, GlyphCache};
 use gpu_cache::GpuCache;
-use internal_types::FastHashSet;
+use internal_types::{FastHashSet, ResourceCacheError};
 use platform::font::FontContext;
 use profiler::TextureCacheProfileCounters;
 use rayon::ThreadPool;
 use rayon::prelude::*;
 use std::cmp;
 use std::collections::hash_map::Entry;
 use std::hash::{Hash, Hasher};
 use std::mem;
 use std::sync::{Arc, Mutex, MutexGuard};
 use std::sync::mpsc::{channel, Receiver, Sender};
 use texture_cache::{TextureCache, TextureCacheHandle};
 #[cfg(test)]
 use thread_profiler::register_thread_with_profiler;
 
 #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
-#[cfg_attr(feature = "capture", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FontTransform {
     pub scale_x: f32,
     pub skew_x: f32,
     pub skew_y: f32,
     pub scale_y: f32,
 }
 
 // Floats don't impl Hash/Eq/Ord...
@@ -130,17 +131,18 @@ impl FontTransform {
 
 impl<'a> From<&'a LayerToWorldTransform> for FontTransform {
     fn from(xform: &'a LayerToWorldTransform) -> Self {
         FontTransform::new(xform.m11, xform.m21, xform.m12, xform.m22)
     }
 }
 
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
-#[cfg_attr(feature = "capture", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FontInstance {
     pub font_key: FontKey,
     // The font size is in *device* pixels, not logical pixels.
     // It is stored as an Au since we need sub-pixel sizes, but
     // can't store as a f32 due to use of this type as a hash key.
     // TODO(gw): Perhaps consider having LogicalAu and DeviceAu
     //           or something similar to that.
     pub size: Au,
@@ -214,17 +216,18 @@ impl FontInstance {
             (bold_offset * x_scale).max(1.0).round() as usize
         } else {
             0
         }
     }
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 #[allow(dead_code)]
 pub enum GlyphFormat {
     Alpha,
     TransformedAlpha,
     Subpixel,
     TransformedSubpixel,
     Bitmap,
     ColorBitmap,
@@ -315,38 +318,40 @@ pub struct GlyphRasterizer {
     // We defer removing fonts to the end of the frame so that:
     // - this work is done outside of the critical path,
     // - we don't have to worry about the ordering of events if a font is used on
     //   a frame where it is used (although it seems unlikely).
     fonts_to_remove: Vec<FontKey>,
 }
 
 impl GlyphRasterizer {
-    pub fn new(workers: Arc<ThreadPool>) -> Self {
+    pub fn new(workers: Arc<ThreadPool>) -> Result<Self, ResourceCacheError> {
         let (glyph_tx, glyph_rx) = channel();
 
         let num_workers = workers.current_num_threads();
         let mut contexts = Vec::with_capacity(num_workers);
 
+        let shared_context = FontContext::new()?;
+
         for _ in 0 .. num_workers {
-            contexts.push(Mutex::new(FontContext::new()));
+            contexts.push(Mutex::new(FontContext::new()?));
         }
 
-        GlyphRasterizer {
+        Ok(GlyphRasterizer {
             font_contexts: Arc::new(FontContexts {
                 worker_contexts: contexts,
-                shared_context: Mutex::new(FontContext::new()),
+                shared_context: Mutex::new(shared_context),
                 workers: Arc::clone(&workers),
             }),
             pending_glyphs: FastHashSet::default(),
             glyph_rx,
             glyph_tx,
             workers,
             fonts_to_remove: Vec::new(),
-        }
+        })
     }
 
     pub fn add_font(&mut self, font_key: FontKey, template: FontTemplate) {
         let font_contexts = Arc::clone(&self.font_contexts);
         // It's important to synchronously add the font for the shared context because
         // we use it to check that fonts have been properly added when requesting glyphs.
         font_contexts
             .lock_shared_context()
@@ -570,17 +575,17 @@ impl GlyphRasterizer {
                     for font_key in &fonts_to_remove {
                         context.delete_font(font_key);
                     }
                 }
             });
         }
     }
 
-    #[cfg(feature = "capture")]
+    #[cfg(feature = "replay")]
     pub fn reset(&mut self) {
         //TODO: any signals need to be sent to the workers?
         self.pending_glyphs.clear();
         self.fonts_to_remove.clear();
     }
 }
 
 impl FontContext {
@@ -592,17 +597,18 @@ impl FontContext {
             &FontTemplate::Native(ref native_font_handle) => {
                 self.add_native_font(&font_key, (*native_font_handle).clone());
             }
         }
     }
 }
 
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GlyphRequest {
     pub key: GlyphKey,
     pub font: FontInstance,
 }
 
 impl GlyphRequest {
     pub fn new(font: &FontInstance, key: &GlyphKey) -> Self {
         GlyphRequest {
@@ -627,17 +633,17 @@ fn rasterize_200_glyphs() {
     use std::io::Read;
 
     let worker_config = Configuration::new()
         .thread_name(|idx|{ format!("WRWorker#{}", idx) })
         .start_handler(move |idx| {
             register_thread_with_profiler(format!("WRWorker#{}", idx));
         });
     let workers = Arc::new(ThreadPool::new(worker_config).unwrap());
-    let mut glyph_rasterizer = GlyphRasterizer::new(workers);
+    let mut glyph_rasterizer = GlyphRasterizer::new(workers).unwrap();
     let mut glyph_cache = GlyphCache::new();
     let mut gpu_cache = GpuCache::new();
     let mut texture_cache = TextureCache::new(2048);
 
     let mut font_file =
         File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file");
     let mut font_data = vec![];
     font_file
--- a/gfx/webrender/src/gpu_cache.rs
+++ b/gfx/webrender/src/gpu_cache.rs
@@ -33,35 +33,38 @@ use std::{mem, u16, u32};
 use std::ops::Add;
 
 
 pub const GPU_CACHE_INITIAL_HEIGHT: u32 = 512;
 const FRAMES_BEFORE_EVICTION: usize = 10;
 const NEW_ROWS_PER_RESIZE: u32 = 512;
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct Epoch(u32);
 
 impl Epoch {
     fn next(&mut self) {
         *self = Epoch(self.0.wrapping_add(1));
     }
 }
 
 #[derive(Debug, Copy, Clone)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct CacheLocation {
     block_index: BlockIndex,
     epoch: Epoch,
 }
 
 /// A single texel in RGBAF32 texture - 16 bytes.
 #[derive(Copy, Clone, Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GpuBlockData {
     data: [f32; 4],
 }
 
 impl GpuBlockData {
     pub const EMPTY: Self = GpuBlockData { data: [0.0; 4] };
 }
 
@@ -106,32 +109,34 @@ impl From<TexelRect> for GpuBlockData {
 // implement this trait.
 pub trait ToGpuBlocks {
     // Request an arbitrary number of GPU data blocks.
     fn write_gpu_blocks(&self, GpuDataRequest);
 }
 
 // A handle to a GPU resource.
 #[derive(Debug, Copy, Clone)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GpuCacheHandle {
     location: Option<CacheLocation>,
 }
 
 impl GpuCacheHandle {
     pub fn new() -> Self {
         GpuCacheHandle { location: None }
     }
 }
 
 // A unique address in the GPU cache. These are uploaded
 // as part of the primitive instances, to allow the vertex
 // shader to fetch the specific data.
 #[derive(Copy, Debug, Clone)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GpuCacheAddress {
     pub u: u16,
     pub v: u16,
 }
 
 impl GpuCacheAddress {
     fn new(u: usize, v: usize) -> Self {
         GpuCacheAddress {
@@ -156,17 +161,18 @@ impl Add<usize> for GpuCacheAddress {
             u: self.u + other as u16,
             v: self.v,
         }
     }
 }
 
 // An entry in a free-list of blocks in the GPU cache.
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct Block {
     // The location in the cache of this block.
     address: GpuCacheAddress,
     // Index of the next free block in the list it
     // belongs to (either a free-list or the
     // occupied list).
     next: Option<BlockIndex>,
     // The current epoch (generation) of this block.
@@ -182,21 +188,23 @@ impl Block {
             next,
             last_access_time: frame_id,
             epoch: Epoch(0),
         }
     }
 }
 
 #[derive(Debug, Copy, Clone)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct BlockIndex(usize);
 
 // A row in the cache texture.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct Row {
     // The fixed size of blocks that this row supports.
     // Each row becomes a slab allocator for a fixed block size.
     // This means no dealing with fragmentation within a cache
     // row as items are allocated and freed.
     block_count_per_item: usize,
 }
 
@@ -207,43 +215,46 @@ impl Row {
         }
     }
 }
 
 // A list of update operations that can be applied on the cache
 // this frame. The list of updates is created by the render backend
 // during frame construction. It's passed to the render thread
 // where GL commands can be applied.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum GpuCacheUpdate {
     Copy {
         block_index: usize,
         block_count: usize,
         address: GpuCacheAddress,
     },
 }
 
 #[must_use]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GpuCacheUpdateList {
     /// The frame current update list was generated from.
     pub frame_id: FrameId,
     /// The current height of the texture. The render thread
     /// should resize the texture if required.
     pub height: u32,
     /// List of updates to apply.
     pub updates: Vec<GpuCacheUpdate>,
     /// A flat list of GPU blocks that are pending upload
     /// to GPU memory.
     pub blocks: Vec<GpuBlockData>,
 }
 
 // Holds the free lists of fixed size blocks. Mostly
 // just serves to work around the borrow checker.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct FreeBlockLists {
     free_list_1: Option<BlockIndex>,
     free_list_2: Option<BlockIndex>,
     free_list_4: Option<BlockIndex>,
     free_list_8: Option<BlockIndex>,
     free_list_16: Option<BlockIndex>,
     free_list_32: Option<BlockIndex>,
     free_list_64: Option<BlockIndex>,
@@ -284,17 +295,18 @@ impl FreeBlockLists {
             65...128 => (128, &mut self.free_list_128),
             129...MAX_VERTEX_TEXTURE_WIDTH => (MAX_VERTEX_TEXTURE_WIDTH, &mut self.free_list_large),
             _ => panic!("Can't allocate > MAX_VERTEX_TEXTURE_WIDTH per resource!"),
         }
     }
 }
 
 // CPU-side representation of the GPU resource cache texture.
-#[cfg_attr(feature = "capture", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct Texture {
     // Current texture height
     height: u32,
     // All blocks that have been created for this texture
     blocks: Vec<Block>,
     // Metadata about each allocated row.
     rows: Vec<Row>,
     // Free lists of available blocks for each supported
@@ -499,17 +511,18 @@ impl<'a> Drop for GpuDataRequest<'a> {
         let location = self.texture
             .push_data(Some(self.start_index), block_count, self.frame_id);
         self.handle.location = Some(location);
     }
 }
 
 
 /// The main LRU cache interface.
-#[cfg_attr(feature = "capture", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GpuCache {
     /// Current frame ID.
     frame_id: FrameId,
     /// CPU-side texture allocator.
     texture: Texture,
     /// Number of blocks requested this frame that don't
     /// need to be re-uploaded.
     saved_block_count: usize,
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -6,48 +6,52 @@ use api::{LayerToWorldTransform};
 use gpu_cache::GpuCacheAddress;
 use prim_store::EdgeAaSegmentMask;
 use render_task::RenderTaskAddress;
 
 // Contains type that must exactly match the same structures declared in GLSL.
 
 #[repr(i32)]
 #[derive(Debug, Copy, Clone)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BlurDirection {
     Horizontal = 0,
     Vertical,
 }
 
 #[derive(Debug)]
 #[repr(C)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BlurInstance {
     pub task_address: RenderTaskAddress,
     pub src_task_address: RenderTaskAddress,
     pub blur_direction: BlurDirection,
 }
 
 /// A clipping primitive drawn into the clipping mask.
 /// Could be an image or a rectangle, which defines the
 /// way `address` is treated.
 #[derive(Debug, Copy, Clone)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub struct ClipMaskInstance {
     pub render_task_address: RenderTaskAddress,
     pub scroll_node_data_index: ClipScrollNodeIndex,
     pub segment: i32,
     pub clip_data_address: GpuCacheAddress,
     pub resource_address: GpuCacheAddress,
 }
 
 // 32 bytes per instance should be enough for anyone!
 #[derive(Debug, Clone)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveInstance {
     data: [i32; 8],
 }
 
 pub struct SimplePrimitiveInstance {
     pub specific_prim_address: GpuCacheAddress,
     pub task_address: RenderTaskAddress,
     pub clip_task_address: RenderTaskAddress,
@@ -188,22 +192,24 @@ impl From<BrushInstance> for PrimitiveIn
 #[derive(Debug, Copy, Clone)]
 pub enum BrushImageKind {
     Simple = 0,     // A normal rect
     NinePatch = 1,  // A nine-patch image (stretch inside segments)
     Mirror = 2,     // A top left corner only (mirror across x/y axes)
 }
 
 #[derive(Copy, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub struct ClipScrollNodeIndex(pub u32);
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub struct ClipScrollNodeData {
     pub transform: LayerToWorldTransform,
     pub transform_kind: f32,
     pub padding: [f32; 3],
 }
 
 impl ClipScrollNodeData {
@@ -216,15 +222,16 @@ impl ClipScrollNodeData {
     }
 }
 
 #[derive(Copy, Debug, Clone, PartialEq)]
 #[repr(C)]
 pub struct ClipChainRectIndex(pub usize);
 
 #[derive(Copy, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub enum PictureType {
     Image = 1,
     TextShadow = 2,
     BoxShadow = 3,
 }
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -13,64 +13,70 @@ use profiler::BackendProfileCounters;
 use std::{usize, i32};
 use std::collections::{HashMap, HashSet};
 use std::f32;
 use std::hash::BuildHasherDefault;
 use std::path::PathBuf;
 use std::sync::Arc;
 
 #[cfg(feature = "capture")]
-use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
+use capture::{CaptureConfig, ExternalCaptureImage};
+#[cfg(feature = "replay")]
+use capture::PlainExternalImage;
 use tiling;
 
 pub type FastHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FxHasher>>;
 pub type FastHashSet<K> = HashSet<K, BuildHasherDefault<FxHasher>>;
 
 // 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
 // IDs, and the rendering thread maintains a
 // map from cache texture ID to native texture.
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CacheTextureId(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderPassIndex(pub usize);
 
 // Represents the source for a texture.
 // These are passed from throughout the
 // pipeline until they reach the rendering
 // thread, where they are resolved to a
 // native texture ID.
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum SourceTexture {
     Invalid,
     TextureCache(CacheTextureId),
     External(ExternalImageData),
     CacheA8,
     CacheRGBA8,
     // XXX Remove this once RenderTaskCacheA8 is used.
     #[allow(dead_code)]
     RenderTaskCacheA8(RenderPassIndex),
     RenderTaskCacheRGBA8(RenderPassIndex),
 }
 
 pub const ORTHO_NEAR_PLANE: f32 = -1000000.0;
 pub const ORTHO_FAR_PLANE: f32 = 1000000.0;
 
 #[derive(Copy, Clone, Debug, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTargetInfo {
     pub has_depth: bool,
 }
 
 #[derive(Debug)]
 pub enum TextureUpdateSource {
     External {
         id: ExternalImageId,
@@ -149,17 +155,17 @@ impl RenderedDocument {
     }
 }
 
 pub enum DebugOutput {
     FetchDocuments(String),
     FetchClipScrollTree(String),
     #[cfg(feature = "capture")]
     SaveCapture(CaptureConfig, Vec<ExternalCaptureImage>),
-    #[cfg(feature = "capture")]
+    #[cfg(feature = "replay")]
     LoadCapture(PathBuf, Vec<PlainExternalImage>),
 }
 
 pub enum ResultMsg {
     DebugCommand(DebugCommand),
     DebugOutput(DebugOutput),
     RefreshShader(PathBuf),
     UpdateGpuCache(GpuCacheUpdateList),
@@ -169,8 +175,21 @@ pub enum ResultMsg {
     },
     PublishDocument(
         DocumentId,
         RenderedDocument,
         TextureUpdateList,
         BackendProfileCounters,
     ),
 }
+
+#[derive(Clone, Debug)]
+pub struct ResourceCacheError {
+    description: String,
+}
+
+impl ResourceCacheError {
+    pub fn new(description: String) -> ResourceCacheError {
+        ResourceCacheError {
+            description,
+        }
+    }
+}
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -43,24 +43,24 @@ they're nestable.
 #[macro_use]
 extern crate bitflags;
 #[macro_use]
 extern crate lazy_static;
 #[macro_use]
 extern crate log;
 #[macro_use]
 extern crate thread_profiler;
-#[cfg(any(feature = "debugger", feature = "capture"))]
+#[cfg(any(feature = "debugger", feature = "capture", feature = "replay"))]
 #[macro_use]
 extern crate serde;
 
 mod batch;
 mod border;
 mod box_shadow;
-#[cfg(feature = "capture")]
+#[cfg(any(feature = "capture", feature = "replay"))]
 mod capture;
 mod clip;
 mod clip_scroll_node;
 mod clip_scroll_tree;
 mod debug_colors;
 mod debug_font_data;
 mod debug_render;
 #[cfg(feature = "debugger")]
@@ -141,17 +141,17 @@ extern crate app_units;
 extern crate bincode;
 extern crate byteorder;
 extern crate euclid;
 extern crate fxhash;
 extern crate gleam;
 extern crate num_traits;
 extern crate plane_split;
 extern crate rayon;
-#[cfg(feature = "capture")]
+#[cfg(feature = "ron")]
 extern crate ron;
 #[cfg(feature = "debugger")]
 extern crate serde_json;
 extern crate smallvec;
 extern crate time;
 #[cfg(feature = "debugger")]
 extern crate ws;
 #[cfg(feature = "debugger")]
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -2,23 +2,23 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ColorF, ClipAndScrollInfo, FilterOp, MixBlendMode};
 use api::{DeviceIntPoint, DeviceIntRect, LayerToWorldScale, PipelineId};
 use api::{BoxShadowClipMode, LayerPoint, LayerRect, LayerVector2D, Shadow};
 use api::{ClipId, PremultipliedColorF};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowCacheKey};
-use frame_builder::PrimitiveContext;
-use gpu_cache::{GpuCache, GpuDataRequest};
+use frame_builder::{FrameContext, FrameState};
+use gpu_cache::GpuDataRequest;
 use gpu_types::{BrushImageKind, PictureType};
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
 use render_task::{ClearMode, RenderTask, RenderTaskCacheKey};
-use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskTree};
-use resource_cache::{CacheItem, ResourceCache};
+use render_task::{RenderTaskCacheKeyKind, RenderTaskId};
+use resource_cache::CacheItem;
 use scene::{FilterOpHelpers, SceneProperties};
 use tiling::RenderTargetKind;
 
 /*
  A picture represents a dynamically rendered image. It consists of:
 
  * A number of primitives that are drawn onto the picture.
  * A composite operation describing how to composite this
@@ -38,17 +38,18 @@ pub enum PictureCompositeMode {
     /// Draw to intermediate surface, copy straight across. This
     /// is used for CSS isolation, and plane splitting.
     Blit,
 }
 
 /// Configure whether the content to be drawn by a picture
 /// in local space rasterization or the screen space.
 #[derive(Debug, Copy, Clone, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ContentOrigin {
     Local(LayerPoint),
     Screen(DeviceIntPoint),
 }
 
 #[derive(Debug)]
 pub enum PictureKind {
     TextShadow {
@@ -323,26 +324,24 @@ impl PicturePrimitive {
             PictureKind::BoxShadow { .. } => PictureType::BoxShadow,
             PictureKind::TextShadow { .. } => PictureType::TextShadow,
         }
     }
 
     pub fn prepare_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
-        prim_context: &PrimitiveContext,
-        render_tasks: &mut RenderTaskTree,
         prim_screen_rect: &DeviceIntRect,
         prim_local_rect: &LayerRect,
         child_tasks: Vec<RenderTaskId>,
         parent_tasks: &mut Vec<RenderTaskId>,
-        resource_cache: &mut ResourceCache,
-        gpu_cache: &mut GpuCache,
+        frame_context: &FrameContext,
+        frame_state: &mut FrameState,
     ) {
-        let content_scale = LayerToWorldScale::new(1.0) * prim_context.device_pixel_scale;
+        let content_scale = LayerToWorldScale::new(1.0) * frame_context.device_pixel_scale;
 
         match self.kind {
             PictureKind::Image {
                 ref mut secondary_render_task_id,
                 composite_mode,
                 ..
             } => {
                 let content_origin = ContentOrigin::Screen(prim_screen_rect.origin);
@@ -354,81 +353,81 @@ impl PicturePrimitive {
                             RenderTargetKind::Color,
                             content_origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             child_tasks,
                             PictureType::Image,
                         );
 
-                        let blur_std_deviation = blur_radius * prim_context.device_pixel_scale.0;
-                        let picture_task_id = render_tasks.add(picture_task);
+                        let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
+                        let picture_task_id = frame_state.render_tasks.add(picture_task);
 
                         let (blur_render_task, _) = RenderTask::new_blur(
                             blur_std_deviation,
                             picture_task_id,
-                            render_tasks,
+                            frame_state.render_tasks,
                             RenderTargetKind::Color,
                             ClearMode::Transparent,
                             PremultipliedColorF::TRANSPARENT,
                         );
 
-                        let render_task_id = render_tasks.add(blur_render_task);
+                        let render_task_id = frame_state.render_tasks.add(blur_render_task);
                         parent_tasks.push(render_task_id);
                         self.surface = Some(PictureSurface::RenderTask(render_task_id));
                     }
                     Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, color))) => {
                         let rect = (prim_local_rect.translate(&-offset) * content_scale).round().to_i32();
                         let picture_task = RenderTask::new_picture(
                             Some(rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             ContentOrigin::Screen(rect.origin),
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             child_tasks,
                             PictureType::Image,
                         );
 
-                        let blur_std_deviation = blur_radius * prim_context.device_pixel_scale.0;
-                        let picture_task_id = render_tasks.add(picture_task);
+                        let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
+                        let picture_task_id = frame_state.render_tasks.add(picture_task);
 
                         let (blur_render_task, _) = RenderTask::new_blur(
                             blur_std_deviation.round(),
                             picture_task_id,
-                            render_tasks,
+                            frame_state.render_tasks,
                             RenderTargetKind::Color,
                             ClearMode::Transparent,
                             color.premultiplied(),
                         );
 
                         *secondary_render_task_id = Some(picture_task_id);
 
-                        let render_task_id = render_tasks.add(blur_render_task);
+                        let render_task_id = frame_state.render_tasks.add(blur_render_task);
                         parent_tasks.push(render_task_id);
                         self.surface = Some(PictureSurface::RenderTask(render_task_id));
                     }
                     Some(PictureCompositeMode::MixBlend(..)) => {
                         let picture_task = RenderTask::new_picture(
                             Some(prim_screen_rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             content_origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             child_tasks,
                             PictureType::Image,
                         );
 
-                        let readback_task_id = render_tasks.add(RenderTask::new_readback(*prim_screen_rect));
+                        let readback_task_id = frame_state.render_tasks.add(RenderTask::new_readback(*prim_screen_rect));
 
                         *secondary_render_task_id = Some(readback_task_id);
                         parent_tasks.push(readback_task_id);
 
-                        let render_task_id = render_tasks.add(picture_task);
+                        let render_task_id = frame_state.render_tasks.add(picture_task);
                         parent_tasks.push(render_task_id);
                         self.surface = Some(PictureSurface::RenderTask(render_task_id));
                     }
                     Some(PictureCompositeMode::Filter(filter)) => {
                         // If this filter is not currently going to affect
                         // the picture, just collapse this picture into the
                         // current render task. This most commonly occurs
                         // when opacity == 1.0, but can also occur on other
@@ -443,34 +442,34 @@ impl PicturePrimitive {
                                 RenderTargetKind::Color,
                                 content_origin,
                                 PremultipliedColorF::TRANSPARENT,
                                 ClearMode::Transparent,
                                 child_tasks,
                                 PictureType::Image,
                             );
 
-                            let render_task_id = render_tasks.add(picture_task);
+                            let render_task_id = frame_state.render_tasks.add(picture_task);
                             parent_tasks.push(render_task_id);
                             self.surface = Some(PictureSurface::RenderTask(render_task_id));
                         }
                     }
                     Some(PictureCompositeMode::Blit) => {
                         let picture_task = RenderTask::new_picture(
                             Some(prim_screen_rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             content_origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             child_tasks,
                             PictureType::Image,
                         );
 
-                        let render_task_id = render_tasks.add(picture_task);
+                        let render_task_id = frame_state.render_tasks.add(picture_task);
                         parent_tasks.push(render_task_id);
                         self.surface = Some(PictureSurface::RenderTask(render_task_id));
                     }
                     None => {
                         parent_tasks.extend(child_tasks);
                         self.surface = None;
                     }
                 }
@@ -486,68 +485,68 @@ impl PicturePrimitive {
                 // the extra part pixel in the case of fractional sizes is correctly
                 // handled. For now, just use rounding which passes the existing
                 // Gecko tests.
                 let cache_size = (content_rect.size * content_scale).round().to_i32();
 
                 // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
                 // "the image that would be generated by applying to the shadow a
                 // Gaussian blur with a standard deviation equal to half the blur radius."
-                let device_radius = (blur_radius * prim_context.device_pixel_scale.0).round();
+                let device_radius = (blur_radius * frame_context.device_pixel_scale.0).round();
                 let blur_std_deviation = device_radius * 0.5;
 
                 let picture_task = RenderTask::new_picture(
                     Some(cache_size),
                     prim_index,
                     RenderTargetKind::Color,
                     ContentOrigin::Local(content_rect.origin),
                     color.premultiplied(),
                     ClearMode::Transparent,
                     Vec::new(),
                     PictureType::TextShadow,
                 );
 
-                let picture_task_id = render_tasks.add(picture_task);
+                let picture_task_id = frame_state.render_tasks.add(picture_task);
 
                 let (blur_render_task, _) = RenderTask::new_blur(
                     blur_std_deviation,
                     picture_task_id,
-                    render_tasks,
+                    frame_state.render_tasks,
                     RenderTargetKind::Color,
                     ClearMode::Transparent,
                     color.premultiplied(),
                 );
 
-                let render_task_id = render_tasks.add(blur_render_task);
+                let render_task_id = frame_state.render_tasks.add(blur_render_task);
                 parent_tasks.push(render_task_id);
                 self.surface = Some(PictureSurface::RenderTask(render_task_id));
             }
             PictureKind::BoxShadow { blur_radius, clip_mode, color, content_rect, cache_key, .. } => {
                 // TODO(gw): Rounding the content rect here to device pixels is not
                 // technically correct. Ideally we should ceil() here, and ensure that
                 // the extra part pixel in the case of fractional sizes is correctly
                 // handled. For now, just use rounding which passes the existing
                 // Gecko tests.
                 let cache_size = (content_rect.size * content_scale).round().to_i32();
 
                 // Request the texture cache item for this box-shadow key. If it
                 // doesn't exist in the cache, the closure is invoked to build
                 // a render task chain to draw the cacheable result.
-                let cache_item = resource_cache.request_render_task(
+                let cache_item = frame_state.resource_cache.request_render_task(
                     RenderTaskCacheKey {
                         size: cache_size,
                         kind: RenderTaskCacheKeyKind::BoxShadow(cache_key),
                     },
-                    gpu_cache,
-                    render_tasks,
+                    frame_state.gpu_cache,
+                    frame_state.render_tasks,
                     |render_tasks| {
                         // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
                         // "the image that would be generated by applying to the shadow a
                         // Gaussian blur with a standard deviation equal to half the blur radius."
-                        let device_radius = (blur_radius * prim_context.device_pixel_scale.0).round();
+                        let device_radius = (blur_radius * frame_context.device_pixel_scale.0).round();
                         let blur_std_deviation = device_radius * 0.5;
 
                         let blur_clear_mode = match clip_mode {
                             BoxShadowClipMode::Outset => {
                                 ClearMode::One
                             }
                             BoxShadowClipMode::Inset => {
                                 ClearMode::Zero
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -3,32 +3,32 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ColorU, FontKey, FontRenderMode, GlyphDimensions};
 use api::{FontInstanceFlags, FontVariation, NativeFontHandle};
 use api::{GlyphKey, SubpixelDirection};
 use app_units::Au;
 use core_foundation::array::{CFArray, CFArrayRef};
 use core_foundation::base::TCFType;
-use core_foundation::dictionary::{CFDictionary, CFDictionaryRef};
+use core_foundation::dictionary::CFDictionary;
 use core_foundation::number::{CFNumber, CFNumberRef};
 use core_foundation::string::{CFString, CFStringRef};
 use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGImageAlphaPremultipliedFirst};
 use core_graphics::base::kCGBitmapByteOrder32Little;
 use core_graphics::color_space::CGColorSpace;
 use core_graphics::context::{CGContext, CGTextDrawingMode};
 use core_graphics::data_provider::CGDataProvider;
 use core_graphics::font::{CGFont, CGGlyph};
 use core_graphics::geometry::{CGAffineTransform, CGPoint, CGRect, CGSize};
 use core_text;
 use core_text::font::{CTFont, CTFontRef};
 use core_text::font_descriptor::{kCTFontDefaultOrientation, kCTFontColorGlyphsTrait};
 use gamma_lut::{ColorLut, GammaLut};
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphFormat, RasterizedGlyph};
-use internal_types::FastHashMap;
+use internal_types::{FastHashMap, ResourceCacheError};
 use std::collections::hash_map::Entry;
 use std::sync::Arc;
 
 pub struct FontContext {
     cg_fonts: FastHashMap<FontKey, CGFont>,
     ct_fonts: FastHashMap<(FontKey, Au, Vec<FontVariation>), CTFont>,
     gamma_lut: GammaLut,
 }
@@ -166,27 +166,26 @@ fn new_ct_font_with_variations(cg_font: 
         let ct_font = core_text::font::new_from_CGFont(cg_font, size);
         if variations.is_empty() {
             return ct_font;
         }
         let axes_ref = CTFontCopyVariationAxes(ct_font.as_concrete_TypeRef());
         if axes_ref.is_null() {
             return ct_font;
         }
-        let axes: CFArray = TCFType::wrap_under_create_rule(axes_ref);
+        let axes: CFArray<CFDictionary> = TCFType::wrap_under_create_rule(axes_ref);
         let mut vals: Vec<(CFString, CFNumber)> = Vec::with_capacity(variations.len() as usize);
-        for axis_ptr in axes.iter() {
-            let axis: CFDictionary = TCFType::wrap_under_get_rule(axis_ptr as CFDictionaryRef);
-            if !axis.instance_of::<CFDictionaryRef, CFDictionary>() {
+        for axis in axes.iter() {
+            if !axis.instance_of::<CFDictionary>() {
                 return ct_font;
             }
             let tag_val = match axis.find(kCTFontVariationAxisIdentifierKey as *const _) {
                 Some(tag_ptr) => {
                     let tag: CFNumber = TCFType::wrap_under_get_rule(tag_ptr as CFNumberRef);
-                    if !tag.instance_of::<CFNumberRef, CFNumber>() {
+                    if !tag.instance_of::<CFNumber>() {
                         return ct_font;
                     }
                     match tag.to_i64() {
                         Some(val) => val,
                         None => return ct_font,
                     }
                 }
                 None => return ct_font,
@@ -195,50 +194,50 @@ fn new_ct_font_with_variations(cg_font: 
                 Some(variation) => variation.value as f64,
                 None => continue,
             };
 
             let name: CFString = match axis.find(kCTFontVariationAxisNameKey as *const _) {
                 Some(name_ptr) => TCFType::wrap_under_get_rule(name_ptr as CFStringRef),
                 None => return ct_font,
             };
-            if !name.instance_of::<CFStringRef, CFString>() {
+            if !name.instance_of::<CFString>() {
                 return ct_font;
             }
 
             let min_val = match axis.find(kCTFontVariationAxisMinimumValueKey as *const _) {
                 Some(min_ptr) => {
                     let min: CFNumber = TCFType::wrap_under_get_rule(min_ptr as CFNumberRef);
-                    if !min.instance_of::<CFNumberRef, CFNumber>() {
+                    if !min.instance_of::<CFNumber>() {
                         return ct_font;
                     }
                     match min.to_f64() {
                         Some(val) => val,
                         None => return ct_font,
                     }
                 }
                 None => return ct_font,
             };
             let max_val = match axis.find(kCTFontVariationAxisMaximumValueKey as *const _) {
                 Some(max_ptr) => {
                     let max: CFNumber = TCFType::wrap_under_get_rule(max_ptr as CFNumberRef);
-                    if !max.instance_of::<CFNumberRef, CFNumber>() {
+                    if !max.instance_of::<CFNumber>() {
                         return ct_font;
                     }
                     match max.to_f64() {
                         Some(val) => val,
                         None => return ct_font,
                     }
                 }
                 None => return ct_font,
             };
             let def_val = match axis.find(kCTFontVariationAxisDefaultValueKey as *const _) {
                 Some(def_ptr) => {
                     let def: CFNumber = TCFType::wrap_under_get_rule(def_ptr as CFNumberRef);
-                    if !def.instance_of::<CFNumberRef, CFNumber>() {
+                    if !def.instance_of::<CFNumber>() {
                         return ct_font;
                     }
                     match def.to_f64() {
                         Some(val) => val,
                         None => return ct_font,
                     }
                 }
                 None => return ct_font,
@@ -262,28 +261,28 @@ fn is_bitmap_font(ct_font: &CTFont) -> b
     let traits = ct_font.symbolic_traits();
     (traits & kCTFontColorGlyphsTrait) != 0
 }
 
 // Skew factor matching Gecko/CG.
 const OBLIQUE_SKEW_FACTOR: f32 = 0.25;
 
 impl FontContext {
-    pub fn new() -> FontContext {
+    pub fn new() -> Result<FontContext, ResourceCacheError> {
         debug!("Test for subpixel AA support: {}", supports_subpixel_aa());
 
         // Force CG to use sRGB color space to gamma correct.
         let contrast = 0.0;
         let gamma = 0.0;
 
-        FontContext {
+        Ok(FontContext {
             cg_fonts: FastHashMap::default(),
             ct_fonts: FastHashMap::default(),
             gamma_lut: GammaLut::new(contrast, gamma, gamma),
-        }
+        })
     }
 
     pub fn has_font(&self, font_key: &FontKey) -> bool {
         self.cg_fonts.contains_key(font_key)
     }
 
     pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: Arc<Vec<u8>>, index: u32) {
         if self.cg_fonts.contains_key(font_key) {
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -13,17 +13,17 @@ use freetype::freetype::{FT_GlyphSlot, F
 use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph};
 use freetype::freetype::{FT_Library, FT_Outline_Get_CBox, FT_Set_Char_Size, FT_Select_Size};
 use freetype::freetype::{FT_Fixed, FT_Matrix, FT_Set_Transform};
 use freetype::freetype::{FT_LOAD_COLOR, FT_LOAD_DEFAULT, FT_LOAD_FORCE_AUTOHINT};
 use freetype::freetype::{FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH, FT_LOAD_NO_AUTOHINT};
 use freetype::freetype::{FT_LOAD_NO_BITMAP, FT_LOAD_NO_HINTING, FT_LOAD_VERTICAL_LAYOUT};
 use freetype::freetype::{FT_FACE_FLAG_SCALABLE, FT_FACE_FLAG_FIXED_SIZES};
 use glyph_rasterizer::{FontInstance, GlyphFormat, RasterizedGlyph};
-use internal_types::FastHashMap;
+use internal_types::{FastHashMap, ResourceCacheError};
 use std::{cmp, mem, ptr, slice};
 use std::cmp::max;
 use std::ffi::CString;
 use std::sync::Arc;
 
 // These constants are not present in the freetype
 // bindings due to bindgen not handling the way
 // the macros are defined.
@@ -133,39 +133,41 @@ fn flip_bitmap_y(bitmap: &mut [u8], widt
         let high_row = (height - 1 - y) * width;
         for x in 0 .. width {
             pixels.swap(low_row + x, high_row + x);
         }
     }
 }
 
 impl FontContext {
-    pub fn new() -> FontContext {
+    pub fn new() -> Result<FontContext, ResourceCacheError> {
         let mut lib: FT_Library = ptr::null_mut();
 
         // Using an LCD filter may add one full pixel to each side if support is built in.
         // As of FreeType 2.8.1, an LCD filter is always used regardless of settings
         // if support for the patent-encumbered LCD filter algorithms is not built in.
         // Thus, the only reasonable way to guess padding is to unconditonally add it if
         // subpixel AA is used.
         let lcd_extra_pixels = 1;
 
-        unsafe {
-            let result = FT_Init_FreeType(&mut lib);
-            assert!(
-                result.succeeded(),
-                "Unable to initialize FreeType library {:?}",
-                result
-            );
-        }
+        let result = unsafe {
+            FT_Init_FreeType(&mut lib)
+        };
 
-        FontContext {
-            lib,
-            faces: FastHashMap::default(),
-            lcd_extra_pixels,
+        if result.succeeded() {
+            Ok(FontContext {
+                lib,
+                faces: FastHashMap::default(),
+                lcd_extra_pixels,
+            })
+        } else {
+            // TODO(gw): Provide detailed error values.
+            Err(ResourceCacheError::new(
+                format!("Failed to initialize FreeType - {}", result.0)
+            ))
         }
     }
 
     pub fn has_font(&self, font_key: &FontKey) -> bool {
         self.faces.contains_key(font_key)
     }
 
     pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: Arc<Vec<u8>>, index: u32) {
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{FontInstanceFlags, FontKey, FontRenderMode};
 use api::{ColorU, GlyphDimensions, GlyphKey, SubpixelDirection};
 use dwrote;
 use gamma_lut::{ColorLut, GammaLut};
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphFormat, RasterizedGlyph};
-use internal_types::FastHashMap;
+use internal_types::{FastHashMap, ResourceCacheError};
 use std::collections::hash_map::Entry;
 use std::sync::Arc;
 
 lazy_static! {
     static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor {
         family_name: "Arial".to_owned(),
         weight: dwrote::FontWeight::Regular,
         stretch: dwrote::FontStretch::Normal,
@@ -85,29 +85,29 @@ fn is_bitmap_font(font: &FontInstance) -
     font.render_mode != FontRenderMode::Mono &&
         font.flags.contains(FontInstanceFlags::EMBEDDED_BITMAPS)
 }
 
 // Skew factor matching Gecko/DWrite.
 const OBLIQUE_SKEW_FACTOR: f32 = 0.3;
 
 impl FontContext {
-    pub fn new() -> FontContext {
+    pub fn new() -> Result<FontContext, ResourceCacheError> {
         // These are the default values we use in Gecko.
         // We use a gamma value of 2.3 for gdi fonts
         // TODO: Fetch this data from Gecko itself.
         let contrast = 1.0;
         let gamma = 1.8;
         let gdi_gamma = 2.3;
-        FontContext {
+        Ok(FontContext {
             fonts: FastHashMap::default(),
             simulations: FastHashMap::default(),
             gamma_lut: GammaLut::new(contrast, gamma, gamma),
             gdi_gamma_lut: GammaLut::new(contrast, gdi_gamma, gdi_gamma),
-        }
+        })
     }
 
     pub fn has_font(&self, font_key: &FontKey) -> bool {
         self.fonts.contains_key(font_key)
     }
 
     pub fn add_raw_font(&mut self, font_key: &FontKey, data: Arc<Vec<u8>>, index: u32) {
         if self.fonts.contains_key(font_key) {
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -5,32 +5,29 @@
 use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipAndScrollInfo, ClipId, ClipMode};
 use api::{ColorF, ColorU, DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch};
 use api::{ComplexClipRegion, ExtendMode, FontRenderMode};
 use api::{GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag};
 use api::{LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LayerVector2D, LineOrientation};
 use api::{LineStyle, PipelineId, PremultipliedColorF, TileOffset};
 use api::{WorldToLayerTransform, YuvColorSpace, YuvFormat};
 use border::{BorderCornerInstance, BorderEdgeKind};
-use clip_scroll_tree::{CoordinateSystemId, ClipScrollTree};
+use clip_scroll_tree::{CoordinateSystemId};
 use clip_scroll_node::ClipScrollNode;
-use clip::{ClipSource, ClipSourcesHandle, ClipStore};
-use frame_builder::PrimitiveContext;
+use clip::{ClipSource, ClipSourcesHandle};
+use frame_builder::{FrameContext, FrameState, PrimitiveRunContext};
 use glyph_rasterizer::{FontInstance, FontTransform};
-use internal_types::{FastHashMap};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
-use gpu_types::{ClipChainRectIndex, ClipScrollNodeData};
+use gpu_types::{ClipChainRectIndex};
 use picture::{PictureKind, PicturePrimitive};
-use profiler::FrameProfileCounters;
 use render_task::{BlitSource, ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipWorkItem};
-use render_task::{RenderTask, RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskTree};
+use render_task::{RenderTask, RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{CacheItem, ImageProperties, ResourceCache};
-use scene::{ScenePipeline, SceneProperties};
 use segment::SegmentBuilder;
 use std::{mem, usize};
 use std::rc::Rc;
 use util::{MatrixHelpers, calculate_screen_bounding_rect, pack_as_float};
 use util::recycle_vec;
 
 
 const MIN_BRUSH_SPLIT_AREA: f32 = 128.0 * 128.0;
@@ -91,27 +88,29 @@ pub struct PrimitiveRunLocalRect {
 /// For external images, it's not possible to know the
 /// UV coords of the image (or the image data itself)
 /// until the render thread receives the frame and issues
 /// callbacks to the client application. For external
 /// images that are visible, a DeferredResolve is created
 /// that is stored in the frame. This allows the render
 /// thread to iterate this list and update any changed
 /// texture data and update the UV rect.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct DeferredResolve {
     pub address: GpuCacheAddress,
     pub image_properties: ImageProperties,
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 pub struct SpecificPrimitiveIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 pub enum PrimitiveKind {
     TextRun,
     Image,
     YuvImage,
     Border,
@@ -318,17 +317,18 @@ impl BrushPrimitive {
             }
         }
     }
 }
 
 // Key that identifies a unique (partial) image that is being
 // stored in the render task cache.
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ImageCacheKey {
     // TODO(gw): Consider introducing a struct that collectively
     //           identifies an image in the resource cache
     //           uniquely. We pass this around to a few places.
     pub image_key: ImageKey,
     pub image_rendering: ImageRendering,
     pub tile_offset: Option<TileOffset>,
     pub texel_rect: Option<DeviceIntRect>,
@@ -1139,63 +1139,62 @@ impl PrimitiveStore {
 
     pub fn prim_count(&self) -> usize {
         self.cpu_metadata.len()
     }
 
     fn prepare_prim_for_render_inner(
         &mut self,
         prim_index: PrimitiveIndex,
-        prim_context: &PrimitiveContext,
-        resource_cache: &mut ResourceCache,
-        gpu_cache: &mut GpuCache,
-        render_tasks: &mut RenderTaskTree,
+        prim_run_context: &PrimitiveRunContext,
         child_tasks: Vec<RenderTaskId>,
         parent_tasks: &mut Vec<RenderTaskId>,
         pic_index: SpecificPrimitiveIndex,
+        frame_context: &FrameContext,
+        frame_state: &mut FrameState,
     ) {
         let metadata = &mut self.cpu_metadata[prim_index.0];
         match metadata.prim_kind {
             PrimitiveKind::Border => {}
             PrimitiveKind::Picture => {
                 self.cpu_pictures[metadata.cpu_prim_index.0]
                     .prepare_for_render(
                         prim_index,
-                        prim_context,
-                        render_tasks,
                         metadata.screen_rect.as_ref().expect("bug: trying to draw an off-screen picture!?"),
                         &metadata.local_rect,
                         child_tasks,
                         parent_tasks,
-                        resource_cache,
-                        gpu_cache,
+                        frame_context,
+                        frame_state,
                     );
             }
             PrimitiveKind::TextRun => {
                 let pic = &self.cpu_pictures[pic_index.0];
                 let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
                 // The transform only makes sense for screen space rasterization
                 let transform = match pic.kind {
                     PictureKind::BoxShadow { .. } => None,
                     PictureKind::TextShadow { .. } => None,
                     PictureKind::Image { .. } => {
-                        Some(&prim_context.scroll_node.world_content_transform)
+                        Some(&prim_run_context.scroll_node.world_content_transform)
                     },
                 };
                 text.prepare_for_render(
-                    resource_cache,
-                    prim_context.device_pixel_scale,
+                    frame_state.resource_cache,
+                    frame_context.device_pixel_scale,
                     transform,
-                    prim_context.display_list,
-                    gpu_cache,
+                    prim_run_context.display_list,
+                    frame_state.gpu_cache,
                 );
             }
             PrimitiveKind::Image => {
                 let image_cpu = &mut self.cpu_images[metadata.cpu_prim_index.0];
-                let image_properties = resource_cache.get_image_properties(image_cpu.key.image_key);
+                let image_properties = frame_state
+                    .resource_cache
+                    .get_image_properties(image_cpu.key.image_key);
 
                 // TODO(gw): Add image.rs and move this code out to a separate
                 //           source file as it gets more complicated, and we
                 //           start pre-rendering images for other reasons.
 
                 if let Some(image_properties) = image_properties {
                     // See if this image has been updated since we last hit this code path.
                     // If so, we need to (at least) update the opacity, and also rebuild
@@ -1222,38 +1221,38 @@ impl PrimitiveStore {
                                 // Simple image - just use a normal texture cache entry.
                                 ImageSource::Default
                             }
                         };
                     }
 
                     // TODO(gw): Don't actually need this in cached source mode if
                     //           the cache item is still valid...
-                    resource_cache.request_image(
+                    frame_state.resource_cache.request_image(
                         image_cpu.key.image_key,
                         image_cpu.key.image_rendering,
                         image_cpu.key.tile_offset,
-                        gpu_cache,
+                        frame_state.gpu_cache,
                     );
 
                     // Every frame, for cached items, we need to request the render
                     // task cache item. The closure will be invoked on the first
                     // time through, and any time the render task output has been
                     // evicted from the texture cache.
                     if let ImageSource::Cache { size, ref mut item } = image_cpu.source {
                         let key = image_cpu.key;
 
                         // Request a pre-rendered image task.
-                        *item = resource_cache.request_render_task(
+                        *item = frame_state.resource_cache.request_render_task(
                             RenderTaskCacheKey {
                                 size,
                                 kind: RenderTaskCacheKeyKind::Image(key),
                             },
-                            gpu_cache,
-                            render_tasks,
+                            frame_state.gpu_cache,
+                            frame_state.render_tasks,
                             |render_tasks| {
                                 // Create a task to blit from the texture cache to
                                 // a normal transient render task surface. This will
                                 // copy only the sub-rect, if specified.
                                 let cache_to_target_task = RenderTask::new_blit(
                                     size,
                                     BlitSource::Image {
                                         key,
@@ -1284,32 +1283,32 @@ impl PrimitiveStore {
                 }
             }
             PrimitiveKind::YuvImage => {
                 let image_cpu = &mut self.cpu_yuv_images[metadata.cpu_prim_index.0];
 
                 let channel_num = image_cpu.format.get_plane_num();
                 debug_assert!(channel_num <= 3);
                 for channel in 0 .. channel_num {
-                    resource_cache.request_image(
+                    frame_state.resource_cache.request_image(
                         image_cpu.yuv_key[channel],
                         image_cpu.image_rendering,
                         None,
-                        gpu_cache,
+                        frame_state.gpu_cache,
                     );
                 }
             }
             PrimitiveKind::Brush |
             PrimitiveKind::AlignedGradient |
             PrimitiveKind::AngleGradient |
             PrimitiveKind::RadialGradient => {}
         }
 
         // Mark this GPU resource as required for this frame.
-        if let Some(mut request) = gpu_cache.request(&mut metadata.gpu_location) {
+        if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) {
             // has to match VECS_PER_BRUSH_PRIM
             request.push(metadata.local_rect);
             request.push(metadata.local_clip_rect);
 
             match metadata.prim_kind {
                 PrimitiveKind::Border => {
                     let border = &self.cpu_borders[metadata.cpu_prim_index.0];
                     border.write_gpu_blocks(request);
@@ -1319,25 +1318,25 @@ impl PrimitiveStore {
                     image.write_gpu_blocks(request);
                 }
                 PrimitiveKind::YuvImage => {
                     let yuv_image = &self.cpu_yuv_images[metadata.cpu_prim_index.0];
                     yuv_image.write_gpu_blocks(request);
                 }
                 PrimitiveKind::AlignedGradient => {
                     let gradient = &self.cpu_gradients[metadata.cpu_prim_index.0];
-                    metadata.opacity = gradient.build_gpu_blocks_for_aligned(prim_context.display_list, request);
+                    metadata.opacity = gradient.build_gpu_blocks_for_aligned(prim_run_context.display_list, request);
                 }
                 PrimitiveKind::AngleGradient => {
                     let gradient = &self.cpu_gradients[metadata.cpu_prim_index.0];
-                    gradient.build_gpu_blocks_for_angle_radial(prim_context.display_list, request);
+                    gradient.build_gpu_blocks_for_angle_radial(prim_run_context.display_list, request);
                 }
                 PrimitiveKind::RadialGradient => {
                     let gradient = &self.cpu_radial_gradients[metadata.cpu_prim_index.0];
-                    gradient.build_gpu_blocks_for_angle_radial(prim_context.display_list, request);
+                    gradient.build_gpu_blocks_for_angle_radial(prim_run_context.display_list, request);
                 }
                 PrimitiveKind::TextRun => {
                     let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
                     text.write_gpu_blocks(&mut request);
                 }
                 PrimitiveKind::Picture => {
                     let pic = &self.cpu_pictures[metadata.cpu_prim_index.0];
                     pic.write_gpu_blocks(&mut request);
@@ -1373,21 +1372,21 @@ impl PrimitiveStore {
                 }
             }
         }
     }
 
     fn write_brush_segment_description(
         brush: &mut BrushPrimitive,
         metadata: &PrimitiveMetadata,
-        prim_context: &PrimitiveContext,
-        clip_store: &mut ClipStore,
-        node_data: &[ClipScrollNodeData],
+        prim_run_context: &PrimitiveRunContext,
         clips: &Vec<ClipWorkItem>,
         has_clips_from_other_coordinate_systems: bool,
+        frame_context: &FrameContext,
+        frame_state: &mut FrameState,
     ) {
         match brush.segment_desc {
             Some(ref segment_desc) => {
                 // If we already have a segment descriptor, only run through the
                 // clips list if we haven't already determined the mask kind.
                 if segment_desc.clip_mask_kind != BrushClipMaskKind::Unknown {
                     return;
                 }
@@ -1414,21 +1413,21 @@ impl PrimitiveStore {
         // need to apply a clip mask for the entire primitive.
         let mut clip_mask_kind = match has_clips_from_other_coordinate_systems {
             true => BrushClipMaskKind::Global,
             false => BrushClipMaskKind::Individual,
         };
 
         // Segment the primitive on all the local-space clip sources that we can.
         for clip_item in clips {
-            if clip_item.coordinate_system_id != prim_context.scroll_node.coordinate_system_id {
+            if clip_item.coordinate_system_id != prim_run_context.scroll_node.coordinate_system_id {
                 continue;
             }
 
-            let local_clips = clip_store.get_opt(&clip_item.clip_sources).expect("bug");
+            let local_clips = frame_state.clip_store.get_opt(&clip_item.clip_sources).expect("bug");
             for &(ref clip, _) in &local_clips.clips {
                 let (local_clip_rect, radius, mode) = match *clip {
                     ClipSource::RoundedRectangle(rect, radii, clip_mode) => {
                         (rect, Some(radii), clip_mode)
                     }
                     ClipSource::Rectangle(rect) => {
                         (rect, None, ClipMode::Clip)
                     }
@@ -1441,21 +1440,22 @@ impl PrimitiveStore {
                         continue;
                     }
                 };
 
                 // If the scroll node transforms are different between the clip
                 // node and the primitive, we need to get the clip rect in the
                 // local space of the primitive, in order to generate correct
                 // local segments.
-                let local_clip_rect = if clip_item.scroll_node_data_index == prim_context.scroll_node.node_data_index {
+                let local_clip_rect = if clip_item.scroll_node_data_index == prim_run_context.scroll_node.node_data_index {
                     local_clip_rect
                 } else {
-                    let clip_transform_data = &node_data[clip_item.scroll_node_data_index.0 as usize];
-                    let prim_transform = &prim_context.scroll_node.world_content_transform;
+                    let clip_transform_data = &frame_context
+                        .node_data[clip_item.scroll_node_data_index.0 as usize];
+                    let prim_transform = &prim_run_context.scroll_node.world_content_transform;
 
                     let relative_transform = prim_transform
                         .inverse()
                         .unwrap_or(WorldToLayerTransform::identity())
                         .pre_mul(&clip_transform_data.transform);
 
                     relative_transform.transform_rect(&local_clip_rect)
                 };
@@ -1491,25 +1491,24 @@ impl PrimitiveStore {
                     clip_mask_kind,
                 });
             }
         }
     }
 
     fn update_clip_task_for_brush(
         &mut self,
-        prim_context: &PrimitiveContext,
+        prim_run_context: &PrimitiveRunContext,
         prim_index: PrimitiveIndex,
-        render_tasks: &mut RenderTaskTree,
-        clip_store: &mut ClipStore,
         tasks: &mut Vec<RenderTaskId>,
-        node_data: &[ClipScrollNodeData],
         clips: &Vec<ClipWorkItem>,
         combined_outer_rect: &DeviceIntRect,
         has_clips_from_other_coordinate_systems: bool,
+        frame_context: &FrameContext,
+        frame_state: &mut FrameState,
     ) -> bool {
         let metadata = &self.cpu_metadata[prim_index.0];
         let brush = match metadata.prim_kind {
             PrimitiveKind::Brush => {
                 &mut self.cpu_brushes[metadata.cpu_prim_index.0]
             }
             PrimitiveKind::Picture => {
                 &mut self.cpu_pictures[metadata.cpu_prim_index.0].brush
@@ -1517,106 +1516,105 @@ impl PrimitiveStore {
             _ => {
                 return false;
             }
         };
 
         PrimitiveStore::write_brush_segment_description(
             brush,
             metadata,
-            prim_context,
-            clip_store,
-            node_data,
+            prim_run_context,
             clips,
             has_clips_from_other_coordinate_systems,
+            frame_context,
+            frame_state,
         );
 
         let segment_desc = match brush.segment_desc {
             Some(ref mut description) => description,
             None => return false,
         };
         let clip_mask_kind = segment_desc.clip_mask_kind;
 
         for segment in &mut segment_desc.segments {
             if !segment.may_need_clip_mask && clip_mask_kind != BrushClipMaskKind::Global {
                 segment.clip_task_id = None;
                 continue;
             }
 
             let segment_screen_rect = calculate_screen_bounding_rect(
-                &prim_context.scroll_node.world_content_transform,
+                &prim_run_context.scroll_node.world_content_transform,
                 &segment.local_rect,
-                prim_context.device_pixel_scale,
+                frame_context.device_pixel_scale,
             );
 
             let intersected_rect = combined_outer_rect.intersection(&segment_screen_rect);
             segment.clip_task_id = intersected_rect.map(|bounds| {
                 let clip_task = RenderTask::new_mask(
                     bounds,
                     clips.clone(),
-                    prim_context.scroll_node.coordinate_system_id,
+                    prim_run_context.scroll_node.coordinate_system_id,
                 );
 
-                let clip_task_id = render_tasks.add(clip_task);
+                let clip_task_id = frame_state.render_tasks.add(clip_task);
                 tasks.push(clip_task_id);
 
                 clip_task_id
             })
         }
 
         true
     }
 
     fn update_clip_task(
         &mut self,
         prim_index: PrimitiveIndex,
-        prim_context: &PrimitiveContext,
+        prim_run_context: &PrimitiveRunContext,
         prim_screen_rect: &DeviceIntRect,
-        screen_rect: &DeviceIntRect,
-        resource_cache: &mut ResourceCache,
-        gpu_cache: &mut GpuCache,
-        render_tasks: &mut RenderTaskTree,
-        clip_store: &mut ClipStore,
         tasks: &mut Vec<RenderTaskId>,
-        node_data: &[ClipScrollNodeData],
+        frame_context: &FrameContext,
+        frame_state: &mut FrameState,
     ) -> bool {
         self.cpu_metadata[prim_index.0].clip_task_id = None;
 
-        let prim_screen_rect = match prim_screen_rect.intersection(screen_rect) {
+        let prim_screen_rect = match prim_screen_rect.intersection(&frame_context.screen_rect) {
             Some(rect) => rect,
             None => {
                 self.cpu_metadata[prim_index.0].screen_rect = None;
                 return false;
             }
         };
 
-        let mut combined_outer_rect = match prim_context.clip_chain {
+        let mut combined_outer_rect = match prim_run_context.clip_chain {
             Some(ref chain) => prim_screen_rect.intersection(&chain.combined_outer_screen_rect),
             None => Some(prim_screen_rect),
         };
 
-        let clip_chain = prim_context.clip_chain.map_or(None, |x| x.nodes.clone());
+        let clip_chain = prim_run_context.clip_chain.map_or(None, |x| x.nodes.clone());
 
-        let prim_coordinate_system_id = prim_context.scroll_node.coordinate_system_id;
-        let transform = &prim_context.scroll_node.world_content_transform;
+        let prim_coordinate_system_id = prim_run_context.scroll_node.coordinate_system_id;
+        let transform = &prim_run_context.scroll_node.world_content_transform;
         let extra_clip =  {
             let metadata = &self.cpu_metadata[prim_index.0];
-            let prim_clips = clip_store.get_mut(&metadata.clip_sources);
+            let prim_clips = frame_state.clip_store.get_mut(&metadata.clip_sources);
             if prim_clips.has_clips() {
-                prim_clips.update(gpu_cache, resource_cache);
+                prim_clips.update(
+                    frame_state.gpu_cache,
+                    frame_state.resource_cache,
+                );
                 let (screen_inner_rect, screen_outer_rect) =
-                    prim_clips.get_screen_bounds(transform, prim_context.device_pixel_scale);
+                    prim_clips.get_screen_bounds(transform, frame_context.device_pixel_scale);
 
                 if let Some(outer) = screen_outer_rect {
                     combined_outer_rect = combined_outer_rect.and_then(|r| r.intersection(&outer));
                 }
 
                 Some(Rc::new(ClipChainNode {
                     work_item: ClipWorkItem {
-                        scroll_node_data_index: prim_context.scroll_node.node_data_index,
+                        scroll_node_data_index: prim_run_context.scroll_node.node_data_index,
                         clip_sources: metadata.clip_sources.weak(),
                         coordinate_system_id: prim_coordinate_system_id,
                     },
                     // The local_clip_rect a property of ClipChain nodes that are ClipScrollNodes.
                     // It's used to calculate a local clipping rectangle before we reach this
                     // point, so we can set it to zero here. It should be unused from this point
                     // on.
                     local_clip_rect: LayerRect::zero(),
@@ -1634,23 +1632,23 @@ impl PrimitiveStore {
             Some(rect) if !rect.is_empty() => rect,
             _ => {
                 self.cpu_metadata[prim_index.0].screen_rect = None;
                 return false;
             }
         };
 
         let mut has_clips_from_other_coordinate_systems = false;
-        let mut combined_inner_rect = *screen_rect;
+        let mut combined_inner_rect = frame_context.screen_rect;
         let clips = convert_clip_chain_to_clip_vector(
             clip_chain,
             extra_clip,
             &combined_outer_rect,
             &mut combined_inner_rect,
-            prim_context.scroll_node.coordinate_system_id,
+            prim_run_context.scroll_node.coordinate_system_id,
             &mut has_clips_from_other_coordinate_systems
         );
 
         // This can happen if we had no clips or if all the clips were optimized away. In
         // some cases we still need to create a clip mask in order to create a rectangular
         // clip in screen space coordinates.
         if clips.is_empty() {
             // If we don't have any clips from other coordinate systems, the local clip
@@ -1670,80 +1668,69 @@ impl PrimitiveStore {
         }
 
         if combined_inner_rect.contains_rect(&prim_screen_rect) {
            return true;
         }
 
         // First try to  render this primitive's mask using optimized brush rendering.
         if self.update_clip_task_for_brush(
-            prim_context,
+            prim_run_context,
             prim_index,
-            render_tasks,
-            clip_store,
             tasks,
-            node_data,
             &clips,
             &combined_outer_rect,
             has_clips_from_other_coordinate_systems,
+            frame_context,
+            frame_state,
         ) {
             return true;
         }
 
         let clip_task = RenderTask::new_mask(
             combined_outer_rect,
             clips,
             prim_coordinate_system_id,
         );
 
-        let clip_task_id = render_tasks.add(clip_task);
+        let clip_task_id = frame_state.render_tasks.add(clip_task);
         self.cpu_metadata[prim_index.0].clip_task_id = Some(clip_task_id);
         tasks.push(clip_task_id);
 
         true
     }
 
     pub fn prepare_prim_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
-        prim_context: &PrimitiveContext,
-        resource_cache: &mut ResourceCache,
-        gpu_cache: &mut GpuCache,
-        render_tasks: &mut RenderTaskTree,
-        clip_store: &mut ClipStore,
-        clip_scroll_tree: &ClipScrollTree,
-        pipelines: &FastHashMap<PipelineId, ScenePipeline>,
+        prim_run_context: &PrimitiveRunContext,
         perform_culling: bool,
         parent_tasks: &mut Vec<RenderTaskId>,
-        scene_properties: &SceneProperties,
-        profile_counters: &mut FrameProfileCounters,
         pic_index: SpecificPrimitiveIndex,
-        screen_rect: &DeviceIntRect,
-        clip_chain_rect_index: ClipChainRectIndex,
-        node_data: &[ClipScrollNodeData],
-        local_rects: &mut Vec<LayerRect>,
+        frame_context: &FrameContext,
+        frame_state: &mut FrameState,
     ) -> Option<LayerRect> {
         // Reset the visibility of this primitive.
         // Do some basic checks first, that can early out
         // without even knowing the local rect.
         let (cpu_prim_index, dependencies, cull_children, may_need_clip_mask) = {
             let metadata = &mut self.cpu_metadata[prim_index.0];
             metadata.screen_rect = None;
 
             if perform_culling &&
                !metadata.is_backface_visible &&
-               prim_context.scroll_node.world_content_transform.is_backface_visible() {
+               prim_run_context.scroll_node.world_content_transform.is_backface_visible() {
                 return None;
             }
 
             let (dependencies, cull_children, may_need_clip_mask) = match metadata.prim_kind {
                 PrimitiveKind::Picture => {
                     let pic = &mut self.cpu_pictures[metadata.cpu_prim_index.0];
 
-                    if !pic.resolve_scene_properties(scene_properties) {
+                    if !pic.resolve_scene_properties(frame_context.scene_properties) {
                         return None;
                     }
 
                     let (rfid, may_need_clip_mask) = match pic.kind {
                         PictureKind::Image { reference_frame_id, .. } => {
                             (Some(reference_frame_id), false)
                         }
                         _ => {
@@ -1767,32 +1754,23 @@ impl PrimitiveStore {
         // For example, scrolling may affect the location of an item in
         // local space, which may force us to render this item on a larger
         // picture target, if being composited.
         let mut child_tasks = Vec::new();
         if let Some((pipeline_id, dependencies, rfid)) = dependencies {
             let result = self.prepare_prim_runs(
                 &dependencies,
                 pipeline_id,
-                gpu_cache,
-                resource_cache,
-                render_tasks,
-                clip_store,
-                clip_scroll_tree,
-                pipelines,
-                prim_context,
+                prim_run_context,
                 cull_children,
                 &mut child_tasks,
-                profile_counters,
                 rfid,
-                scene_properties,
                 cpu_prim_index,
-                screen_rect,
-                node_data,
-                local_rects,
+                frame_context,
+                frame_state,
             );
 
             let metadata = &mut self.cpu_metadata[prim_index.0];
 
             // Restore the dependencies (borrow check dance)
             let pic = &mut self.cpu_pictures[cpu_prim_index.0];
             pic.runs = dependencies;
 
@@ -1813,60 +1791,55 @@ impl PrimitiveStore {
             let local_rect = metadata.local_clip_rect.intersection(&metadata.local_rect);
             let local_rect = match local_rect {
                 Some(local_rect) => local_rect,
                 None if perform_culling => return None,
                 None => LayerRect::zero(),
             };
 
             let screen_bounding_rect = calculate_screen_bounding_rect(
-                &prim_context.scroll_node.world_content_transform,
+                &prim_run_context.scroll_node.world_content_transform,
                 &local_rect,
-                prim_context.device_pixel_scale,
+                frame_context.device_pixel_scale,
             );
 
-            let clip_bounds = match prim_context.clip_chain {
+            let clip_bounds = match prim_run_context.clip_chain {
                 Some(ref node) => node.combined_outer_screen_rect,
-                None => *screen_rect,
+                None => frame_context.screen_rect,
             };
             metadata.screen_rect = screen_bounding_rect.intersection(&clip_bounds);
 
             if metadata.screen_rect.is_none() && perform_culling {
                 return None;
             }
 
-            metadata.clip_chain_rect_index = clip_chain_rect_index;
+            metadata.clip_chain_rect_index = prim_run_context.clip_chain_rect_index;
 
             (local_rect, screen_bounding_rect)
         };
 
         if perform_culling && may_need_clip_mask && !self.update_clip_task(
             prim_index,
-            prim_context,
+            prim_run_context,
             &unclipped_device_rect,
-            screen_rect,
-            resource_cache,
-            gpu_cache,
-            render_tasks,
-            clip_store,
             parent_tasks,
-            node_data,
+            frame_context,
+            frame_state,
         ) {
             return None;
         }
 
         self.prepare_prim_for_render_inner(
             prim_index,
-            prim_context,
-            resource_cache,
-            gpu_cache,
-            render_tasks,
+            prim_run_context,
             child_tasks,
             parent_tasks,
             pic_index,
+            frame_context,
+            frame_state,
         );
 
         Some(local_rect)
     }
 
     // TODO(gw): Make this simpler / more efficient by tidying
     //           up the logic that early outs from prepare_prim_for_render.
     pub fn reset_prim_visibility(&mut self) {
@@ -1874,44 +1847,39 @@ impl PrimitiveStore {
             md.screen_rect = None;
         }
     }
 
     pub fn prepare_prim_runs(
         &mut self,
         runs: &[PrimitiveRun],
         pipeline_id: PipelineId,
-        gpu_cache: &mut GpuCache,
-        resource_cache: &mut ResourceCache,
-        render_tasks: &mut RenderTaskTree,
-        clip_store: &mut ClipStore,
-        clip_scroll_tree: &ClipScrollTree,
-        pipelines: &FastHashMap<PipelineId, ScenePipeline>,
-        parent_prim_context: &PrimitiveContext,
+        parent_prim_run_context: &PrimitiveRunContext,
         perform_culling: bool,
         parent_tasks: &mut Vec<RenderTaskId>,
-        profile_counters: &mut FrameProfileCounters,
         original_reference_frame_id: Option<ClipId>,
-        scene_properties: &SceneProperties,
         pic_index: SpecificPrimitiveIndex,
-        screen_rect: &DeviceIntRect,
-        node_data: &[ClipScrollNodeData],
-        local_rects: &mut Vec<LayerRect>,
+        frame_context: &FrameContext,
+        frame_state: &mut FrameState,
     ) -> PrimitiveRunLocalRect {
         let mut result = PrimitiveRunLocalRect {
             local_rect_in_actual_parent_space: LayerRect::zero(),
             local_rect_in_original_parent_space: LayerRect::zero(),
         };
 
         for run in runs {
             // TODO(gw): Perhaps we can restructure this to not need to create
             //           a new primitive context for every run (if the hash
             //           lookups ever show up in a profile).
-            let scroll_node = &clip_scroll_tree.nodes[&run.clip_and_scroll.scroll_node_id];
-            let clip_chain = clip_scroll_tree.get_clip_chain(&run.clip_and_scroll.clip_node_id());
+            let scroll_node = &frame_context
+                .clip_scroll_tree
+                .nodes[&run.clip_and_scroll.scroll_node_id];
+            let clip_chain = frame_context
+                .clip_scroll_tree
+                .get_clip_chain(&run.clip_and_scroll.clip_node_id());
 
             if perform_culling {
                 if !scroll_node.invertible {
                     debug!("{:?} {:?}: position not invertible", run.base_prim_index, pipeline_id);
                     continue;
                 }
 
                 match clip_chain {
@@ -1919,86 +1887,75 @@ impl PrimitiveStore {
                         debug!("{:?} {:?}: clipped out", run.base_prim_index, pipeline_id);
                         continue;
                     }
                     _ => {},
                 }
             }
 
 
-            let parent_relative_transform = parent_prim_context
+            let parent_relative_transform = parent_prim_run_context
                 .scroll_node
                 .world_content_transform
                 .inverse()
                 .map(|inv_parent| {
                     inv_parent.pre_mul(&scroll_node.world_content_transform)
                 });
 
             let original_relative_transform = original_reference_frame_id
                 .and_then(|original_reference_frame_id| {
-                    let parent = clip_scroll_tree
+                    let parent = frame_context
+                        .clip_scroll_tree
                         .nodes[&original_reference_frame_id]
                         .world_content_transform;
                     parent.inverse()
                         .map(|inv_parent| {
                             inv_parent.pre_mul(&scroll_node.world_content_transform)
                         })
                 });
 
-            let display_list = &pipelines
+            let display_list = &frame_context.pipelines
                 .get(&pipeline_id)
                 .expect("No display list?")
                 .display_list;
 
-            let child_prim_context = PrimitiveContext::new(
-                parent_prim_context.device_pixel_scale,
-                display_list,
-                clip_chain,
-                scroll_node,
-            );
-
-
             let clip_chain_rect = match perform_culling {
                 true => get_local_clip_rect_for_nodes(scroll_node, clip_chain),
                 false => None,
             };
 
             let clip_chain_rect_index = match clip_chain_rect {
                 Some(rect) if rect.is_empty() => continue,
                 Some(rect) => {
-                    local_rects.push(rect);
-                    ClipChainRectIndex(local_rects.len() - 1)
+                    frame_state.local_clip_rects.push(rect);
+                    ClipChainRectIndex(frame_state.local_clip_rects.len() - 1)
                 }
                 None => ClipChainRectIndex(0), // This is no clipping.
             };
 
+            let child_prim_run_context = PrimitiveRunContext::new(
+                display_list,
+                clip_chain,
+                scroll_node,
+                clip_chain_rect_index,
+            );
 
             for i in 0 .. run.count {
                 let prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
 
                 if let Some(prim_local_rect) = self.prepare_prim_for_render(
                     prim_index,
-                    &child_prim_context,
-                    resource_cache,
-                    gpu_cache,
-                    render_tasks,
-                    clip_store,
-                    clip_scroll_tree,
-                    pipelines,
+                    &child_prim_run_context,
                     perform_culling,
                     parent_tasks,
-                    scene_properties,
-                    profile_counters,
                     pic_index,
-                    screen_rect,
-                    clip_chain_rect_index,
-                    node_data,
-                    local_rects,
+                    frame_context,
+                    frame_state,
                 ) {
-                    profile_counters.visible_primitives.inc();
+                    frame_state.profile_counters.visible_primitives.inc();
 
                     if let Some(ref matrix) = original_relative_transform {
                         let bounds = matrix.transform_rect(&prim_local_rect);
                         result.local_rect_in_original_parent_space =
                             result.local_rect_in_original_parent_space.union(&bounds);
                     }
 
                     if let Some(ref matrix) = parent_relative_transform {
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -1,52 +1,52 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ApiMsg, BlobImageRenderer, BuiltDisplayList, DebugCommand, DeviceIntPoint};
+use api::{ApiMsg, BuiltDisplayList, DebugCommand, DeviceIntPoint};
 #[cfg(feature = "debugger")]
 use api::{BuiltDisplayListIter, SpecificDisplayItem};
 use api::{DevicePixelScale, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
 use api::{DocumentId, DocumentLayer, DocumentMsg, HitTestFlags, HitTestResult};
 use api::{IdNamespace, PipelineId, RenderNotifier, WorldPoint};
 use api::channel::{MsgReceiver, MsgSender, PayloadReceiver, PayloadReceiverHelperMethods};
 use api::channel::{PayloadSender, PayloadSenderHelperMethods};
 #[cfg(feature = "capture")]
+use api::CaptureBits;
+#[cfg(feature = "replay")]
 use api::CapturedDocument;
-#[cfg(feature = "capture")]
-use capture::{CaptureConfig, ExternalCaptureImage};
 #[cfg(feature = "debugger")]
 use debug_server;
 use frame::FrameContext;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
 use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
-use rayon::ThreadPool;
 use record::ApiRecordingReceiver;
 use resource_cache::ResourceCache;
-#[cfg(feature = "capture")]
-use resource_cache::{PlainCacheOwn, PlainResources};
+#[cfg(feature = "replay")]
+use resource_cache::PlainCacheOwn;
+#[cfg(any(feature = "capture", feature = "replay"))]
+use resource_cache::PlainResources;
 use scene::Scene;
 #[cfg(feature = "serialize")]
 use serde::{Serialize, Deserialize};
 #[cfg(feature = "debugger")]
 use serde_json;
-#[cfg(feature = "capture")]
+#[cfg(any(feature = "capture", feature = "replay"))]
 use std::path::PathBuf;
 use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
-use std::sync::Arc;
 use std::sync::mpsc::Sender;
 use std::u32;
-use texture_cache::TextureCache;
 use time::precise_time_ns;
 
 
-#[cfg_attr(feature = "capture", derive(Clone, Serialize, Deserialize))]
+#[cfg_attr(feature = "capture", derive(Clone, Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct DocumentView {
     window_size: DeviceUintSize,
     inner_rect: DeviceUintRect,
     layer: DocumentLayer,
     pan: DeviceIntPoint,
     device_pixel_ratio: f32,
     page_zoom_factor: f32,
     pinch_zoom_factor: f32,
@@ -154,49 +154,53 @@ impl Document {
 }
 
 type HitTestQuery = (Option<PipelineId>, WorldPoint, HitTestFlags, MsgSender<HitTestResult>);
 
 struct DocumentOps {
     scroll: bool,
     build: bool,
     render: bool,
+    composite: bool,
     queries: Vec<HitTestQuery>,
 }
 
 impl DocumentOps {
     fn nop() -> Self {
         DocumentOps {
             scroll: false,
             build: false,
             render: false,
+            composite: false,
             queries: vec![],
         }
     }
 
     fn build() -> Self {
         DocumentOps {
             build: true,
             ..DocumentOps::nop()
         }
     }
 
     fn combine(&mut self, mut other: Self) {
         self.scroll = self.scroll || other.scroll;
         self.build = self.build || other.build;
         self.render = self.render || other.render;
+        self.composite = self.composite || other.composite;
         self.queries.extend(other.queries.drain(..));
     }
 }
 
 /// The unique id for WR resource identification.
 static NEXT_NAMESPACE_ID: AtomicUsize = ATOMIC_USIZE_INIT;
 
-#[cfg(feature = "capture")]
-#[derive(Serialize, Deserialize)]
+#[cfg(any(feature = "capture", feature = "replay"))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct PlainRenderBackend {
     default_device_pixel_ratio: f32,
     enable_render_on_scroll: bool,
     frame_config: FrameBuilderConfig,
     documents: FastHashMap<DocumentId, DocumentView>,
     resources: PlainResources,
 }
 
@@ -225,42 +229,37 @@ pub struct RenderBackend {
 
 impl RenderBackend {
     pub fn new(
         api_rx: MsgReceiver<ApiMsg>,
         payload_rx: PayloadReceiver,
         payload_tx: PayloadSender,
         result_tx: Sender<ResultMsg>,
         default_device_pixel_ratio: f32,
-        texture_cache: TextureCache,
-        workers: Arc<ThreadPool>,
+        resource_cache: ResourceCache,
         notifier: Box<RenderNotifier>,
         frame_config: FrameBuilderConfig,
         recorder: Option<Box<ApiRecordingReceiver>>,
-        blob_image_renderer: Option<Box<BlobImageRenderer>>,
         enable_render_on_scroll: bool,
     ) -> RenderBackend {
         // The namespace_id should start from 1.
         NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed);
 
-        let resource_cache = ResourceCache::new(texture_cache, workers, blob_image_renderer);
-
         RenderBackend {
             api_rx,
             payload_rx,
             payload_tx,
             result_tx,
             default_device_pixel_ratio,
             resource_cache,
             gpu_cache: GpuCache::new(),
             frame_config,
             documents: FastHashMap::default(),
             notifier,
             recorder,
-
             enable_render_on_scroll,
         }
     }
 
     fn process_document(
         &mut self,
         document_id: DocumentId,
         message: DocumentMsg,
@@ -403,46 +402,56 @@ impl RenderBackend {
                 profile_scope!("Scroll");
 
                 let should_render = doc.frame_ctx.scroll(delta, cursor, move_phase)
                     && doc.render_on_scroll == Some(true);
 
                 DocumentOps {
                     scroll: true,
                     render: should_render,
+                    composite: should_render,
                     ..DocumentOps::nop()
                 }
             }
             DocumentMsg::HitTest(pipeline_id, point, flags, tx) => {
+                // note that we render without compositing here; we only
+                // need to render so we have an updated clip-scroll tree
+                // for handling the hit-test queries. Doing a composite
+                // here (without having being explicitly requested) causes
+                // Gecko assertion failures.
                 DocumentOps {
                     render: doc.render_on_hittest,
                     queries: vec![(pipeline_id, point, flags, tx)],
                     ..DocumentOps::nop()
                 }
             }
             DocumentMsg::ScrollNodeWithId(origin, id, clamp) => {
                 profile_scope!("ScrollNodeWithScrollId");
 
                 let should_render = doc.frame_ctx.scroll_node(origin, id, clamp)
                     && doc.render_on_scroll == Some(true);
 
                 DocumentOps {
                     scroll: true,
                     render: should_render,
+                    composite: should_render,
                     ..DocumentOps::nop()
                 }
             }
             DocumentMsg::TickScrollingBounce => {
                 profile_scope!("TickScrollingBounce");
 
                 doc.frame_ctx.tick_scrolling_bounce_animations();
 
+                let should_render = doc.render_on_scroll == Some(true);
+
                 DocumentOps {
                     scroll: true,
-                    render: doc.render_on_scroll == Some(true),
+                    render: should_render,
+                    composite: should_render,
                     ..DocumentOps::nop()
                 }
             }
             DocumentMsg::GetScrollNodeState(tx) => {
                 profile_scope!("GetScrollNodeState");
                 tx.send(doc.frame_ctx.get_scroll_node_state()).unwrap();
                 DocumentOps::nop()
             }
@@ -464,16 +473,17 @@ impl RenderBackend {
                 let mut op = DocumentOps::nop();
 
                 if let Some(ref mut ros) = doc.render_on_scroll {
                     *ros = true;
                 }
 
                 if doc.scene.root_pipeline_id.is_some() {
                     op.render = true;
+                    op.composite = true;
                 }
 
                 op
             }
         }
     }
 
     fn next_namespace_id(&self) -> IdNamespace {
@@ -570,21 +580,20 @@ impl RenderBackend {
                             ResultMsg::DebugOutput(DebugOutput::FetchDocuments(json))
                         }
                         DebugCommand::FetchClipScrollTree => {
                             let json = self.get_clip_scroll_tree_for_debugger();
                             ResultMsg::DebugOutput(DebugOutput::FetchClipScrollTree(json))
                         }
                         #[cfg(feature = "capture")]
                         DebugCommand::SaveCapture(root, bits) => {
-                            let config = CaptureConfig::new(root, bits);
-                            let deferred = self.save_capture(&config, &mut profile_counters);
-                            ResultMsg::DebugOutput(DebugOutput::SaveCapture(config, deferred))
+                            let output = self.save_capture(root, bits, &mut profile_counters);
+                            ResultMsg::DebugOutput(output)
                         },
-                        #[cfg(feature = "capture")]
+                        #[cfg(feature = "replay")]
                         DebugCommand::LoadCapture(root, tx) => {
                             NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed);
                             frame_counter += 1;
 
                             self.load_capture(&root, &mut profile_counters);
 
                             for (id, doc) in &self.documents {
                                 let captured = CapturedDocument {
@@ -651,16 +660,18 @@ impl RenderBackend {
                     doc_msg,
                     *frame_counter,
                     &mut profile_counters.ipc,
                     &mut profile_counters.resources,
                 )
             );
         }
 
+        debug_assert!(op.render || !op.composite);
+
         let doc = self.documents.get_mut(&document_id).unwrap();
 
         if op.build {
             let _timer = profile_counters.total_time.timer();
             profile_scope!("build scene");
             doc.build_scene(&mut self.resource_cache);
             doc.render_on_hittest = true;
         }
@@ -698,17 +709,17 @@ impl RenderBackend {
                 profile_counters.clone()
             );
             self.result_tx.send(msg).unwrap();
             profile_counters.reset();
             doc.render_on_hittest = false;
         }
 
         if op.render || op.scroll {
-            self.notifier.new_document_ready(document_id, op.scroll, op.render);
+            self.notifier.new_document_ready(document_id, op.scroll, op.composite);
         }
 
         for (pipeline_id, point, flags, tx) in op.queries {
             profile_scope!("HitTest");
             let cst = doc.frame_ctx.get_clip_scroll_tree();
             let result = doc.frame_builder
                 .as_ref()
                 .unwrap()
@@ -836,28 +847,30 @@ impl ToDebugString for SpecificDisplayIt
             SpecificDisplayItem::SetGradientStops => String::from("set_gradient_stops"),
             SpecificDisplayItem::PopStackingContext => String::from("pop_stacking_context"),
             SpecificDisplayItem::PushShadow(..) => String::from("push_shadow"),
             SpecificDisplayItem::PopAllShadows => String::from("pop_all_shadows"),
         }
     }
 }
 
-#[cfg(feature = "capture")]
 impl RenderBackend {
+    #[cfg(feature = "capture")]
     // Note: the mutable `self` is only needed here for resolving blob images
     fn save_capture(
         &mut self,
-        config: &CaptureConfig,
+        root: PathBuf,
+        bits: CaptureBits,
         profile_counters: &mut BackendProfileCounters,
-    ) -> Vec<ExternalCaptureImage> {
-        use api::CaptureBits;
+    ) -> DebugOutput {
+        use capture::CaptureConfig;
 
-        info!("capture: saving {:?}", config.root);
-        let (resources, deferred) = self.resource_cache.save_capture(&config.root);
+        info!("capture: saving {:?}", root);
+        let (resources, deferred) = self.resource_cache.save_capture(&root);
+        let config = CaptureConfig::new(root, bits);
 
         for (&id, doc) in &mut self.documents {
             info!("\tdocument {:?}", id);
             if config.bits.contains(CaptureBits::SCENE) {
                 let file_name = format!("scene-{}-{}", (id.0).0, id.1);
                 config.serialize(&doc.scene, file_name);
             }
             if config.bits.contains(CaptureBits::FRAME) {
@@ -884,40 +897,44 @@ impl RenderBackend {
                 .map(|(id, doc)| (*id, doc.view.clone()))
                 .collect(),
             resources,
         };
 
         config.serialize(&backend, "backend");
 
         if config.bits.contains(CaptureBits::FRAME) {
-            // After we rendered the frames, there are pending updates.
-            // Instead of serializing them, we are going to make sure
+            // After we rendered the frames, there are pending updates to both
+            // GPU cache and resources. Instead of serializing them, we are going to make sure
             // they are applied on the `Renderer` side.
-            let msg = ResultMsg::UpdateResources {
+            let msg_update_gpu_cache = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates());
+            self.result_tx.send(msg_update_gpu_cache).unwrap();
+            let msg_update_resources = ResultMsg::UpdateResources {
                 updates: self.resource_cache.pending_updates(),
                 cancel_rendering: false,
             };
-            self.result_tx.send(msg).unwrap();
+            self.result_tx.send(msg_update_resources).unwrap();
             // Save the texture/glyph/image caches.
             info!("\tresource cache");
             let caches = self.resource_cache.save_caches(&config.root);
             config.serialize(&caches, "resource_cache");
             info!("\tgpu cache");
             config.serialize(&self.gpu_cache, "gpu_cache");
         }
 
-        deferred
+        DebugOutput::SaveCapture(config, deferred)
     }
 
+    #[cfg(feature = "replay")]
     fn load_capture(
         &mut self,
         root: &PathBuf,
         profile_counters: &mut BackendProfileCounters,
     ) {
+        use capture::CaptureConfig;
         use tiling::Frame;
 
         info!("capture: loading {:?}", root);
         let backend = CaptureConfig::deserialize::<PlainRenderBackend, _>(root, "backend")
             .expect("Unable to open backend.ron");
         let caches_maybe = CaptureConfig::deserialize::<PlainCacheOwn, _>(root, "resource_cache");
 
         // Note: it would be great to have `RenderBackend` to be split
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -22,26 +22,29 @@ use texture_cache::{TextureCache, Textur
 use tiling::{RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind};
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
 pub const MAX_BLUR_STD_DEVIATION: f32 = 4.0;
 pub const MIN_DOWNSCALING_RT_SIZE: i32 = 128;
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskId(pub u32); // TODO(gw): Make private when using GPU cache!
 
 #[derive(Debug, Copy, Clone)]
 #[repr(C)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskAddress(pub u32);
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskTree {
     pub tasks: Vec<RenderTask>,
     pub task_data: Vec<RenderTaskData>,
 }
 
 pub type ClipChainNodeRef = Option<Rc<ClipChainNode>>;
 
 #[derive(Debug, Clone)]
@@ -207,51 +210,56 @@ impl ops::Index<RenderTaskId> for Render
 
 impl ops::IndexMut<RenderTaskId> for RenderTaskTree {
     fn index_mut(&mut self, id: RenderTaskId) -> &mut RenderTask {
         &mut self.tasks[id.0 as usize]
     }
 }
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskLocation {
     Fixed,
     Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
     TextureCache(SourceTexture, i32, DeviceIntRect),
 }
 
 #[derive(Debug, Clone)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipWorkItem {
     pub scroll_node_data_index: ClipScrollNodeIndex,
     pub clip_sources: ClipSourcesWeakHandle,
     pub coordinate_system_id: CoordinateSystemId,
 }
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CacheMaskTask {
     actual_rect: DeviceIntRect,
     pub clips: Vec<ClipWorkItem>,
     pub coordinate_system_id: CoordinateSystemId,
 }
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PictureTask {
     pub prim_index: PrimitiveIndex,
     pub target_kind: RenderTargetKind,
     pub content_origin: ContentOrigin,
     pub color: PremultipliedColorF,
     pub pic_type: PictureType,
 }
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BlurTask {
     pub blur_std_deviation: f32,
     pub target_kind: RenderTargetKind,
     pub color: PremultipliedColorF,
     pub scale_factor: f32,
 }
 
 impl BlurTask {
@@ -260,63 +268,69 @@ impl BlurTask {
         pt.add_item(format!("std deviation: {}", self.blur_std_deviation));
         pt.add_item(format!("target: {:?}", self.target_kind));
         pt.add_item(format!("scale: {}", self.scale_factor));
     }
 }
 
 // Where the source data for a blit task can be found.
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BlitSource {
     Image {
         key: ImageCacheKey,
     },
     RenderTask {
         task_id: RenderTaskId,
     },
 }
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BlitTask {
     pub source: BlitSource,
 }
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskData {
     pub data: [f32; FLOATS_PER_RENDER_TASK_INFO],
 }
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskKind {
     Picture(PictureTask),
     CacheMask(CacheMaskTask),
     VerticalBlur(BlurTask),
     HorizontalBlur(BlurTask),
     Readback(DeviceIntRect),
     Scaling(RenderTargetKind),
     Blit(BlitTask),
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ClearMode {
     // Applicable to color and alpha targets.
     Zero,
     One,
 
     // Applicable to color targets only.
     Transparent,
 }
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTask {
     pub location: RenderTaskLocation,
     pub children: Vec<RenderTaskId>,
     pub kind: RenderTaskKind,
     pub clear_mode: ClearMode,
     pub pass_index: Option<RenderPassIndex>,
 }
 
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -16,17 +16,17 @@ use api::{RenderApiSender, RenderNotifie
 use api::{YUV_COLOR_SPACES, YUV_FORMATS, channel};
 #[cfg(not(feature = "debugger"))]
 use api::ApiMsg;
 use api::DebugCommand;
 #[cfg(not(feature = "debugger"))]
 use api::channel::MsgSender;
 use batch::{BatchKey, BatchKind, BatchTextures, BrushBatchKind};
 use batch::{BrushImageSourceKind, TransformBatchKind};
-#[cfg(feature = "capture")]
+#[cfg(any(feature = "capture", feature = "replay"))]
 use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
 use debug_colors;
 use debug_render::DebugRenderer;
 #[cfg(feature = "debugger")]
 use debug_server::{self, DebugServer};
 use device::{DepthFunction, Device, FrameId, Program, UploadMethod, Texture,
              VertexDescriptor, PBO};
 use device::{ExternalTexture, FBOId, TextureSlot, VertexAttribute, VertexAttributeKind};
@@ -34,29 +34,30 @@ use device::{FileWatcherHandler, ShaderE
              VertexUsageHint, VAO, VBO, CustomVAO};
 use device::{ProgramCache, ReadPixelsFormat};
 use euclid::{rect, Transform3D};
 use frame_builder::FrameBuilderConfig;
 use gleam::gl;
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
 use gpu_types::PrimitiveInstance;
-use internal_types::{SourceTexture, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE};
+use internal_types::{SourceTexture, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError};
 use internal_types::{CacheTextureId, FastHashMap, RenderedDocument, ResultMsg, TextureUpdateOp};
 use internal_types::{DebugOutput, RenderPassIndex, RenderTargetInfo, TextureUpdateList, TextureUpdateSource};
 use picture::ContentOrigin;
 use prim_store::DeferredResolve;
 use profiler::{BackendProfileCounters, Profiler};
 use profiler::{GpuProfileTag, RendererProfileCounters, RendererProfileTimers};
 use query::{GpuProfiler, GpuTimer};
 use rayon::Configuration as ThreadPoolConfig;
 use rayon::ThreadPool;
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
 use render_task::{RenderTaskKind, RenderTaskTree};
+use resource_cache::ResourceCache;
 #[cfg(feature = "debugger")]
 use serde_json;
 use std;
 use std::cmp;
 use std::collections::VecDeque;
 use std::collections::hash_map::Entry;
 use std::f32;
 use std::mem;
@@ -482,17 +483,18 @@ pub enum GraphicsApi {
 #[derive(Clone, Debug)]
 pub struct GraphicsApiInfo {
     pub kind: GraphicsApi,
     pub renderer: String,
     pub version: String,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ImageBufferKind {
     Texture2D = 0,
     TextureRect = 1,
     TextureExternal = 2,
     Texture2DArray = 3,
 }
 
 //TODO: those types are the same, so let's merge them
@@ -772,17 +774,18 @@ impl SourceTextureResolver {
                     .expect("BUG: pass_index doesn't map to pool_index");
                 Some(&self.render_target_pool[pool_index.0])
             },
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 #[allow(dead_code)] // SubpixelVariableTextColor is not used at the moment.
 pub enum BlendMode {
     None,
     Alpha,
     PremultipliedAlpha,
     PremultipliedDestOut,
     SubpixelDualSource,
     SubpixelConstantTextColor(ColorF),
@@ -1562,26 +1565,16 @@ struct FrameOutput {
 
 #[derive(PartialEq)]
 struct TargetSelector {
     size: DeviceUintSize,
     num_layers: usize,
     format: ImageFormat,
 }
 
-#[cfg(feature = "capture")]
-struct RendererCapture {
-    read_fbo: FBOId,
-    owned_external_images: FastHashMap<(ExternalImageId, u8), ExternalTexture>,
-}
-
-// Note: we can't just feature-gate the fields because `cbindgen` fails on those.
-// see https://github.com/eqrion/cbindgen/issues/116
-#[cfg(not(feature = "capture"))]
-struct RendererCapture;
 
 /// The renderer is responsible for submitting to the GPU the work prepared by the
 /// RenderBackend.
 pub struct Renderer {
     result_rx: Receiver<ResultMsg>,
     debug_server: DebugServer,
     device: Device,
     pending_texture_updates: Vec<TextureUpdateList>,
@@ -1682,39 +1675,48 @@ pub struct Renderer {
 
     renderer_errors: Vec<RendererError>,
 
     /// List of profile results from previous frames. Can be retrieved
     /// via get_frame_profiles().
     cpu_profiles: VecDeque<CpuProfile>,
     gpu_profiles: VecDeque<GpuProfile>,
 
-    #[cfg_attr(not(feature = "capture"), allow(dead_code))]
-    capture: RendererCapture,
+    #[cfg(feature = "capture")]
+    read_fbo: FBOId,
+    #[cfg(feature = "replay")]
+    owned_external_images: FastHashMap<(ExternalImageId, u8), ExternalTexture>,
 }
 
 #[derive(Debug)]
 pub enum RendererError {
     Shader(ShaderError),
     Thread(std::io::Error),
+    Resource(ResourceCacheError),
     MaxTextureSize,
 }
 
 impl From<ShaderError> for RendererError {
     fn from(err: ShaderError) -> Self {
         RendererError::Shader(err)
     }
 }
 
 impl From<std::io::Error> for RendererError {
     fn from(err: std::io::Error) -> Self {
         RendererError::Thread(err)
     }
 }
 
+impl From<ResourceCacheError> for RendererError {
+    fn from(err: ResourceCacheError) -> Self {
+        RendererError::Resource(err)
+    }
+}
+
 impl Renderer {
     /// Initializes webrender and creates a `Renderer` and `RenderApiSender`.
     ///
     /// # Examples
     /// Initializes a `Renderer` with some reasonable values. For more information see
     /// [`RendererOptions`][rendereroptions].
     ///
     /// ```rust,ignore
@@ -2214,52 +2216,49 @@ impl Renderer {
                     });
                 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();
         let thread_listener_for_render_backend = thread_listener.clone();
         let thread_name = format!("WRRenderBackend#{}", options.renderer_id.unwrap_or(0));
+        let resource_cache = ResourceCache::new(
+            texture_cache,
+            workers,
+            blob_image_renderer,
+        )?;
         try!{
             thread::Builder::new().name(thread_name.clone()).spawn(move || {
                 register_thread_with_profiler(thread_name.clone());
                 if let Some(ref thread_listener) = *thread_listener_for_render_backend {
                     thread_listener.thread_started(&thread_name);
                 }
                 let mut backend = RenderBackend::new(
                     api_rx,
                     payload_rx,
                     payload_tx_for_backend,
                     result_tx,
                     device_pixel_ratio,
-                    texture_cache,
-                    workers,
+                    resource_cache,
                     backend_notifier,
                     config,
                     recorder,
-                    blob_image_renderer,
                     enable_render_on_scroll,
                 );
                 backend.run(backend_profile_counters);
                 if let Some(ref thread_listener) = *thread_listener_for_render_backend {
                     thread_listener.thread_stopped(&thread_name);
                 }
             })
         };
 
+        let gpu_profile = GpuProfiler::new(Rc::clone(device.rc_gl()));
         #[cfg(feature = "capture")]
-        let capture = RendererCapture {
-            read_fbo: device.create_fbo_for_external_texture(0),
-            owned_external_images: FastHashMap::default(),
-        };
-        #[cfg(not(feature = "capture"))]
-        let capture = RendererCapture;
-
-        let gpu_profile = GpuProfiler::new(Rc::clone(device.rc_gl()));
+        let read_fbo = device.create_fbo_for_external_texture(0);
 
         let mut renderer = Renderer {
             result_rx,
             debug_server,
             device,
             active_documents: Vec::new(),
             pending_texture_updates: Vec::new(),
             pending_gpu_cache_updates: Vec::new(),
@@ -2314,17 +2313,20 @@ impl Renderer {
             output_targets: FastHashMap::default(),
             cpu_profiles: VecDeque::new(),
             gpu_profiles: VecDeque::new(),
             gpu_cache_texture,
             gpu_cache_frame_id: FrameId::new(0),
             texture_cache_upload_pbo,
             texture_resolver,
             renderer_errors: Vec::new(),
-            capture,
+            #[cfg(feature = "capture")]
+            read_fbo,
+            #[cfg(feature = "replay")]
+            owned_external_images: FastHashMap::default(),
         };
 
         renderer.set_debug_flags(options.debug_flags);
 
         let sender = RenderApiSender::new(api_tx, payload_tx);
         Ok((renderer, sender))
     }
 
@@ -2375,20 +2377,16 @@ impl Renderer {
         while let Ok(msg) = self.result_rx.try_recv() {
             match msg {
                 ResultMsg::PublishDocument(
                     document_id,
                     doc,
                     texture_update_list,
                     profile_counters,
                 ) => {
-                    //TODO: associate `document_id` with target window
-                    self.pending_texture_updates.push(texture_update_list);
-                    self.backend_profile_counters = profile_counters;
-
                     // Update the list of available epochs for use during reftests.
                     // This is a workaround for https://github.com/servo/servo/issues/13149.
                     for (pipeline_id, epoch) in &doc.pipeline_epoch_map {
                         self.pipeline_epoch_map.insert(*pipeline_id, *epoch);
                     }
 
                     // Add a new document to the active set, expressed as a `Vec` in order
                     // to re-order based on `DocumentLayer` during rendering.
@@ -2399,26 +2397,42 @@ impl Renderer {
                             // a render just to off-screen targets.
                             if self.active_documents[pos].1.frame.must_be_drawn() {
                                 self.render_impl(None).ok();
                             }
                             self.active_documents[pos].1 = doc;
                         }
                         None => self.active_documents.push((document_id, doc)),
                     }
+
+                    // IMPORTANT: The pending texture cache updates must be applied
+                    //            *after* the previous frame has been rendered above
+                    //            (if neceessary for a texture cache update). For
+                    //            an example of why this is required:
+                    //            1) Previous frame contains a render task that
+                    //               targets Texture X.
+                    //            2) New frame contains a texture cache update which
+                    //               frees Texture X.
+                    //            3) bad stuff happens.
+
+                    //TODO: associate `document_id` with target window
+                    self.pending_texture_updates.push(texture_update_list);
+                    self.backend_profile_counters = profile_counters;
                 }
                 ResultMsg::UpdateGpuCache(list) => {
                     self.pending_gpu_cache_updates.push(list);
                 }
                 ResultMsg::UpdateResources {
                     updates,
                     cancel_rendering,
                 } => {
                     self.pending_texture_updates.push(updates);
+                    self.device.begin_frame();
                     self.update_texture_cache();
+                    self.device.end_frame();
                     // If we receive a `PublishDocument` message followed by this one
                     // within the same update we need ot cancel the frame because we
                     // might have deleted the resources in use in the frame due to a
                     // memory pressure event.
                     if cancel_rendering {
                         self.active_documents.clear();
                     }
                 }
@@ -2429,17 +2443,17 @@ impl Renderer {
                     DebugOutput::FetchDocuments(string) |
                     DebugOutput::FetchClipScrollTree(string) => {
                         self.debug_server.send(string);
                     }
                     #[cfg(feature = "capture")]
                     DebugOutput::SaveCapture(config, deferred) => {
                         self.save_capture(config, deferred);
                     }
-                    #[cfg(feature = "capture")]
+                    #[cfg(feature = "replay")]
                     DebugOutput::LoadCapture(root, plain_externals) => {
                         self.active_documents.clear();
                         self.load_capture(root, plain_externals);
                     }
                 },
                 ResultMsg::DebugCommand(command) => {
                     self.handle_debug_command(command);
                 }
@@ -2835,19 +2849,19 @@ impl Renderer {
             }
 
             // Re-use whatever targets possible from the pool, before
             // they get changed/re-allocated by the rendered frames.
             for doc_with_id in &mut active_documents {
                 self.prepare_tile_frame(&mut doc_with_id.1.frame);
             }
 
-            #[cfg(feature = "capture")]
+            #[cfg(feature = "replay")]
             self.texture_resolver.external_images.extend(
-                self.capture.owned_external_images.iter().map(|(key, value)| (*key, value.clone()))
+                self.owned_external_images.iter().map(|(key, value)| (*key, value.clone()))
             );
 
             for &mut (_, RenderedDocument { ref mut frame, .. }) in &mut active_documents {
                 self.prepare_gpu_cache(frame);
                 assert!(frame.gpu_cache_frame_id <= self.gpu_cache_frame_id);
 
                 self.draw_tile_frame(
                     frame,
@@ -2920,22 +2934,19 @@ impl Renderer {
     }
 
     pub fn layers_are_bouncing_back(&self) -> bool {
         self.active_documents
             .iter()
             .any(|&(_, ref render_doc)| !render_doc.layers_bouncing_back.is_empty())
     }
 
-    fn prepare_gpu_cache(&mut self, frame: &Frame) {
+    fn update_gpu_cache(&mut self) {
         let _gm = self.gpu_profile.start_marker("gpu cache update");
 
-        let deferred_update_list = self.update_deferred_resolves(&frame.deferred_resolves);
-        self.pending_gpu_cache_updates.extend(deferred_update_list);
-
         // For an artificial stress test of GPU cache resizing,
         // always pass an extra update list with at least one block in it.
         let gpu_cache_height = self.gpu_cache_texture.get_height();
         if gpu_cache_height != 0 &&  GPU_CACHE_RESIZE_TEST {
             self.pending_gpu_cache_updates.push(GpuCacheUpdateList {
                 frame_id: FrameId::new(0),
                 height: gpu_cache_height,
                 blocks: vec![[1f32; 4].into()],
@@ -2965,26 +2976,33 @@ impl Renderer {
                 self.gpu_cache_frame_id = update_list.frame_id
             }
             self.gpu_cache_texture
                 .update(&mut self.device, &update_list);
         }
 
         let updated_rows = self.gpu_cache_texture.flush(&mut self.device);
 
+        let counters = &mut self.backend_profile_counters.resources.gpu_cache;
+        counters.updated_rows.set(updated_rows);
+        counters.updated_blocks.set(updated_blocks);
+    }
+
+    fn prepare_gpu_cache(&mut self, frame: &Frame) {
+        let deferred_update_list = self.update_deferred_resolves(&frame.deferred_resolves);
+        self.pending_gpu_cache_updates.extend(deferred_update_list);
+
+        self.update_gpu_cache();
+
         // Note: the texture might have changed during the `update`,
         // so we need to bind it here.
         self.device.bind_texture(
             TextureSampler::ResourceCache,
             &self.gpu_cache_texture.texture,
         );
-
-        let counters = &mut self.backend_profile_counters.resources.gpu_cache;
-        counters.updated_rows.set(updated_rows);
-        counters.updated_blocks.set(updated_blocks);
     }
 
     fn update_texture_cache(&mut self) {
         let _gm = self.gpu_profile.start_marker("texture cache update");
         let mut pending_texture_updates = mem::replace(&mut self.pending_texture_updates, vec![]);
 
         for update_list in pending_texture_updates.drain(..) {
             for update in update_list.updates {
@@ -4689,19 +4707,19 @@ impl Renderer {
         self.ps_gradient.deinit(&mut self.device);
         self.ps_angle_gradient.deinit(&mut self.device);
         self.ps_radial_gradient.deinit(&mut self.device);
         self.ps_blend.deinit(&mut self.device);
         self.ps_hw_composite.deinit(&mut self.device);
         self.ps_split_composite.deinit(&mut self.device);
         self.ps_composite.deinit(&mut self.device);
         #[cfg(feature = "capture")]
-        self.device.delete_fbo(self.capture.read_fbo);
-        #[cfg(feature = "capture")]
-        for (_, ext) in self.capture.owned_external_images {
+        self.device.delete_fbo(self.read_fbo);
+        #[cfg(feature = "replay")]
+        for (_, ext) in self.owned_external_images {
             self.device.delete_external_texture(ext);
         }
         self.device.end_frame();
     }
 }
 
 pub enum ExternalImageSource<'a> {
     RawData(&'a [u8]),  // raw buffers.
@@ -4843,72 +4861,77 @@ impl RendererStats {
             total_draw_calls: 0,
             alpha_target_count: 0,
             color_target_count: 0,
         }
     }
 }
 
 
-#[cfg(feature = "capture")]
-#[derive(Deserialize, Serialize)]
+
+#[cfg(any(feature = "capture", feature = "replay"))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct PlainTexture {
     data: String,
     size: (u32, u32, i32),
     format: ImageFormat,
     filter: TextureFilter,
     render_target: Option<RenderTargetInfo>,
 }
 
-#[cfg(feature = "capture")]
-#[derive(Deserialize, Serialize)]
+
+#[cfg(any(feature = "capture", feature = "replay"))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct PlainRenderer {
     gpu_cache: PlainTexture,
+    gpu_cache_frame_id: FrameId,
     textures: Vec<PlainTexture>,
     external_images: Vec<ExternalCaptureImage>
 }
 
-#[cfg(feature = "capture")]
+#[cfg(feature = "replay")]
 enum CapturedExternalImageData {
     NativeTexture(gl::GLuint),
     Buffer(Arc<Vec<u8>>),
 }
 
-#[cfg(feature = "capture")]
+#[cfg(feature = "replay")]
 struct DummyExternalImageHandler {
     data: FastHashMap<(ExternalImageId, u8), (CapturedExternalImageData, TexelRect)>,
 }
 
-#[cfg(feature = "capture")]
+#[cfg(feature = "replay")]
 impl ExternalImageHandler for DummyExternalImageHandler {
     fn lock(&mut self, key: ExternalImageId, channel_index: u8) -> ExternalImage {
         let (ref captured_data, ref uv) = self.data[&(key, channel_index)];
         ExternalImage {
             uv: *uv,
             source: match *captured_data {
                 CapturedExternalImageData::NativeTexture(tid) => ExternalImageSource::NativeTexture(tid),
                 CapturedExternalImageData::Buffer(ref arc) => ExternalImageSource::RawData(&*arc),
             }
         }
     }
     fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {}
 }
 
-#[cfg(feature = "capture")]
+#[cfg(feature = "replay")]
 impl OutputImageHandler for () {
     fn lock(&mut self, _: PipelineId) -> Option<(u32, DeviceIntSize)> {
         None
     }
     fn unlock(&mut self, _: PipelineId) {
         unreachable!()
     }
 }
 
-#[cfg(feature = "capture")]
 impl Renderer {
+    #[cfg(feature = "capture")]
     fn save_texture(
         texture: &Texture, name: &str, root: &PathBuf, device: &mut Device
     ) -> PlainTexture {
         use std::fs;
         use std::io::Write;
 
         let short_path = format!("textures/{}.raw", name);
 
@@ -4955,16 +4978,17 @@ impl Renderer {
             data: short_path,
             size: (rect.size.width, rect.size.height, texture.get_layer_count()),
             format: texture.get_format(),
             filter: texture.get_filter(),
             render_target: texture.get_render_target(),
         }
     }
 
+    #[cfg(feature = "replay")]
     fn load_texture(texture: &mut Texture, plain: &PlainTexture, root: &PathBuf, device: &mut Device) -> Vec<u8> {
         use std::fs::File;
         use std::io::Read;
 
         let mut texels = Vec::new();
         assert_eq!(plain.format, texture.get_format());
         File::open(root.join(&plain.data))
             .expect(&format!("Unable to open texture at {}", plain.data))
@@ -4975,28 +4999,29 @@ impl Renderer {
             texture, plain.size.0, plain.size.1,
             plain.filter, plain.render_target,
             plain.size.2, Some(texels.as_slice()),
         );
 
         texels
     }
 
+    #[cfg(feature = "capture")]
     fn save_capture(
         &mut self,
         config: CaptureConfig,
         deferred_images: Vec<ExternalCaptureImage>,
     ) {
         use std::fs;
         use std::io::Write;
         use api::{CaptureBits, ExternalImageData};
 
         self.device.begin_frame();
         let _gm = self.gpu_profile.start_marker("read GPU data");
-        self.device.bind_read_target_impl(self.capture.read_fbo);
+        self.device.bind_read_target_impl(self.read_fbo);
 
         if !deferred_images.is_empty() {
             info!("saving external images");
             let mut arc_map = FastHashMap::<*const u8, String>::default();
             let mut tex_map = FastHashMap::<u32, String>::default();
             let handler = self.external_image_handler
                 .as_mut()
                 .expect("Unable to lock the external image handler!");
@@ -5063,21 +5088,23 @@ impl Renderer {
 
         if config.bits.contains(CaptureBits::FRAME) {
             let path_textures = config.root.join("textures");
             if !path_textures.is_dir() {
                 fs::create_dir(&path_textures).unwrap();
             }
 
             info!("saving GPU cache");
+            self.update_gpu_cache(); // flush pending updates
             let mut plain_self = PlainRenderer {
                 gpu_cache: Self::save_texture(
                     &self.gpu_cache_texture.texture,
                     "gpu", &config.root, &mut self.device,
                 ),
+                gpu_cache_frame_id: self.gpu_cache_frame_id,
                 textures: Vec::new(),
                 external_images: deferred_images,
             };
 
             info!("saving cached textures");
             for texture in &self.texture_resolver.cache_texture_map {
                 let file_name = format!("cache-{}", plain_self.textures.len() + 1);
                 info!("\t{}", file_name);
@@ -5088,16 +5115,17 @@ impl Renderer {
             config.serialize(&plain_self, "renderer");
         }
 
         self.device.bind_read_target(None);
         self.device.end_frame();
         info!("done.");
     }
 
+    #[cfg(feature = "replay")]
     fn load_capture(
         &mut self, root: PathBuf, plain_externals: Vec<PlainExternalImage>
     ) {
         use std::fs::File;
         use std::io::Read;
         use std::slice;
 
         info!("loading external buffer-backed images");
@@ -5160,16 +5188,17 @@ impl Renderer {
                     // fill up the CPU cache from the contents we just loaded
                     rows.clear();
                     cpu_blocks.clear();
                     rows.extend((0 .. dim.height).map(|_| CacheRow::new()));
                     cpu_blocks.extend_from_slice(blocks);
                 }
                 CacheBus::Scatter { .. } => {}
             }
+            self.gpu_cache_frame_id = renderer.gpu_cache_frame_id;
 
             info!("loading external texture-backed images");
             let mut native_map = FastHashMap::<String, gl::GLuint>::default();
             for ExternalCaptureImage { short_path, external, descriptor } in renderer.external_images {
                 let target = match external.image_type {
                     ExternalImageType::TextureHandle(target) => target,
                     ExternalImageType::Buffer => continue,
                 };
@@ -5187,17 +5216,17 @@ impl Renderer {
                             size: (descriptor.width, descriptor.height, layer_count),
                             format: descriptor.format,
                             filter,
                             render_target: None,
                         };
                         let mut t = self.device.create_texture(target, plain_tex.format);
                         Self::load_texture(&mut t, &plain_tex, &root, &mut self.device);
                         let extex = t.into_external();
-                        self.capture.owned_external_images.insert(key, extex.clone());
+                        self.owned_external_images.insert(key, extex.clone());
                         e.insert(extex.internal_id()).clone()
                     }
                 };
 
                 let value = (CapturedExternalImageData::NativeTexture(tid), plain_ext.uv);
                 image_handler.data.insert(key, value);
             }
 
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -6,46 +6,51 @@ use api::{AddFont, BlobImageData, BlobIm
 use api::{BlobImageDescriptor, BlobImageError, BlobImageRenderer, BlobImageRequest};
 use api::{ColorF, DevicePoint, DeviceUintRect, DeviceUintSize};
 use api::{Epoch, FontInstanceKey, FontKey, FontTemplate};
 use api::{ExternalImageData, ExternalImageType};
 use api::{FontInstanceOptions, FontInstancePlatformOptions, FontVariation};
 use api::{GlyphDimensions, GlyphKey, IdNamespace};
 use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering};
 use api::{TileOffset, TileSize};
-#[cfg(feature = "capture")]
-use api::{NativeFontHandle};
 use app_units::Au;
 #[cfg(feature = "capture")]
-use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
+use capture::ExternalCaptureImage;
+#[cfg(feature = "replay")]
+use capture::PlainExternalImage;
+#[cfg(any(feature = "replay", feature = "png"))]
+use capture::CaptureConfig;
 use device::TextureFilter;
 use frame::FrameId;
 use glyph_cache::GlyphCache;
 #[cfg(feature = "capture")]
-use glyph_cache::{CachedGlyphInfo, PlainGlyphCacheOwn, PlainGlyphCacheRef, PlainCachedGlyphInfo};
+use glyph_cache::{PlainGlyphCacheRef, PlainCachedGlyphInfo};
+#[cfg(feature = "replay")]
+use glyph_cache::{CachedGlyphInfo, PlainGlyphCacheOwn};
 use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphRasterizer, GlyphRequest};
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
-use internal_types::{FastHashMap, FastHashSet, SourceTexture, TextureUpdateList};
+use internal_types::{FastHashMap, FastHashSet, ResourceCacheError, SourceTexture, TextureUpdateList};
 use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
 use rayon::ThreadPool;
 use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId, RenderTaskTree};
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::cmp;
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::mem;
-#[cfg(feature = "capture")]
+#[cfg(any(feature = "capture", feature = "replay"))]
 use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
 use texture_cache::{TextureCache, TextureCacheHandle};
 
 
 const DEFAULT_TILE_SIZE: TileSize = 512;
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GlyphFetchResult {
     pub index_in_text_run: i32,
     pub uv_rect_address: GpuCacheAddress,
 }
 
 // These coordinates are always in texels.
 // They are converted to normalized ST
 // values in the vertex shader. The reason
@@ -70,17 +75,18 @@ impl CacheItem {
             uv_rect_handle: GpuCacheHandle::new(),
             uv_rect: DeviceUintRect::zero(),
             texture_layer: 0,
         }
     }
 }
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ImageProperties {
     pub descriptor: ImageDescriptor,
     pub external_image: Option<ExternalImageData>,
     pub tiling: Option<TileSize>,
     pub epoch: Epoch,
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
@@ -130,31 +136,34 @@ impl ImageTemplates {
         self.images.get(&key)
     }
 
     fn get_mut(&mut self, key: ImageKey) -> Option<&mut ImageResource> {
         self.images.get_mut(&key)
     }
 }
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct CachedImageInfo {
     texture_cache_handle: TextureCacheHandle,
     epoch: Epoch,
 }
 
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Clone, Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Clone, Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ResourceClassCacheError {
     OverLimitSize,
 }
 
 pub type ResourceCacheResult<V> = Result<V, ResourceClassCacheError>;
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ResourceClassCache<K: Hash + Eq, V> {
     resources: FastHashMap<K, ResourceCacheResult<V>>,
 }
 
 impl<K, V> ResourceClassCache<K, V>
 where
     K: Clone + Hash + Eq + Debug,
 {
@@ -198,17 +207,18 @@ where
         for key in resources_to_destroy {
             let _ = self.resources.remove(&key).unwrap();
         }
     }
 }
 
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct ImageRequest {
     key: ImageKey,
     rendering: ImageRendering,
     tile: Option<TileOffset>,
 }
 
 impl Into<BlobImageRequest> for ImageRequest {
     fn into(self) -> BlobImageRequest {
@@ -264,34 +274,36 @@ pub struct ResourceCache {
     blob_image_renderer: Option<Box<BlobImageRenderer>>,
 }
 
 impl ResourceCache {
     pub fn new(
         texture_cache: TextureCache,
         workers: Arc<ThreadPool>,
         blob_image_renderer: Option<Box<BlobImageRenderer>>,
-    ) -> Self {
-        ResourceCache {
+    ) -> Result<Self, ResourceCacheError> {
+        let glyph_rasterizer = GlyphRasterizer::new(workers)?;
+
+        Ok(ResourceCache {
             cached_glyphs: GlyphCache::new(),
             cached_images: ResourceClassCache::new(),
             cached_render_tasks: RenderTaskCache::new(),
             resources: Resources {
                 font_templates: FastHashMap::default(),
                 font_instances: Arc::new(RwLock::new(FastHashMap::default())),
                 image_templates: ImageTemplates::new(),
             },
             cached_glyph_dimensions: FastHashMap::default(),
             texture_cache,
             state: State::Idle,
             current_frame_id: FrameId(0),
             pending_image_requests: FastHashSet::default(),
-            glyph_rasterizer: GlyphRasterizer::new(workers),
+            glyph_rasterizer,
             blob_image_renderer,
-        }
+        })
     }
 
     pub fn max_texture_size(&self) -> u32 {
         self.texture_cache.max_texture_size()
     }
 
     fn should_tile(limit: u32, descriptor: &ImageDescriptor, data: &ImageData) -> bool {
         let size_check = descriptor.width > limit || descriptor.height > limit;
@@ -981,65 +993,71 @@ pub fn compute_tile_size(
         base_size
     } else {
         descriptor.height % base_size
     };
 
     (actual_width, actual_height)
 }
 
-#[cfg(feature = "capture")]
-#[derive(Serialize, Deserialize)]
+#[cfg(any(feature = "capture", feature = "replay"))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 enum PlainFontTemplate {
     Raw {
         data: String,
         index: u32,
     },
-    Native(NativeFontHandle),
+    Native,
 }
 
-#[cfg(feature = "capture")]
-#[derive(Serialize, Deserialize)]
+#[cfg(any(feature = "capture", feature = "replay"))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct PlainImageTemplate {
     data: String,
     descriptor: ImageDescriptor,
     epoch: Epoch,
     tiling: Option<TileSize>,
 }
 
-#[cfg(feature = "capture")]
-#[derive(Serialize, Deserialize)]
+#[cfg(any(feature = "capture", feature = "replay"))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PlainResources {
     font_templates: FastHashMap<FontKey, PlainFontTemplate>,
     font_instances: FastHashMap<FontInstanceKey, FontInstance>,
     image_templates: FastHashMap<ImageKey, PlainImageTemplate>,
 }
 
 #[cfg(feature = "capture")]
 #[derive(Serialize)]
 pub struct PlainCacheRef<'a> {
     current_frame_id: FrameId,
     glyphs: PlainGlyphCacheRef<'a>,
     glyph_dimensions: &'a GlyphDimensionsCache,
     images: &'a ImageCache,
     textures: &'a TextureCache,
 }
 
-#[cfg(feature = "capture")]
+#[cfg(feature = "replay")]
 #[derive(Deserialize)]
 pub struct PlainCacheOwn {
     current_frame_id: FrameId,
     glyphs: PlainGlyphCacheOwn,
     glyph_dimensions: GlyphDimensionsCache,
     images: ImageCache,
     textures: TextureCache,
 }
 
-#[cfg(feature = "capture")]
+#[cfg(feature = "replay")]
+const NATIVE_FONT: &'static [u8] = include_bytes!("../res/Proggy.ttf");
+
 impl ResourceCache {
+    #[cfg(feature = "capture")]
     pub fn save_capture(
         &mut self, root: &PathBuf
     ) -> (PlainResources, Vec<ExternalCaptureImage>) {
         #[cfg(feature = "png")]
         use device::ReadPixelsFormat;
         use std::fs;
         use std::io::Write;
 
@@ -1176,18 +1194,18 @@ impl ResourceCache {
                 .map(|(key, template)| {
                     (*key, match *template {
                         FontTemplate::Raw(ref arc, index) => {
                             PlainFontTemplate::Raw {
                                 data: font_paths[&arc.as_ptr()].clone(),
                                 index,
                             }
                         }
-                        FontTemplate::Native(ref native) => {
-                            PlainFontTemplate::Native(native.clone())
+                        FontTemplate::Native(_) => {
+                            PlainFontTemplate::Native
                         }
                     })
                 })
                 .collect(),
             font_instances: res.font_instances.read().unwrap().clone(),
             image_templates: res.image_templates.images
                 .iter()
                 .map(|(key, template)| {
@@ -1202,16 +1220,17 @@ impl ResourceCache {
                     })
                 })
                 .collect(),
         };
 
         (resources, external_images)
     }
 
+    #[cfg(feature = "capture")]
     pub fn save_caches(&self, root: &PathBuf) -> PlainCacheRef {
         use std::io::Write;
         use std::fs;
 
         let path_glyphs = root.join("glyphs");
         if !path_glyphs.is_dir() {
             fs::create_dir(&path_glyphs).unwrap();
         }
@@ -1266,16 +1285,17 @@ impl ResourceCache {
                 })
                 .collect(),
             glyph_dimensions: &self.cached_glyph_dimensions,
             images: &self.cached_images,
             textures: &self.texture_cache,
         }
     }
 
+    #[cfg(feature = "replay")]
     pub fn load_capture(
         &mut self,
         resources: PlainResources,
         caches: Option<PlainCacheOwn>,
         root: &PathBuf,
     ) -> Vec<PlainExternalImage> {
         use std::fs::File;
         use std::io::Read;
@@ -1347,16 +1367,17 @@ impl ResourceCache {
         self.pending_image_requests.clear();
 
         let res = &mut self.resources;
         res.font_templates.clear();
         *res.font_instances.write().unwrap() = resources.font_instances;
         res.image_templates.images.clear();
 
         info!("\tfont templates...");
+        let native_font_replacement = Arc::new(NATIVE_FONT.to_vec());
         for (key, plain_template) in resources.font_templates {
             let template = match plain_template {
                 PlainFontTemplate::Raw { data, index } => {
                     let arc = match raw_map.entry(data) {
                         Entry::Occupied(e) => {
                             e.get().clone()
                         }
                         Entry::Vacant(e) => {
@@ -1366,18 +1387,18 @@ impl ResourceCache {
                                 .read_to_end(&mut buffer)
                                 .unwrap();
                             e.insert(Arc::new(buffer))
                                 .clone()
                         }
                     };
                     FontTemplate::Raw(arc, index)
                 }
-                PlainFontTemplate::Native(native) => {
-                    FontTemplate::Native(native)
+                PlainFontTemplate::Native => {
+                    FontTemplate::Raw(native_font_replacement.clone(), 0)
                 }
             };
 
             self.glyph_rasterizer.add_font(key, template.clone());
             res.font_templates.insert(key, template);
         }
 
         info!("\timage templates...");
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -5,17 +5,18 @@
 use api::{BuiltDisplayList, ColorF, DynamicProperties, Epoch, LayerSize, LayoutSize};
 use api::{FilterOp, LayoutTransform, PipelineId, PropertyBinding, PropertyBindingId};
 use api::{ItemRange, MixBlendMode, StackingContext};
 use internal_types::FastHashMap;
 
 /// Stores a map of the animated property bindings for the current display list. These
 /// can be used to animate the transform and/or opacity of a display list without
 /// re-submitting the display list itself.
-#[cfg_attr(feature = "capture", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct SceneProperties {
     transform_properties: FastHashMap<PropertyBindingId, LayoutTransform>,
     float_properties: FastHashMap<PropertyBindingId, f32>,
 }
 
 impl SceneProperties {
     pub fn new() -> Self {
         SceneProperties {
@@ -76,28 +77,30 @@ impl SceneProperties {
                         default_value
                     })
             }
         }
     }
 }
 
 /// A representation of the layout within the display port for a given document or iframe.
-#[cfg_attr(feature = "capture", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ScenePipeline {
     pub pipeline_id: PipelineId,
     pub epoch: Epoch,
     pub viewport_size: LayerSize,
     pub content_size: LayoutSize,
     pub background_color: Option<ColorF>,
     pub display_list: BuiltDisplayList,
 }
 
 /// A complete representation of the layout bundling visible pipelines together.
-#[cfg_attr(feature = "capture", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct Scene {
     pub root_pipeline_id: Option<PipelineId>,
     pub pipelines: FastHashMap<PipelineId, ScenePipeline>,
     pub properties: SceneProperties,
 }
 
 impl Scene {
     pub fn new() -> Self {
--- a/gfx/webrender/src/texture_allocator.rs
+++ b/gfx/webrender/src/texture_allocator.rs
@@ -17,17 +17,18 @@ const MINIMUM_LARGE_RECT_SIZE: u32 = 32;
 /// A texture allocator using the guillotine algorithm with the rectangle merge improvement. See
 /// sections 2.2 and 2.2.5 in "A Thousand Ways to Pack the Bin - A Practical Approach to Two-
 /// Dimensional Rectangle Bin Packing":
 ///
 ///    http://clb.demon.fi/files/RectangleBinPack.pdf
 ///
 /// This approach was chosen because of its simplicity, good performance, and easy support for
 /// dynamic texture deallocation.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GuillotineAllocator {
     texture_size: DeviceUintSize,
     free_list: FreeRectList,
     allocations: u32,
     dirty: bool,
 }
 
 impl GuillotineAllocator {
@@ -166,17 +167,18 @@ impl GuillotineAllocator {
         ));
         self.allocations = 0;
         self.dirty = false;
     }
 }
 
 /// A binning free list. Binning is important to avoid sifting through lots of small strips when
 /// allocating many texture items.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct FreeRectList {
     small: Vec<DeviceUintRect>,
     medium: Vec<DeviceUintRect>,
     large: Vec<DeviceUintRect>,
 }
 
 impl FreeRectList {
     fn new() -> Self {
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -24,17 +24,18 @@ const TEXTURE_ARRAY_LAYERS_NEAREST: usiz
 // The dimensions of each layer in the texture cache.
 const TEXTURE_LAYER_DIMENSIONS: u32 = 2048;
 
 // The size of each region (page) in a texture layer.
 const TEXTURE_REGION_DIMENSIONS: u32 = 512;
 
 // Maintains a simple freelist of texture IDs that are mapped
 // to real API-specific texture IDs in the renderer.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct CacheTextureIdList {
     free_lists: FastHashMap<ImageFormat, Vec<CacheTextureId>>,
     next_id: usize,
 }
 
 impl CacheTextureIdList {
     fn new() -> Self {
         CacheTextureIdList {
@@ -60,34 +61,36 @@ impl CacheTextureIdList {
             .or_insert(Vec::new())
             .push(id);
     }
 }
 
 // Items in the texture cache can either be standalone textures,
 // or a sub-rect inside the shared cache.
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 enum EntryKind {
     Standalone,
     Cache {
         // Origin within the texture layer where this item exists.
         origin: DeviceUintPoint,
         // The layer index of the texture array.
         layer_index: u16,
         // The region that this entry belongs to in the layer.
         region_index: u16,
     },
 }
 
 // Stores information related to a single entry in the texture
 // cache. This is stored for each item whether it's in the shared
 // cache or a standalone texture.
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct CacheEntry {
     // Size the requested item, in device pixels.
     size: DeviceUintSize,
     // Details specific to standalone or shared items.
     kind: EntryKind,
     // Arbitrary user data associated with this item.
     user_data: [f32; 3],
     // The last frame this item was requested for rendering.
@@ -152,28 +155,30 @@ type WeakCacheEntryHandle = WeakFreeList
 
 // A texture cache handle is a weak reference to a cache entry.
 // If the handle has not been inserted into the cache yet, the
 // value will be None. Even when the value is Some(), the location
 // may not actually be valid if it has been evicted by the cache.
 // In this case, the cache handle needs to re-upload this item
 // to the texture cache (see request() below).
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Clone, Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Clone, Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct TextureCacheHandle {
     entry: Option<WeakCacheEntryHandle>,
 }
 
 impl TextureCacheHandle {
     pub fn new() -> Self {
         TextureCacheHandle { entry: None }
     }
 }
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct TextureCache {
     // A lazily allocated, fixed size, texture array for
     // each format the texture cache supports.
     array_rgba8_nearest: TextureArray,
     array_a8_linear: TextureArray,
     array_rgba8_linear: TextureArray,
 
     // Maximum texture size supported by hardware.
@@ -182,17 +187,17 @@ pub struct TextureCache {
     // A list of texture IDs that represent native
     // texture handles. This indirection allows the texture
     // cache to create / destroy / reuse texture handles
     // without knowing anything about the device code.
     cache_textures: CacheTextureIdList,
 
     // A list of updates that need to be applied to the
     // texture cache in the rendering thread this frame.
-    #[cfg_attr(feature = "capture", serde(skip))]
+    #[cfg_attr(feature = "serde", serde(skip))]
     pending_updates: TextureUpdateList,
 
     // The current frame ID. Used for cache eviction policies.
     frame_id: FrameId,
 
     // Maintains the list of all current items in
     // the texture cache.
     entries: FreeList<CacheEntry>,
@@ -813,29 +818,31 @@ impl SlabSize {
             SlabSize::Size128x128 => 128,
             SlabSize::Size256x256 => 256,
             SlabSize::Size512x512 => 512,
         }
     }
 }
 
 // The x/y location within a texture region of an allocation.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct TextureLocation(u8, u8);
 
 impl TextureLocation {
     fn new(x: u32, y: u32) -> Self {
         debug_assert!(x < 0x100 && y < 0x100);
         TextureLocation(x as u8, y as u8)
     }
 }
 
 // A region is a sub-rect of a texture array layer.
 // All allocations within a region are of the same size.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct TextureRegion {
     layer_index: i32,
     region_size: u32,
     slab_size: u32,
     free_slots: Vec<TextureLocation>,
     slots_per_axis: u32,
     total_slot_count: usize,
     origin: DeviceUintPoint,
@@ -908,17 +915,18 @@ impl TextureRegion {
             self.deinit();
         }
     }
 }
 
 // A texture array contains a number of texture layers, where
 // each layer contains one or more regions that can act
 // as slab allocators.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct TextureArray {
     filter: TextureFilter,
     layer_count: usize,
     format: ImageFormat,
     is_allocated: bool,
     regions: Vec<TextureRegion>,
     texture_id: Option<CacheTextureId>,
 }
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -30,29 +30,31 @@ const MIN_TARGET_SIZE: u32 = 2048;
 #[derive(Debug)]
 pub struct ScrollbarPrimitive {
     pub clip_id: ClipId,
     pub prim_index: PrimitiveIndex,
     pub frame_rect: LayerRect,
 }
 
 #[derive(Debug, Copy, Clone)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTargetIndex(pub usize);
 
 pub struct RenderTargetContext<'a> {
     pub device_pixel_scale: DevicePixelScale,
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'a ResourceCache,
-    pub node_data: &'a [ClipScrollNodeData],
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub use_dual_source_blending: bool,
+    pub node_data: &'a [ClipScrollNodeData],
 }
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 struct TextureAllocator {
     // TODO(gw): Replace this with a simpler allocator for
     // render target allocation - this use case doesn't need
     // to deal with coalescing etc that the general texture
     // cache allocator requires.
     allocator: GuillotineAllocator,
 
     // Track the used rect of the render target, so that
@@ -116,29 +118,31 @@ pub trait RenderTarget {
         clip_store: &ClipStore,
         deferred_resolves: &mut Vec<DeferredResolve>,
     );
     fn used_rect(&self) -> DeviceIntRect;
     fn needs_depth(&self) -> bool;
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTargetKind {
     Color, // RGBA32
     Alpha, // R8
 }
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTargetList<T> {
     screen_size: DeviceIntSize,
     pub format: ImageFormat,
     pub max_size: DeviceUintSize,
     pub targets: Vec<T>,
-    #[cfg_attr(feature = "capture", serde(skip))]
+    #[cfg_attr(feature = "serde", serde(skip))]
     pub texture: Option<Texture>,
 }
 
 impl<T: RenderTarget> RenderTargetList<T> {
     fn new(
         screen_size: DeviceIntSize,
         format: ImageFormat,
     ) -> Self {
@@ -226,44 +230,49 @@ impl<T: RenderTarget> RenderTargetList<T
         }
     }
 }
 
 /// Frame output information for a given pipeline ID.
 /// Storing the task ID allows the renderer to find
 /// the target rect within the render target that this
 /// pipeline exists at.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameOutput {
     pub task_id: RenderTaskId,
     pub pipeline_id: PipelineId,
 }
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ScalingInfo {
     pub src_task_id: RenderTaskId,
     pub dest_task_id: RenderTaskId,
 }
 
 // Defines where the source data for a blit job can be found.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BlitJobSource {
     Texture(SourceTexture, i32, DeviceIntRect),
     RenderTask(RenderTaskId),
 }
 
 // Information required to do a blit from a source to a target.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BlitJob {
     pub source: BlitJobSource,
     pub target_rect: DeviceIntRect,
 }
 
 /// A render target represents a number of rendering operations on a surface.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ColorRenderTarget {
     pub alpha_batcher: AlphaBatcher,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurInstance>,
     pub horizontal_blurs: Vec<BlurInstance>,
     pub readbacks: Vec<DeviceIntRect>,
     pub scalings: Vec<ScalingInfo>,
     pub blits: Vec<BlitJob>,
@@ -436,17 +445,18 @@ impl RenderTarget for ColorRenderTarget 
             .used_rect
     }
 
     fn needs_depth(&self) -> bool {
         !self.alpha_batcher.batch_list.opaque_batch_list.batches.is_empty()
     }
 }
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct AlphaRenderTarget {
     pub clip_batcher: ClipBatcher,
     pub brush_mask_corners: Vec<PrimitiveInstance>,
     pub brush_mask_rounded_rects: Vec<PrimitiveInstance>,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurInstance>,
     pub horizontal_blurs: Vec<BlurInstance>,
     pub scalings: Vec<ScalingInfo>,
@@ -609,17 +619,18 @@ impl RenderTarget for AlphaRenderTarget 
         self.allocator.used_rect
     }
 
     fn needs_depth(&self) -> bool {
         false
     }
 }
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct TextureCacheRenderTarget {
     pub horizontal_blurs: Vec<BlurInstance>,
     pub blits: Vec<BlitJob>,
 }
 
 impl TextureCacheRenderTarget {
     fn new(
         _size: Option<DeviceUintSize>,
@@ -672,32 +683,34 @@ impl TextureCacheRenderTarget {
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) => {
                 panic!("BUG: unexpected task kind for texture cache target");
             }
         }
     }
 }
 
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderPassKind {
     MainFramebuffer(ColorRenderTarget),
     OffScreen {
         alpha: RenderTargetList<AlphaRenderTarget>,
         color: RenderTargetList<ColorRenderTarget>,
         texture_cache: FastHashMap<(SourceTexture, i32), TextureCacheRenderTarget>,
     },
 }
 
 /// A render pass represents a set of rendering operations that don't depend on one
 /// another.
 ///
 /// A render pass can have several render targets if there wasn't enough space in one
 /// target to do all of the rendering for that pass.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderPass {
     pub kind: RenderPassKind,
     tasks: Vec<RenderTaskId>,
 }
 
 impl RenderPass {
     pub fn new_main_framebuffer(screen_size: DeviceIntSize) -> Self {
         let target = ColorRenderTarget::new(None, screen_size);
@@ -854,26 +867,27 @@ impl CompositeOps {
 
     pub fn count(&self) -> usize {
         self.filters.len() + if self.mix_blend_mode.is_some() { 1 } else { 0 }
     }
 }
 
 /// A rendering-oriented representation of frame::Frame built by the render backend
 /// and presented to the renderer.
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct Frame {
     //TODO: share the fields with DocumentView struct
     pub window_size: DeviceUintSize,
     pub inner_rect: DeviceUintRect,
     pub background_color: Option<ColorF>,
     pub layer: DocumentLayer,
     pub device_pixel_ratio: f32,
     pub passes: Vec<RenderPass>,
-    #[cfg_attr(feature = "capture", serde(default = "FrameProfileCounters::new", skip))]
+    #[cfg_attr(any(feature = "capture", feature = "replay"), serde(default = "FrameProfileCounters::new", skip))]
     pub profile_counters: FrameProfileCounters,
 
     pub node_data: Vec<ClipScrollNodeData>,
     pub clip_chain_local_clip_rects: Vec<LayerRect>,
     pub render_tasks: RenderTaskTree,
 
     /// The GPU cache frame that the contents of Self depend on
     pub gpu_cache_frame_id: FrameId,
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -211,17 +211,18 @@ pub fn _subtract_rect<U>(
         None => {
             results.push(*rect);
         }
     }
 }
 
 #[repr(u32)]
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum TransformedRectKind {
     AxisAligned = 0,
     Complex = 1,
 }
 
 #[inline(always)]
 pub fn pack_as_float(value: u32) -> f32 {
     value as f32 + 0.5
--- a/gfx/webrender_api/Cargo.toml
+++ b/gfx/webrender_api/Cargo.toml
@@ -1,29 +1,30 @@
 [package]
 name = "webrender_api"
-version = "0.56.1"
+version = "0.57.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 
 [features]
 nightly = ["euclid/unstable", "serde/unstable"]
 ipc = ["ipc-channel"]
-debug-serialization = []
+serialize = []
+deserialize = []
 
 [dependencies]
 app_units = "0.6"
 bitflags = "1.0"
 bincode = "0.9"
 byteorder = "1.2.1"
 euclid = "0.16"
 ipc-channel = {version = "0.9", optional = true}
 serde = { version = "=1.0.27", features = ["rc"] }
 serde_derive = { version = "=1.0.27", features = ["deserialize_in_place"] }
 time = "0.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
-core-foundation = "0.4.6"
-core-graphics = "0.12.3"
+core-foundation = "0.5"
+core-graphics = "0.13"
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
new file mode 100644
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use {ColorF, FontInstanceKey, ImageKey, LayerPixel, LayoutPixel, LayoutPoint, LayoutRect,
      LayoutSize, LayoutTransform};
 use {GlyphOptions, LayoutVector2D, PipelineId, PropertyBinding};
 use euclid::{SideOffsets2D, TypedRect};
 use std::ops::Not;
 
-#[cfg(feature = "debug-serialization")]
+#[cfg(any(feature = "serialize", feature = "deserialize"))]
 use GlyphInstance;
 
 // 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)]
@@ -118,18 +118,19 @@ pub enum SpecificDisplayItem {
     SetGradientStops,
     PushShadow(Shadow),
     PopAllShadows,
 }
 
 /// This is a "complete" version of the DI specifics,
 /// containing the auxiliary data within the corresponding
 /// enumeration variants, to be used for debug serialization.
-#[cfg(feature = "debug-serialization")]
-#[derive(Deserialize, Serialize)]
+#[cfg(any(feature = "serialize", feature = "deserialize"))]
+#[cfg_attr(feature = "serialize", derive(Serialize))]
+#[cfg_attr(feature = "deserialize", derive(Deserialize))]
 pub enum CompletelySpecificDisplayItem {
     Clip(ClipDisplayItem, Vec<ComplexClipRegion>),
     ClipChain(ClipChainItem, Vec<ClipId>),
     ScrollFrame(ScrollFrameDisplayItem, Vec<ComplexClipRegion>),
     StickyFrame(StickyFrameDisplayItem),
     Rectangle(RectangleDisplayItem),
     ClearRectangle,
     Line(LineDisplayItem),
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -17,19 +17,19 @@ use bincode;
 use euclid::SideOffsets2D;
 use serde::{Deserialize, Serialize};
 use std::io::{Read, Write};
 use std::{io, mem, ptr};
 use std::marker::PhantomData;
 use std::slice;
 use time::precise_time_ns;
 
-#[cfg(feature = "debug-serialization")]
+#[cfg(feature = "deserialize")]
 use serde::de::Deserializer;
-#[cfg(feature = "debug-serialization")]
+#[cfg(feature = "serialize")]
 use serde::ser::{Serializer, SerializeSeq};
 
 // We don't want to push a long text-run. If a text-run is too long, split it into several parts.
 // This needs to be set to (renderer::MAX_VERTEX_TEXTURE_WIDTH - VECS_PER_PRIM_HEADER - VECS_PER_TEXT_RUN) * 2
 pub const MAX_TEXT_RUN_LENGTH: usize = 2038;
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
@@ -414,17 +414,17 @@ impl<'a, T: for<'de> Deserialize<'de>> I
     fn size_hint(&self) -> (usize, Option<usize>) {
         (self.size, Some(self.size))
     }
 }
 
 impl<'a, T: for<'de> Deserialize<'de>> ::std::iter::ExactSizeIterator for AuxIter<'a, T> {}
 
 
-#[cfg(feature = "debug-serialization")]
+#[cfg(feature = "serialize")]
 impl Serialize for BuiltDisplayList {
     fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
         use display_item::CompletelySpecificDisplayItem::*;
         use display_item::GenericDisplayItem;
 
         let mut seq = serializer.serialize_seq(None)?;
         let mut traversal = self.iter();
         while let Some(item) = traversal.next() {
@@ -477,17 +477,17 @@ impl Serialize for BuiltDisplayList {
         seq.end()
     }
 }
 
 // The purpose of this implementation is to deserialize
 // a display list from one format just to immediately
 // serialize then into a "built" `Vec<u8>`.
 
-#[cfg(feature = "debug-serialization")]
+#[cfg(feature = "deserialize")]
 impl<'de> Deserialize<'de> for BuiltDisplayList {
     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     where
         D: Deserializer<'de>,
     {
         use display_item::CompletelySpecificDisplayItem::*;
         use display_item::{CompletelySpecificDisplayItem, GenericDisplayItem};
 
--- a/gfx/webrender_api/src/font.rs
+++ b/gfx/webrender_api/src/font.rs
@@ -3,68 +3,67 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use {ColorU, IdNamespace, LayoutPoint};
 #[cfg(target_os = "macos")]
 use core_foundation::string::CFString;
 #[cfg(target_os = "macos")]
 use core_graphics::font::CGFont;
 #[cfg(target_os = "windows")]
-use dwrote::FontDescriptor;
+pub use dwrote::FontDescriptor as NativeFontHandle;
 #[cfg(target_os = "macos")]
 use serde::de::{self, Deserialize, Deserializer};
 #[cfg(target_os = "macos")]
 use serde::ser::{Serialize, Serializer};
 use std::cmp::Ordering;
 use std::hash::{Hash, Hasher};
 use std::sync::Arc;
 
 
+#[cfg(not(any(target_os = "macos", target_os = "windows")))]
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct NativeFontHandle {
+    pub pathname: String,
+    pub index: u32,
+}
+
 #[cfg(target_os = "macos")]
 #[derive(Clone)]
 pub struct NativeFontHandle(pub CGFont);
 
 #[cfg(target_os = "macos")]
 impl Serialize for NativeFontHandle {
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
     {
-        let postscript_name = self.0.postscript_name().to_string();
-        postscript_name.serialize(serializer)
+        self.0
+            .postscript_name()
+            .to_string()
+            .serialize(serializer)
     }
 }
 
 #[cfg(target_os = "macos")]
 impl<'de> Deserialize<'de> for NativeFontHandle {
     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     where
         D: Deserializer<'de>,
     {
-        let postscript_name: String = try!(Deserialize::deserialize(deserializer));
+        let postscript_name: String = Deserialize::deserialize(deserializer)?;
 
         match CGFont::from_name(&CFString::new(&*postscript_name)) {
             Ok(font) => Ok(NativeFontHandle(font)),
-            _ => Err(de::Error::custom(
+            Err(_) => Err(de::Error::custom(
                 "Couldn't find a font with that PostScript name!",
             )),
         }
     }
 }
 
-#[cfg(not(any(target_os = "macos", target_os = "windows")))]
-#[derive(Clone, Serialize, Deserialize)]
-pub struct NativeFontHandle {
-    pub pathname: String,
-    pub index: u32,
-}
-
-#[cfg(target_os = "windows")]
-pub type NativeFontHandle = FontDescriptor;
-
 #[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,
@@ -198,17 +197,17 @@ impl Hash for FontVariation {
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)]
 pub struct GlyphOptions {
     pub render_mode: FontRenderMode,
     pub flags: FontInstanceFlags,
 }
 
 impl Default for GlyphOptions {
-    fn default() -> GlyphOptions {
+    fn default() -> Self {
         GlyphOptions {
             render_mode: FontRenderMode::Subpixel,
             flags: FontInstanceFlags::empty(),
         }
     }
 }
 
 bitflags! {
--- a/gfx/webrender_bindings/Cargo.toml
+++ b/gfx/webrender_bindings/Cargo.toml
@@ -9,20 +9,20 @@ rayon = "0.8"
 thread_profiler = "0.1.1"
 euclid = "0.16"
 app_units = "0.6"
 gleam = "0.4.20"
 log = "0.3"
 
 [dependencies.webrender]
 path = "../webrender"
-version = "0.56.1"
+version = "0.57.0"
 default-features = false
 features = ["capture"]
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
-core-foundation = "0.4.6"
-core-graphics = "0.12.3"
+core-foundation = "0.5"
+core-graphics = "0.13"
 foreign-types = "0.3.0"
 
--- a/gfx/wrench/Cargo.toml
+++ b/gfx/wrench/Cargo.toml
@@ -1,44 +1,44 @@
 [package]
 name = "wrench"
-version = "0.2.6"
+version = "0.3.0"
 authors = ["Vladimir Vukicevic <vladimir@pobox.com>"]
 build = "build.rs"
 license = "MPL-2.0"
 
 [dependencies]
 base64 = "0.3"
 bincode = "0.9"
 byteorder = "1.0"
 env_logger = { version = "0.4", optional = true }
 euclid = "0.16"
 gleam = "0.4"
-servo-glutin = "0.13"
+servo-glutin = "0.14"
 app_units = "0.6"
 image = "0.17"
 clap = { version = "2", features = ["yaml"] }
 lazy_static = "1"
 log = "0.3"
 yaml-rust = { git = "https://github.com/vvuk/yaml-rust", features = ["preserve_order"] }
 serde_json = "1.0"
 ron = "0.1.5"
 time = "0.1"
 crossbeam = "0.2"
 osmesa-sys = { version = "0.1.2", optional = true }
 osmesa-src = { git = "https://github.com/servo/osmesa-src", optional = true }
-webrender = {path = "../webrender", features=["capture","debugger","png","profiler"]}
-webrender_api = {path = "../webrender_api", features=["debug-serialization"]}
+webrender = {path = "../webrender", features=["capture","replay","debugger","png","profiler"]}
+webrender_api = {path = "../webrender_api", features=["serialize","deserialize"]}
 serde = {version = "1.0", features = ["derive"] }
 
 [target.'cfg(target_os = "macos")'.dependencies]
-core-graphics = "0.12.4"
-core-foundation = "0.4"
+core-graphics = "0.13"
+core-foundation = "0.5"
 
 [features]
 headless = [ "osmesa-sys", "osmesa-src" ]
 logging = [ "env_logger" ]
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
 
 [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
-font-loader = "0.5"
+font-loader = "0.6"
--- a/gfx/wrench/src/cgfont_to_data.rs
+++ b/gfx/wrench/src/cgfont_to_data.rs
@@ -53,17 +53,18 @@ pub fn font_to_data(font: CGFont) -> Res
     let mut cff = false;
     let tags = font.copy_table_tags();
     let count = tags.len() as u16;
 
     let mut records = Vec::new();
     let mut offset: u32 = 0;
     offset += 4 * 3;
     offset += 4 * 4 * (count as u32);
-    for tag in tags.iter() {
+    for tag_ref in tags.iter() {
+        let tag = *tag_ref;
         if tag == CFF_TAG {
             cff = true;
         }
         let data = font.copy_table_for_tag(tag).unwrap();
         let length = data.len() as u32;
         let checksum;
         if tag == HEAD_TAG {
             // we need to skip the checksum field
--- a/gfx/wrench/src/rawtest.rs
+++ b/gfx/wrench/src/rawtest.rs
@@ -524,19 +524,17 @@ impl<'a> RawtestHarness<'a> {
         );
         txn.generate_frame();
 
         self.wrench.api.send_transaction(self.wrench.document_id, txn);
 
         let pixels0 = self.render_and_get_pixels(window_rect);
 
         // 2. capture it
-
         self.wrench.api.save_capture(path.into(), CaptureBits::all());
-        self.rx.recv().unwrap();
 
         // 3. set a different scene
 
         builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let mut txn = Transaction::new();
         txn.set_display_list(
             Epoch(1),
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -64,56 +64,56 @@ impl NotifierData {
             timing_receiver,
             verbose,
         }
     }
 }
 
 struct Notifier(Arc<Mutex<NotifierData>>);
 
-impl RenderNotifier for Notifier {
-    fn clone(&self) -> Box<RenderNotifier> {
-        Box::new(Notifier(self.0.clone()))
-    }
-
-    fn wake_up(&self) {
+impl Notifier {
+    fn update(&self, check_document: bool) {
         let mut data = self.0.lock();
         let data = data.as_mut().unwrap();
-        match data.timing_receiver.steal() {
-            chase_lev::Steal::Data(last_timing) => {
-                data.frames_notified += 1;
-                if data.verbose && data.frames_notified == 600 {
-                    let elapsed = time::SteadyTime::now() - last_timing;
-                    println!(
-                        "frame latency (consider queue depth here): {:3.6} ms",
-                        elapsed.num_microseconds().unwrap() as f64 / 1000.
-                    );
-                    data.frames_notified = 0;
+        if check_document {
+            match data.timing_receiver.steal() {
+                chase_lev::Steal::Data(last_timing) => {
+                    data.frames_notified += 1;
+                    if data.verbose && data.frames_notified == 600 {
+                        let elapsed = time::SteadyTime::now() - last_timing;
+                        println!(
+                            "frame latency (consider queue depth here): {:3.6} ms",
+                            elapsed.num_microseconds().unwrap() as f64 / 1000.
+                        );
+                        data.frames_notified = 0;
+                    }
                 }
-            }
-            _ => {
-                println!("Notified of frame, but no frame was ready?");
+                _ => {
+                    println!("Notified of frame, but no frame was ready?");
+                }
             }
         }
         if let Some(ref window_proxy) = data.window_proxy {
             #[cfg(not(target_os = "android"))]
             window_proxy.wakeup_event_loop();
         }
     }
+}
+
+impl RenderNotifier for Notifier {
+    fn clone(&self) -> Box<RenderNotifier> {
+        Box::new(Notifier(self.0.clone()))
+    }
+
+    fn wake_up(&self) {
+        self.update(false);
+    }
 
     fn new_document_ready(&self, _: DocumentId, scrolled: bool, _composite_needed: bool) {
-        if scrolled {
-            let data = self.0.lock();
-            if let Some(ref window_proxy) = data.unwrap().window_proxy {
-                #[cfg(not(target_os = "android"))]
-                window_proxy.wakeup_event_loop();
-            }
-        } else {
-            self.wake_up();
-        }
+        self.update(!scrolled);
     }
 }
 
 pub trait WrenchThing {
     fn next_frame(&mut self);
     fn prev_frame(&mut self);
     fn do_frame(&mut self, &mut Wrench) -> u32;
     fn queue_frames(&self) -> u32 {