servo: Merge #17326 - Implement paint worklets rendering context (from asajeffrey:script-paint-worklets-rendering-context); r=jdm
authorAlan Jeffrey <ajeffrey@mozilla.com>
Fri, 30 Jun 2017 15:40:26 -0700
changeset 603154 e2a58b831fd5b97f913016d58631293ec5c7cc9b
parent 603153 036f834ff1294a31008c1b57a558904e83a66a08
child 603155 73a5b3d5f996574f737f7e52fca1c1b7259196df
push id66676
push userdgottwald@mozilla.com
push dateSun, 02 Jul 2017 09:54:00 +0000
reviewersjdm
milestone56.0a1
servo: Merge #17326 - Implement paint worklets rendering context (from asajeffrey:script-paint-worklets-rendering-context); r=jdm <!-- Please describe your changes on the following line: --> Implement the rendering context for paint worklets. They really paint things now! --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes do not require tests because the existing reftest now passes <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: a9fea0653a25d5f26f1115203015c5cf699f5227
servo/components/layout/display_list_builder.rs
servo/components/script/dom/bindings/refcounted.rs
servo/components/script/dom/canvasrenderingcontext2d.rs
servo/components/script/dom/paintrenderingcontext2d.rs
servo/components/script/dom/paintworkletglobalscope.rs
servo/components/script/dom/webidls/CanvasGradient.webidl
servo/components/script/dom/webidls/CanvasPattern.webidl
servo/components/script/dom/webidls/CanvasRenderingContext2D.webidl
servo/components/script/dom/webidls/PaintRenderingContext2D.webidl
servo/components/script/dom/window.rs
servo/components/script/dom/worklet.rs
servo/components/script/dom/workletglobalscope.rs
servo/components/script/script_thread.rs
servo/components/script_traits/lib.rs
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -1169,31 +1169,40 @@ impl FragmentDisplayListBuilding for Fra
         let executor = match state.layout_context.paint_worklet_executor {
             Some(ref executor) => executor,
             None => return debug!("Worklet {} called before any paint modules are added.", name),
         };
 
         // TODO: add a one-place cache to avoid drawing the paint image every time.
         // https://github.com/servo/servo/issues/17369
         debug!("Drawing a paint image {}({},{}).", name, size.width.to_px(), size.height.to_px());
-        let mut image = match executor.draw_a_paint_image(name, size) {
-            Ok(image) => image,
-            Err(err) => return warn!("Error running paint worklet ({:?}).", err),
+        let (sender, receiver) = ipc::channel().unwrap();
+        executor.draw_a_paint_image(name, size, sender);
+
+        // TODO: timeout
+        let webrender_image = match receiver.recv() {
+            Ok(CanvasData::Image(canvas_data)) => {
+                WebRenderImageInfo {
+                    // TODO: it would be nice to get this data back from the canvas
+                    width: size.width.to_px().abs() as u32,
+                    height: size.height.to_px().abs() as u32,
+                    format: PixelFormat::BGRA8,
+                    key: Some(canvas_data.image_key),
+                }
+            },
+            Ok(CanvasData::WebGL(_)) => return warn!("Paint worklet generated WebGL."),
+            Err(err) => return warn!("Paint worklet recv generated error ({}).", err),
         };
 
-        // Make sure the image has a webrender key.
-        state.layout_context.image_cache.set_webrender_image_key(&mut image);
-
-        debug!("Drew a paint image ({},{}).", image.width, image.height);
         self.build_display_list_for_webrender_image(state,
                                                     style,
                                                     display_list_section,
                                                     absolute_bounds,
                                                     clip,
-                                                    WebRenderImageInfo::from_image(&image),
+                                                    webrender_image,
                                                     index);
     }
 
     fn convert_linear_gradient(&self,
                                bounds: &Rect<Au>,
                                stops: &[GradientItem],
                                direction: &LineDirection,
                                repeating: bool)
--- a/servo/components/script/dom/bindings/refcounted.rs
+++ b/servo/components/script/dom/bindings/refcounted.rs
@@ -122,16 +122,17 @@ impl TrustedPromise {
     }
 
     /// A runnable which will reject the promise.
     #[allow(unrooted_must_root)]
     pub fn reject_runnable(self, error: Error) -> impl Runnable + Send {
         struct RejectPromise(TrustedPromise, Error);
         impl Runnable for RejectPromise {
             fn main_thread_handler(self: Box<Self>, script_thread: &ScriptThread) {
+                debug!("Rejecting promise.");
                 let this = *self;
                 let cx = script_thread.get_cx();
                 let promise = this.0.root();
                 let _ac = JSAutoCompartment::new(cx, promise.reflector().get_jsobject().get());
                 promise.reject_error(cx, this.1);
             }
         }
         RejectPromise(self, error)
@@ -140,16 +141,17 @@ impl TrustedPromise {
     /// A runnable which will resolve the promise.
     #[allow(unrooted_must_root)]
     pub fn resolve_runnable<T>(self, value: T) -> impl Runnable + Send where
         T: ToJSValConvertible + Send
     {
         struct ResolvePromise<T>(TrustedPromise, T);
         impl<T: ToJSValConvertible> Runnable for ResolvePromise<T> {
             fn main_thread_handler(self: Box<Self>, script_thread: &ScriptThread) {
+                debug!("Resolving promise.");
                 let this = *self;
                 let cx = script_thread.get_cx();
                 let promise = this.0.root();
                 let _ac = JSAutoCompartment::new(cx, promise.reflector().get_jsobject().get());
                 promise.resolve_native(cx, &this.1);
             }
         }
         ResolvePromise(self, value)
--- a/servo/components/script/dom/canvasrenderingcontext2d.rs
+++ b/servo/components/script/dom/canvasrenderingcontext2d.rs
@@ -56,17 +56,19 @@ enum CanvasFillOrStrokeStyle {
 }
 
 // https://html.spec.whatwg.org/multipage/#canvasrenderingcontext2d
 #[dom_struct]
 pub struct CanvasRenderingContext2D {
     reflector_: Reflector,
     #[ignore_heap_size_of = "Defined in ipc-channel"]
     ipc_renderer: IpcSender<CanvasMsg>,
-    canvas: JS<HTMLCanvasElement>,
+    // For rendering contexts created by an HTML canvas element, this is Some,
+    // for ones created by a paint worklet, this is None.
+    canvas: Option<JS<HTMLCanvasElement>>,
     state: DOMRefCell<CanvasContextState>,
     saved_states: DOMRefCell<Vec<CanvasContextState>>,
     origin_clean: Cell<bool>,
 }
 
 #[must_root]
 #[derive(JSTraceable, Clone, HeapSizeOf)]
 struct CanvasContextState {
@@ -104,39 +106,42 @@ impl CanvasContextState {
             shadow_offset_y: 0.0,
             shadow_blur: 0.0,
             shadow_color: RGBA::transparent(),
         }
     }
 }
 
 impl CanvasRenderingContext2D {
-    fn new_inherited(global: &GlobalScope,
-                     canvas: &HTMLCanvasElement,
-                     size: Size2D<i32>)
-                     -> CanvasRenderingContext2D {
+    pub fn new_inherited(global: &GlobalScope,
+                         canvas: Option<&HTMLCanvasElement>,
+                         size: Size2D<i32>)
+                         -> CanvasRenderingContext2D {
+        debug!("Creating new canvas rendering context.");
         let (sender, receiver) = ipc::channel().unwrap();
         let constellation_chan = global.constellation_chan();
+        debug!("Asking constellation to create new canvas thread.");
         constellation_chan.send(ConstellationMsg::CreateCanvasPaintThread(size, sender)).unwrap();
         let ipc_renderer = receiver.recv().unwrap();
+        debug!("Done.");
         CanvasRenderingContext2D {
             reflector_: Reflector::new(),
             ipc_renderer: ipc_renderer,
-            canvas: JS::from_ref(canvas),
+            canvas: canvas.map(JS::from_ref),
             state: DOMRefCell::new(CanvasContextState::new()),
             saved_states: DOMRefCell::new(Vec::new()),
             origin_clean: Cell::new(true),
         }
     }
 
     pub fn new(global: &GlobalScope,
                canvas: &HTMLCanvasElement,
                size: Size2D<i32>)
                -> Root<CanvasRenderingContext2D> {
-        reflect_dom_object(box CanvasRenderingContext2D::new_inherited(global, canvas, size),
+        reflect_dom_object(box CanvasRenderingContext2D::new_inherited(global, Some(canvas), size),
                            global,
                            CanvasRenderingContext2DBinding::Wrap)
     }
 
     // https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions
     pub fn set_bitmap_dimensions(&self, size: Size2D<i32>) {
         self.reset_to_initial_state();
         self.ipc_renderer
@@ -150,17 +155,19 @@ impl CanvasRenderingContext2D {
         *self.state.borrow_mut() = CanvasContextState::new();
     }
 
     pub fn ipc_renderer(&self) -> IpcSender<CanvasMsg> {
         self.ipc_renderer.clone()
     }
 
     fn mark_as_dirty(&self) {
-        self.canvas.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
+        if let Some(ref canvas) = self.canvas {
+            canvas.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
+        }
     }
 
     fn update_transform(&self) {
         self.ipc_renderer
             .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetTransform(self.state.borrow().transform)))
             .unwrap()
     }
 
@@ -221,18 +228,22 @@ impl CanvasRenderingContext2D {
                            -> bool {
         match image {
             HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::HTMLCanvasElement(canvas) => {
                 canvas.origin_is_clean()
             }
             HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::CanvasRenderingContext2D(image) =>
                 image.origin_is_clean(),
             HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::HTMLImageElement(image) => {
+                let canvas = match self.canvas {
+                    Some(ref canvas) => canvas,
+                    None => return false,
+                };
                 let image_origin = image.get_origin().expect("Image's origin is missing");
-                let document = document_from_node(&*self.canvas);
+                let document = document_from_node(&**canvas);
                 document.url().clone().origin() == image_origin
             }
         }
     }
 
     //
     // drawImage coordinates explained
     //
@@ -342,17 +353,17 @@ impl CanvasRenderingContext2D {
                                                                      dh);
 
         if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
             return Ok(());
         }
 
         let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
 
-        if &*self.canvas == canvas {
+        if self.canvas.as_ref().map_or(false, |c| &**c == canvas) {
             let msg = CanvasMsg::Canvas2d(Canvas2dMsg::DrawImageSelf(
                 image_size, dest_rect, source_rect, smoothing_enabled));
             self.ipc_renderer.send(msg).unwrap();
         } else {
             let context = match canvas.get_or_init_2d_context() {
                 Some(context) => context,
                 None => return Err(Error::InvalidState),
             };
@@ -437,18 +448,20 @@ impl CanvasRenderingContext2D {
             PixelFormat::KA8 => panic!("KA8 color type not supported"),
         };
 
         Some((image_data, image_size))
     }
 
     #[inline]
     fn request_image_from_cache(&self, url: ServoUrl) -> ImageResponse {
-        let window = window_from_node(&*self.canvas);
-        canvas_utils::request_image_from_cache(&window, url)
+        self.canvas.as_ref()
+            .map(|canvas| window_from_node(&**canvas))
+            .map(|window| canvas_utils::request_image_from_cache(&window, url))
+            .unwrap_or(ImageResponse::None)
     }
 
     fn create_drawable_rect(&self, x: f64, y: f64, w: f64, h: f64) -> Option<Rect<f32>> {
         if !([x, y, w, h].iter().all(|val| val.is_finite())) {
             return None;
         }
 
         if w == 0.0 && h == 0.0 {
@@ -467,22 +480,30 @@ impl CanvasRenderingContext2D {
             match color {
                 Ok(CSSColor::RGBA(rgba)) => Ok(rgba),
                 Ok(CSSColor::CurrentColor) => {
                     // TODO: https://github.com/whatwg/html/issues/1099
                     // Reconsider how to calculate currentColor in a display:none canvas
 
                     // TODO: will need to check that the context bitmap mode is fixed
                     // once we implement CanvasProxy
-                    let window = window_from_node(&*self.canvas);
+                    let canvas = match self.canvas {
+                        // https://drafts.css-houdini.org/css-paint-api/#2d-rendering-context
+                        // Whenever "currentColor" is used as a color in the PaintRenderingContext2D API,
+                        // it is treated as opaque black.
+                        None => return Ok(RGBA::new(0, 0, 0, 255)),
+                        Some(ref canvas) => &**canvas,
+                    };
 
-                    let style = window.GetComputedStyle(&*self.canvas.upcast(), None);
+                    let window = window_from_node(canvas);
+
+                    let style = window.GetComputedStyle(canvas.upcast(), None);
 
                     let element_not_rendered =
-                        !self.canvas.upcast::<Node>().is_in_doc() ||
+                        !canvas.upcast::<Node>().is_in_doc() ||
                         style.GetPropertyValue(DOMString::from("display")) == "none";
 
                     if element_not_rendered {
                         Ok(RGBA::new(0, 0, 0, 255))
                     } else {
                         self.parse_color(&style.GetPropertyValue(DOMString::from("color")))
                     }
                 },
@@ -525,17 +546,19 @@ impl LayoutCanvasRenderingContext2DHelpe
 // > any method call with a numeric argument whose value is infinite or a NaN value must be ignored.
 //
 //  Restricted values are guarded in glue code. Therefore we need not add a guard.
 //
 // FIXME: this behavior should might be generated by some annotattions to idl.
 impl CanvasRenderingContext2DMethods for CanvasRenderingContext2D {
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-canvas
     fn Canvas(&self) -> Root<HTMLCanvasElement> {
-        Root::from_ref(&*self.canvas)
+        // This method is not called from a paint worklet rendering context,
+        // so it's OK to panic if self.canvas is None.
+        Root::from_ref(self.canvas.as_ref().expect("No canvas."))
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-save
     fn Save(&self) {
         self.saved_states.borrow_mut().push(self.state.borrow().clone());
         self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::SaveContext)).unwrap();
     }
 
@@ -1032,17 +1055,17 @@ impl CanvasRenderingContext2DMethods for
         }
 
         let sh = cmp::max(1, sh.to_u32().unwrap());
         let sw = cmp::max(1, sw.to_u32().unwrap());
 
         let (sender, receiver) = ipc::channel::<Vec<u8>>().unwrap();
         let dest_rect = Rect::new(Point2D::new(sx.to_i32().unwrap(), sy.to_i32().unwrap()),
                                   Size2D::new(sw as i32, sh as i32));
-        let canvas_size = self.canvas.get_size();
+        let canvas_size = self.canvas.as_ref().map(|c| c.get_size()).unwrap_or(Size2D::zero());
         let canvas_size = Size2D::new(canvas_size.width as f64, canvas_size.height as f64);
         self.ipc_renderer
             .send(CanvasMsg::Canvas2d(Canvas2dMsg::GetImageData(dest_rect, canvas_size, sender)))
             .unwrap();
         let mut data = receiver.recv().unwrap();
 
         // Un-premultiply alpha
         for chunk in data.chunks_mut(4) {
--- a/servo/components/script/dom/paintrenderingcontext2d.rs
+++ b/servo/components/script/dom/paintrenderingcontext2d.rs
@@ -1,29 +1,378 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+use app_units::Au;
+use canvas_traits::CanvasData;
+use canvas_traits::CanvasMsg;
+use canvas_traits::FromLayoutMsg;
+use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasFillRule;
+use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap;
+use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineJoin;
+use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods;
 use dom::bindings::codegen::Bindings::PaintRenderingContext2DBinding;
+use dom::bindings::codegen::Bindings::PaintRenderingContext2DBinding::PaintRenderingContext2DMethods;
+use dom::bindings::codegen::UnionTypes::HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D;
+use dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern;
+use dom::bindings::error::ErrorResult;
+use dom::bindings::error::Fallible;
+use dom::bindings::inheritance::Castable;
 use dom::bindings::js::Root;
-use dom::bindings::reflector::Reflector;
+use dom::bindings::num::Finite;
 use dom::bindings::reflector::reflect_dom_object;
+use dom::bindings::str::DOMString;
+use dom::canvasgradient::CanvasGradient;
+use dom::canvaspattern::CanvasPattern;
+use dom::canvasrenderingcontext2d::CanvasRenderingContext2D;
 use dom::paintworkletglobalscope::PaintWorkletGlobalScope;
 use dom_struct::dom_struct;
+use euclid::Size2D;
+use ipc_channel::ipc::IpcSender;
 
 #[dom_struct]
 pub struct PaintRenderingContext2D {
-    reflector: Reflector,
+    context: CanvasRenderingContext2D,
 }
 
 impl PaintRenderingContext2D {
-    fn new_inherited() -> PaintRenderingContext2D {
+    fn new_inherited(global: &PaintWorkletGlobalScope) -> PaintRenderingContext2D {
+        let size = Size2D::zero();
         PaintRenderingContext2D {
-            reflector: Reflector::new(),
+            context: CanvasRenderingContext2D::new_inherited(global.upcast(), None, size),
         }
     }
 
     pub fn new(global: &PaintWorkletGlobalScope) -> Root<PaintRenderingContext2D> {
-        reflect_dom_object(box PaintRenderingContext2D::new_inherited(),
+        reflect_dom_object(box PaintRenderingContext2D::new_inherited(global),
                            global,
                            PaintRenderingContext2DBinding::Wrap)
     }
+
+    pub fn send_data(&self, sender: IpcSender<CanvasData>) {
+        let msg = CanvasMsg::FromLayout(FromLayoutMsg::SendData(sender));
+        let _ = self.context.ipc_renderer().send(msg);
+    }
+
+    pub fn set_bitmap_dimensions(&self, size: Size2D<Au>) {
+        let size = Size2D::new(size.width.to_px(), size.height.to_px());
+        self.context.set_bitmap_dimensions(size);
+    }
 }
+
+impl PaintRenderingContext2DMethods for PaintRenderingContext2D {
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-save
+    fn Save(&self) {
+        self.context.Save()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-restore
+    fn Restore(&self) {
+        self.context.Restore()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-scale
+    fn Scale(&self, x: f64, y: f64) {
+        self.context.Scale(x, y)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-rotate
+    fn Rotate(&self, angle: f64) {
+        self.context.Rotate(angle)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-translate
+    fn Translate(&self, x: f64, y: f64) {
+        self.context.Translate(x, y)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-transform
+    fn Transform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
+        self.context.Transform(a, b, c, d, e, f)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-settransform
+    fn SetTransform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
+        self.context.SetTransform(a, b, c, d, e, f)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-resettransform
+    fn ResetTransform(&self) {
+        self.context.ResetTransform()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalalpha
+    fn GlobalAlpha(&self) -> f64 {
+        self.context.GlobalAlpha()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalalpha
+    fn SetGlobalAlpha(&self, alpha: f64) {
+        self.context.SetGlobalAlpha(alpha)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalcompositeoperation
+    fn GlobalCompositeOperation(&self) -> DOMString {
+        self.context.GlobalCompositeOperation()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalcompositeoperation
+    fn SetGlobalCompositeOperation(&self, op_str: DOMString) {
+        self.context.SetGlobalCompositeOperation(op_str)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-fillrect
+    fn FillRect(&self, x: f64, y: f64, width: f64, height: f64) {
+        self.context.FillRect(x, y, width, height)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-clearrect
+    fn ClearRect(&self, x: f64, y: f64, width: f64, height: f64) {
+        self.context.ClearRect(x, y, width, height)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokerect
+    fn StrokeRect(&self, x: f64, y: f64, width: f64, height: f64) {
+        self.context.StrokeRect(x, y, width, height)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-beginpath
+    fn BeginPath(&self) {
+        self.context.BeginPath()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-closepath
+    fn ClosePath(&self) {
+        self.context.ClosePath()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-fill
+    fn Fill(&self, fill_rule: CanvasFillRule) {
+        self.context.Fill(fill_rule)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke
+    fn Stroke(&self) {
+        self.context.Stroke()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
+    fn Clip(&self, fill_rule: CanvasFillRule) {
+        self.context.Clip(fill_rule)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath
+    fn IsPointInPath(&self, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool {
+        self.context.IsPointInPath(x, y, fill_rule)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
+    fn DrawImage(&self,
+                 image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
+                 dx: f64,
+                 dy: f64)
+                 -> ErrorResult {
+        self.context.DrawImage(image, dx, dy)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
+    fn DrawImage_(&self,
+                  image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
+                  dx: f64,
+                  dy: f64,
+                  dw: f64,
+                  dh: f64)
+                  -> ErrorResult {
+        self.context.DrawImage_(image, dx, dy, dw, dh)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
+    fn DrawImage__(&self,
+                   image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
+                   sx: f64,
+                   sy: f64,
+                   sw: f64,
+                   sh: f64,
+                   dx: f64,
+                   dy: f64,
+                   dw: f64,
+                   dh: f64)
+                   -> ErrorResult {
+        self.context.DrawImage__(image, sx, sy, sw, sh, dx, dy, dw, dh)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-moveto
+    fn MoveTo(&self, x: f64, y: f64) {
+        self.context.MoveTo(x, y)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-lineto
+    fn LineTo(&self, x: f64, y: f64) {
+        self.context.LineTo(x, y)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-rect
+    fn Rect(&self, x: f64, y: f64, width: f64, height: f64) {
+        self.context.Rect(x, y, width, height)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-quadraticcurveto
+    fn QuadraticCurveTo(&self, cpx: f64, cpy: f64, x: f64, y: f64) {
+        self.context.QuadraticCurveTo(cpx, cpy, x, y)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-beziercurveto
+    fn BezierCurveTo(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) {
+        self.context.BezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-arc
+    fn Arc(&self, x: f64, y: f64, r: f64, start: f64, end: f64, ccw: bool) -> ErrorResult {
+        self.context.Arc(x, y, r, start, end, ccw)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-arcto
+    fn ArcTo(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, r: f64) -> ErrorResult {
+        self.context.ArcTo(cp1x, cp1y, cp2x, cp2y, r)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-imagesmoothingenabled
+    fn ImageSmoothingEnabled(&self) -> bool {
+        self.context.ImageSmoothingEnabled()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-imagesmoothingenabled
+    fn SetImageSmoothingEnabled(&self, value: bool) {
+        self.context.SetImageSmoothingEnabled(value)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle
+    fn StrokeStyle(&self) -> StringOrCanvasGradientOrCanvasPattern {
+        self.context.StrokeStyle()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle
+    fn SetStrokeStyle(&self, value: StringOrCanvasGradientOrCanvasPattern) {
+        self.context.SetStrokeStyle(value)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle
+    fn FillStyle(&self) -> StringOrCanvasGradientOrCanvasPattern {
+        self.context.FillStyle()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle
+    fn SetFillStyle(&self, value: StringOrCanvasGradientOrCanvasPattern) {
+        self.context.SetFillStyle(value)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-createlineargradient
+    fn CreateLinearGradient(&self,
+                            x0: Finite<f64>,
+                            y0: Finite<f64>,
+                            x1: Finite<f64>,
+                            y1: Finite<f64>)
+                            -> Root<CanvasGradient> {
+        self.context.CreateLinearGradient(x0, y0, x1, y1)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-createradialgradient
+    fn CreateRadialGradient(&self,
+                            x0: Finite<f64>,
+                            y0: Finite<f64>,
+                            r0: Finite<f64>,
+                            x1: Finite<f64>,
+                            y1: Finite<f64>,
+                            r1: Finite<f64>)
+                            -> Fallible<Root<CanvasGradient>> {
+        self.context.CreateRadialGradient(x0, y0, r0, x1, y1, r1)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-createpattern
+    fn CreatePattern(&self,
+                     image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
+                     repetition: DOMString)
+                     -> Fallible<Root<CanvasPattern>> {
+        self.context.CreatePattern(image, repetition)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth
+    fn LineWidth(&self) -> f64 {
+        self.context.LineWidth()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth
+    fn SetLineWidth(&self, width: f64) {
+        self.context.SetLineWidth(width)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap
+    fn LineCap(&self) -> CanvasLineCap {
+        self.context.LineCap()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap
+    fn SetLineCap(&self, cap: CanvasLineCap) {
+        self.context.SetLineCap(cap)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin
+    fn LineJoin(&self) -> CanvasLineJoin {
+        self.context.LineJoin()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin
+    fn SetLineJoin(&self, join: CanvasLineJoin) {
+        self.context.SetLineJoin(join)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit
+    fn MiterLimit(&self) -> f64 {
+        self.context.MiterLimit()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit
+    fn SetMiterLimit(&self, limit: f64) {
+        self.context.SetMiterLimit(limit)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsetx
+    fn ShadowOffsetX(&self) -> f64 {
+        self.context.ShadowOffsetX()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsetx
+    fn SetShadowOffsetX(&self, value: f64) {
+        self.context.SetShadowOffsetX(value)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety
+    fn ShadowOffsetY(&self) -> f64 {
+        self.context.ShadowOffsetY()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety
+    fn SetShadowOffsetY(&self, value: f64) {
+        self.context.SetShadowOffsetY(value)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur
+    fn ShadowBlur(&self) -> f64 {
+        self.context.ShadowBlur()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur
+    fn SetShadowBlur(&self, value: f64) {
+        self.context.SetShadowBlur(value)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor
+    fn ShadowColor(&self) -> DOMString {
+        self.context.ShadowColor()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor
+    fn SetShadowColor(&self, value: DOMString) {
+        self.context.SetShadowColor(value)
+    }
+
+}
--- a/servo/components/script/dom/paintworkletglobalscope.rs
+++ b/servo/components/script/dom/paintworkletglobalscope.rs
@@ -1,32 +1,36 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
+use canvas_traits::CanvasData;
+use canvas_traits::CanvasImageData;
 use dom::bindings::callback::CallbackContainer;
 use dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::PaintWorkletGlobalScopeBinding;
 use dom::bindings::codegen::Bindings::PaintWorkletGlobalScopeBinding::PaintWorkletGlobalScopeMethods;
 use dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction;
 use dom::bindings::conversions::StringificationBehavior;
 use dom::bindings::conversions::get_property;
 use dom::bindings::conversions::get_property_jsval;
 use dom::bindings::error::Error;
 use dom::bindings::error::Fallible;
+use dom::bindings::js::JS;
 use dom::bindings::js::Root;
 use dom::bindings::reflector::DomObject;
 use dom::bindings::str::DOMString;
 use dom::paintrenderingcontext2d::PaintRenderingContext2D;
 use dom::paintsize::PaintSize;
 use dom::workletglobalscope::WorkletGlobalScope;
 use dom::workletglobalscope::WorkletGlobalScopeInit;
 use dom_struct::dom_struct;
 use euclid::Size2D;
+use ipc_channel::ipc::IpcSender;
 use ipc_channel::ipc::IpcSharedMemory;
 use js::jsapi::Call;
 use js::jsapi::Construct1;
 use js::jsapi::HandleValue;
 use js::jsapi::HandleValueArray;
 use js::jsapi::Heap;
 use js::jsapi::IsCallable;
 use js::jsapi::IsConstructor;
@@ -35,108 +39,106 @@ use js::jsapi::JS_ClearPendingException;
 use js::jsapi::JS_IsExceptionPending;
 use js::jsval::JSVal;
 use js::jsval::ObjectValue;
 use js::jsval::UndefinedValue;
 use js::rust::Runtime;
 use msg::constellation_msg::PipelineId;
 use net_traits::image::base::Image;
 use net_traits::image::base::PixelFormat;
-use script_traits::PaintWorkletError;
+use net_traits::image_cache::ImageCache;
 use servo_atoms::Atom;
 use servo_url::ServoUrl;
 use std::cell::Cell;
 use std::collections::HashMap;
 use std::collections::hash_map::Entry;
 use std::ptr::null_mut;
 use std::rc::Rc;
-use std::sync::mpsc::Sender;
+use std::sync::Arc;
 
 /// https://drafts.css-houdini.org/css-paint-api/#paintworkletglobalscope
 #[dom_struct]
 pub struct PaintWorkletGlobalScope {
     /// The worklet global for this object
     worklet_global: WorkletGlobalScope,
+    /// The image cache (used for generating invalid images).
+    #[ignore_heap_size_of = "Arc"]
+    image_cache: Arc<ImageCache>,
     /// https://drafts.css-houdini.org/css-paint-api/#paint-definitions
     paint_definitions: DOMRefCell<HashMap<Atom, Box<PaintDefinition>>>,
     /// https://drafts.css-houdini.org/css-paint-api/#paint-class-instances
     paint_class_instances: DOMRefCell<HashMap<Atom, Box<Heap<JSVal>>>>,
-    /// A buffer to draw into
-    buffer: DOMRefCell<Vec<u8>>,
 }
 
 impl PaintWorkletGlobalScope {
     #[allow(unsafe_code)]
     pub fn new(runtime: &Runtime,
                pipeline_id: PipelineId,
                base_url: ServoUrl,
                init: &WorkletGlobalScopeInit)
                -> Root<PaintWorkletGlobalScope> {
         debug!("Creating paint worklet global scope for pipeline {}.", pipeline_id);
         let global = box PaintWorkletGlobalScope {
             worklet_global: WorkletGlobalScope::new_inherited(pipeline_id, base_url, init),
+            image_cache: init.image_cache.clone(),
             paint_definitions: Default::default(),
             paint_class_instances: Default::default(),
-            buffer: Default::default(),
         };
         unsafe { PaintWorkletGlobalScopeBinding::Wrap(runtime.cx(), global) }
     }
 
     pub fn perform_a_worklet_task(&self, task: PaintWorkletTask) {
         match task {
             PaintWorkletTask::DrawAPaintImage(name, size, sender) => self.draw_a_paint_image(name, size, sender),
         }
     }
 
     /// https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image
     fn draw_a_paint_image(&self,
                           name: Atom,
                           size: Size2D<Au>,
-                          sender: Sender<Result<Image, PaintWorkletError>>)
+                          sender: IpcSender<CanvasData>)
     {
         // TODO: document paint definitions.
         self.invoke_a_paint_callback(name, size, sender);
     }
 
     /// https://drafts.css-houdini.org/css-paint-api/#invoke-a-paint-callback
     #[allow(unsafe_code)]
     fn invoke_a_paint_callback(&self,
                                name: Atom,
                                size: Size2D<Au>,
-                               sender: Sender<Result<Image, PaintWorkletError>>)
+                               sender: IpcSender<CanvasData>)
     {
         let width = size.width.to_px().abs() as u32;
         let height = size.height.to_px().abs() as u32;
         debug!("Invoking a paint callback {}({},{}).", name, width, height);
 
         let cx = self.worklet_global.get_cx();
         let _ac = JSAutoCompartment::new(cx, self.worklet_global.reflector().get_jsobject().get());
 
         // TODO: Steps 1-2.1.
         // Step 2.2-5.1.
         rooted!(in(cx) let mut class_constructor = UndefinedValue());
         rooted!(in(cx) let mut paint_function = UndefinedValue());
-        match self.paint_definitions.borrow().get(&name) {
+        let rendering_context = match self.paint_definitions.borrow().get(&name) {
             None => {
                 // Step 2.2.
                 warn!("Drawing un-registered paint definition {}.", name);
-                let image = self.placeholder_image(width, height, [0x00, 0x00, 0xFF, 0xFF]);
-                let _ = sender.send(Ok(image));
-                return;
+                return self.send_invalid_image(size, sender);
             }
             Some(definition) => {
                 // Step 5.1
                 if !definition.constructor_valid_flag.get() {
                     debug!("Drawing invalid paint definition {}.", name);
-                    let image = self.placeholder_image(width, height, [0x00, 0x00, 0xFF, 0xFF]);
-                    let _ = sender.send(Ok(image));
-                    return;
+                    return self.send_invalid_image(size, sender);
                 }
                 class_constructor.set(definition.class_constructor.get());
                 paint_function.set(definition.paint_function.get());
+                Root::from_ref(&*definition.context)
             }
         };
 
         // Steps 5.2-5.4
         // TODO: the spec requires calling the constructor now, but we might want to
         // prepopulate the paint instance in `RegisterPaint`, to avoid calling it in
         // the primary worklet thread.
         // https://github.com/servo/servo/issues/17377
@@ -150,28 +152,28 @@ impl PaintWorkletGlobalScope {
                 unsafe { Construct1(cx, class_constructor.handle(), &args, result.handle_mut()); }
                 paint_instance.set(ObjectValue(result.get()));
                 if unsafe { JS_IsExceptionPending(cx) } {
                     debug!("Paint constructor threw an exception {}.", name);
                     unsafe { JS_ClearPendingException(cx); }
                     self.paint_definitions.borrow_mut().get_mut(&name)
                         .expect("Vanishing paint definition.")
                         .constructor_valid_flag.set(false);
-                    let image = self.placeholder_image(width, height, [0x00, 0x00, 0xFF, 0xFF]);
-                    let _ = sender.send(Ok(image));
-                    return;
+                    return self.send_invalid_image(size, sender);
                 }
                 // Step 5.4
                 entry.insert(Box::new(Heap::default())).set(paint_instance.get());
             }
         };
 
         // TODO: Steps 6-7
         // Step 8
-        let rendering_context = PaintRenderingContext2D::new(self);
+        // TODO: the spec requires creating a new paint rendering context each time,
+        // this code recycles the same one.
+        rendering_context.set_bitmap_dimensions(size);
 
         // Step 9
         let paint_size = PaintSize::new(self, size);
 
         // TODO: Step 10
         // Steps 11-12
         debug!("Invoking paint function {}.", name);
         let args_slice = [
@@ -181,47 +183,47 @@ impl PaintWorkletGlobalScope {
         let args = unsafe { HandleValueArray::from_rooted_slice(&args_slice) };
         rooted!(in(cx) let mut result = UndefinedValue());
         unsafe { Call(cx, paint_instance.handle(), paint_function.handle(), &args, result.handle_mut()); }
 
         // Step 13.
         if unsafe { JS_IsExceptionPending(cx) } {
             debug!("Paint function threw an exception {}.", name);
             unsafe { JS_ClearPendingException(cx); }
-            let image = self.placeholder_image(width, height, [0x00, 0x00, 0xFF, 0xFF]);
-            let _ = sender.send(Ok(image));
-            return;
+            return self.send_invalid_image(size, sender);
         }
 
-        // For now, we just build a dummy image.
-        let image = self.placeholder_image(width, height, [0xFF, 0x00, 0x00, 0xFF]);
-        let _ = sender.send(Ok(image));
+        rendering_context.send_data(sender);
     }
 
-    fn placeholder_image(&self, width: u32, height: u32, pixel: [u8; 4]) -> Image {
-        let area = (width as usize) * (height as usize);
-        let old_buffer_size = self.buffer.borrow().len();
-        let new_buffer_size = area * 4;
-        if new_buffer_size > old_buffer_size {
-            self.buffer.borrow_mut().extend(pixel.iter().cycle().take(new_buffer_size - old_buffer_size));
-        } else {
-            self.buffer.borrow_mut().truncate(new_buffer_size);
-        }
-        Image {
+    fn send_invalid_image(&self, size: Size2D<Au>, sender: IpcSender<CanvasData>) {
+        debug!("Sending an invalid image.");
+        let width = size.width.to_px().abs() as u32;
+        let height = size.height.to_px().abs() as u32;
+        let len = (width as usize) * (height as usize) * 4;
+        let pixel = [0xFF, 0x00, 0x00, 0xFF];
+        let bytes: Vec<u8> = pixel.iter().cloned().cycle().take(len).collect();
+        let mut image = Image {
             width: width,
             height: height,
             format: PixelFormat::BGRA8,
-            bytes: IpcSharedMemory::from_bytes(&*self.buffer.borrow()),
+            bytes: IpcSharedMemory::from_bytes(&*bytes),
             id: None,
-        }
+        };
+        self.image_cache.set_webrender_image_key(&mut image);
+        let image_key = image.id.expect("Image cache should set image key.");
+        let image_data = CanvasImageData { image_key: image_key };
+        let canvas_data = CanvasData::Image(image_data);
+        let _ = sender.send(canvas_data);
     }
 }
 
 impl PaintWorkletGlobalScopeMethods for PaintWorkletGlobalScope {
     #[allow(unsafe_code)]
+    #[allow(unrooted_must_root)]
     /// https://drafts.css-houdini.org/css-paint-api/#dom-paintworkletglobalscope-registerpaint
     fn RegisterPaint(&self, name: DOMString, paint_ctor: Rc<VoidFunction>) -> Fallible<()> {
         let name = Atom::from(name);
         let cx = self.worklet_global.get_cx();
         rooted!(in(cx) let paint_obj = paint_ctor.callback_holder().get());
         rooted!(in(cx) let paint_val = ObjectValue(paint_obj.get()));
 
         debug!("Registering paint image name {}.", name);
@@ -274,58 +276,70 @@ impl PaintWorkletGlobalScopeMethods for 
 
         // Steps 17-18
         rooted!(in(cx) let mut paint_function = UndefinedValue());
         unsafe { get_property_jsval(cx, prototype.handle(), "paint", paint_function.handle_mut())?; }
         if !paint_function.is_object() || unsafe { !IsCallable(paint_function.to_object()) } {
             return Err(Error::Type(String::from("Paint function is not callable.")));
         }
 
-        // Steps 19-20.
+        // Step 19.
+        let context = PaintRenderingContext2D::new(self);
+        let definition = PaintDefinition::new(paint_val.handle(),
+                                              paint_function.handle(),
+                                              input_properties,
+                                              alpha,
+                                              &*context);
+
+        // Step 20.
         debug!("Registering definition {}.", name);
-        self.paint_definitions.borrow_mut()
-            .insert(name,
-                    PaintDefinition::new(paint_val.handle(), paint_function.handle(), input_properties, alpha));
+        self.paint_definitions.borrow_mut().insert(name, definition);
 
         // TODO: Step 21.
 
         Ok(())
     }
 }
 
 /// Tasks which can be peformed by a paint worklet
 pub enum PaintWorkletTask {
-    DrawAPaintImage(Atom, Size2D<Au>, Sender<Result<Image, PaintWorkletError>>)
+    DrawAPaintImage(Atom, Size2D<Au>, IpcSender<CanvasData>)
 }
 
 /// A paint definition
 /// https://drafts.css-houdini.org/css-paint-api/#paint-definition
 /// This type is dangerous, because it contains uboxed `Heap<JSVal>` values,
 /// which can't be moved.
 #[derive(JSTraceable, HeapSizeOf)]
 #[must_root]
 struct PaintDefinition {
     class_constructor: Heap<JSVal>,
     paint_function: Heap<JSVal>,
     constructor_valid_flag: Cell<bool>,
     input_properties: Vec<DOMString>,
     context_alpha_flag: bool,
+    // TODO: the spec calls for fresh rendering contexts each time a paint image is drawn,
+    // but to avoid having the primary worklet thread create a new renering context,
+    // we recycle them.
+    context: JS<PaintRenderingContext2D>,
 }
 
 impl PaintDefinition {
     fn new(class_constructor: HandleValue,
            paint_function: HandleValue,
            input_properties: Vec<DOMString>,
-           alpha: bool)
+           alpha: bool,
+           context: &PaintRenderingContext2D)
            -> Box<PaintDefinition>
     {
         let result = Box::new(PaintDefinition {
             class_constructor: Heap::default(),
             paint_function: Heap::default(),
             constructor_valid_flag: Cell::new(true),
             input_properties: input_properties,
             context_alpha_flag: alpha,
+            context: JS::from_ref(context),
         });
         result.class_constructor.set(class_constructor.get());
         result.paint_function.set(paint_function.get());
         result
     }
 }
--- a/servo/components/script/dom/webidls/CanvasGradient.webidl
+++ b/servo/components/script/dom/webidls/CanvasGradient.webidl
@@ -1,12 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // https://html.spec.whatwg.org/multipage/#canvasgradient
+[Exposed=(Window, PaintWorklet)]
 interface CanvasGradient {
   // opaque object
   [Throws]
   void addColorStop(double offset, DOMString color);
 };
 
 
--- a/servo/components/script/dom/webidls/CanvasPattern.webidl
+++ b/servo/components/script/dom/webidls/CanvasPattern.webidl
@@ -1,9 +1,10 @@
 /* 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/. */
 
 // https://html.spec.whatwg.org/multipage/#canvaspattern
+[Exposed=(Window, PaintWorklet)]
 interface CanvasPattern {
   //void setTransform(SVGMatrix matrix);
 };
 
--- a/servo/components/script/dom/webidls/CanvasRenderingContext2D.webidl
+++ b/servo/components/script/dom/webidls/CanvasRenderingContext2D.webidl
@@ -36,24 +36,24 @@ CanvasRenderingContext2D implements Canv
 CanvasRenderingContext2D implements CanvasText;
 CanvasRenderingContext2D implements CanvasDrawImage;
 CanvasRenderingContext2D implements CanvasHitRegion;
 CanvasRenderingContext2D implements CanvasImageData;
 CanvasRenderingContext2D implements CanvasPathDrawingStyles;
 CanvasRenderingContext2D implements CanvasTextDrawingStyles;
 CanvasRenderingContext2D implements CanvasPath;
 
-[NoInterfaceObject]
+[NoInterfaceObject, Exposed=(Window, PaintWorklet)]
 interface CanvasState {
   // state
   void save(); // push state on state stack
   void restore(); // pop state stack and restore state
 };
 
-[NoInterfaceObject]
+[NoInterfaceObject, Exposed=(Window, PaintWorklet)]
 interface CanvasTransform {
   // transformations (default transform is the identity matrix)
   void scale(unrestricted double x, unrestricted double y);
   void rotate(unrestricted double angle);
   void translate(unrestricted double x, unrestricted double y);
   void transform(unrestricted double a,
                  unrestricted double b,
                  unrestricted double c,
@@ -67,64 +67,64 @@ interface CanvasTransform {
                     unrestricted double c,
                     unrestricted double d,
                     unrestricted double e,
                     unrestricted double f);
   // void setTransform(optional DOMMatrixInit matrix);
   void resetTransform();
 };
 
-[NoInterfaceObject]
+[NoInterfaceObject, Exposed=(Window, PaintWorklet)]
 interface CanvasCompositing {
   // compositing
   attribute unrestricted double globalAlpha; // (default 1.0)
   attribute DOMString globalCompositeOperation; // (default source-over)
 };
 
-[NoInterfaceObject]
+[NoInterfaceObject, Exposed=(Window, PaintWorklet)]
 interface CanvasImageSmoothing {
   // image smoothing
   attribute boolean imageSmoothingEnabled; // (default true)
   // attribute ImageSmoothingQuality imageSmoothingQuality; // (default low)
 };
 
-[NoInterfaceObject]
+[NoInterfaceObject, Exposed=(Window, PaintWorklet)]
 interface CanvasFillStrokeStyles {
 
   // colours and styles (see also the CanvasDrawingStyles interface)
   attribute (DOMString or CanvasGradient or CanvasPattern) strokeStyle; // (default black)
   attribute (DOMString or CanvasGradient or CanvasPattern) fillStyle; // (default black)
   CanvasGradient createLinearGradient(double x0, double y0, double x1, double y1);
   [Throws]
   CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1);
   [Throws]
   CanvasPattern createPattern(CanvasImageSource image, [TreatNullAs=EmptyString] DOMString repetition);
 };
 
-[NoInterfaceObject]
+[NoInterfaceObject, Exposed=(Window, PaintWorklet)]
 interface CanvasShadowStyles {
   // shadows
   attribute unrestricted double shadowOffsetX; // (default 0)
   attribute unrestricted double shadowOffsetY; // (default 0)
   attribute unrestricted double shadowBlur; // (default 0)
   attribute DOMString shadowColor; // (default transparent black)
 };
 
-[NoInterfaceObject]
+[NoInterfaceObject, Exposed=(Window, PaintWorklet)]
 interface CanvasRect {
   // rects
   //[LenientFloat]
   void clearRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
   //[LenientFloat]
   void fillRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
   //[LenientFloat]
   void strokeRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
 };
 
-[NoInterfaceObject]
+[NoInterfaceObject, Exposed=(Window, PaintWorklet)]
 interface CanvasDrawPath {
   // path API (see also CanvasPathMethods)
   void beginPath();
   void fill(optional CanvasFillRule fillRule = "nonzero");
   //void fill(Path2D path, optional CanvasFillRule fillRule = "nonzero");
   void stroke();
   //void stroke(Path2D path);
   //void drawFocusIfNeeded(Element element);
@@ -152,17 +152,17 @@ interface CanvasText {
   // text (see also the CanvasDrawingStyles interface)
   //void fillText(DOMString text, unrestricted double x, unrestricted double y,
   //              optional unrestricted double maxWidth);
   //void strokeText(DOMString text, unrestricted double x, unrestricted double y,
   //                optional unrestricted double maxWidth);
   //TextMetrics measureText(DOMString text);
 };
 
-[NoInterfaceObject]
+[NoInterfaceObject, Exposed=(Window, PaintWorklet)]
 interface CanvasDrawImage {
   // drawing images
   [Throws]
   void drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy);
   [Throws]
   void drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy,
                                           unrestricted double dw, unrestricted double dh);
   [Throws]
@@ -200,17 +200,17 @@ CanvasRenderingContext2D implements Canv
 CanvasRenderingContext2D implements CanvasPath;
 
 enum CanvasLineCap { "butt", "round", "square" };
 enum CanvasLineJoin { "round", "bevel", "miter"};
 enum CanvasTextAlign { "start", "end", "left", "right", "center" };
 enum CanvasTextBaseline { "top", "hanging", "middle", "alphabetic", "ideographic", "bottom" };
 enum CanvasDirection { "ltr", "rtl", "inherit" };
 
-[NoInterfaceObject]
+[NoInterfaceObject, Exposed=(Window, PaintWorklet)]
 interface CanvasPathDrawingStyles {
   // line caps/joins
   attribute unrestricted double lineWidth; // (default 1)
   attribute CanvasLineCap lineCap; // "butt", "round", "square" (default "butt")
   attribute CanvasLineJoin lineJoin; // "round", "bevel", "miter" (default "miter")
   attribute unrestricted double miterLimit; // (default 10)
 
   // dashed lines
@@ -224,17 +224,17 @@ interface CanvasTextDrawingStyles {
   // text
   //attribute DOMString font; // (default 10px sans-serif)
   //attribute CanvasTextAlign textAlign; // "start", "end", "left", "right", "center" (default: "start")
   //attribute CanvasTextBaseline textBaseline; // "top", "hanging", "middle", "alphabetic",
                                       // "ideographic", "bottom" (default: "alphabetic")
   //attribute CanvasDirection direction; // "ltr", "rtl", "inherit" (default: "inherit")
 };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
+[NoInterfaceObject, Exposed=(Window, Worker, PaintWorklet)]
 interface CanvasPath {
   // shared path API methods
   void closePath();
   void moveTo(unrestricted double x, unrestricted double y);
   void lineTo(unrestricted double x, unrestricted double y);
   void quadraticCurveTo(unrestricted double cpx, unrestricted double cpy,
                         unrestricted double x, unrestricted double y);
 
--- a/servo/components/script/dom/webidls/PaintRenderingContext2D.webidl
+++ b/servo/components/script/dom/webidls/PaintRenderingContext2D.webidl
@@ -1,19 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // https://drafts.css-houdini.org/css-paint-api/#paintrenderingcontext2d
 [Exposed=PaintWorklet]
 interface PaintRenderingContext2D {
 };
-// PaintRenderingContext2D implements CanvasState;
-// PaintRenderingContext2D implements CanvasTransform;
-// PaintRenderingContext2D implements CanvasCompositing;
-// PaintRenderingContext2D implements CanvasImageSmoothing;
-// PaintRenderingContext2D implements CanvasFillStrokeStyles;
-// PaintRenderingContext2D implements CanvasShadowStyles;
-// PaintRenderingContext2D implements CanvasRect;
-// PaintRenderingContext2D implements CanvasDrawPath;
-// PaintRenderingContext2D implements CanvasDrawImage;
-// PaintRenderingContext2D implements CanvasPathDrawingStyles;
-// PaintRenderingContext2D implements CanvasPath;
+PaintRenderingContext2D implements CanvasState;
+PaintRenderingContext2D implements CanvasTransform;
+PaintRenderingContext2D implements CanvasCompositing;
+PaintRenderingContext2D implements CanvasImageSmoothing;
+PaintRenderingContext2D implements CanvasFillStrokeStyles;
+PaintRenderingContext2D implements CanvasShadowStyles;
+PaintRenderingContext2D implements CanvasRect;
+PaintRenderingContext2D implements CanvasDrawPath;
+PaintRenderingContext2D implements CanvasDrawImage;
+PaintRenderingContext2D implements CanvasPathDrawingStyles;
+PaintRenderingContext2D implements CanvasPath;
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -147,16 +147,17 @@ pub enum ReflowReason {
     Viewport,
     WindowResize,
     DOMContentLoaded,
     DocumentLoaded,
     StylesheetLoaded,
     ImageLoaded,
     RequestAnimationFrame,
     WebFontLoaded,
+    WorkletLoaded,
     FramedContentChanged,
     IFrameLoadEvent,
     MissingExplicitReflow,
     ElementStateChanged,
 }
 
 #[dom_struct]
 pub struct Window {
@@ -1934,16 +1935,17 @@ fn debug_reflow_events(id: PipelineId, g
         ReflowReason::Viewport => "\tViewport",
         ReflowReason::WindowResize => "\tWindowResize",
         ReflowReason::DOMContentLoaded => "\tDOMContentLoaded",
         ReflowReason::DocumentLoaded => "\tDocumentLoaded",
         ReflowReason::StylesheetLoaded => "\tStylesheetLoaded",
         ReflowReason::ImageLoaded => "\tImageLoaded",
         ReflowReason::RequestAnimationFrame => "\tRequestAnimationFrame",
         ReflowReason::WebFontLoaded => "\tWebFontLoaded",
+        ReflowReason::WorkletLoaded => "\tWorkletLoaded",
         ReflowReason::FramedContentChanged => "\tFramedContentChanged",
         ReflowReason::IFrameLoadEvent => "\tIFrameLoadEvent",
         ReflowReason::MissingExplicitReflow => "\tMissingExplicitReflow",
         ReflowReason::ElementStateChanged => "\tElementStateChanged",
     });
 
     println!("{}", debug_msg);
 }
--- a/servo/components/script/dom/worklet.rs
+++ b/servo/components/script/dom/worklet.rs
@@ -6,16 +6,17 @@
 //!
 //! The goal of this implementation is to maximize responsiveness of worklets,
 //! and in particular to ensure that the thread performing worklet tasks
 //! is never busy GCing or loading worklet code. We do this by providing a custom
 //! thread pool implementation, which only performs GC or code loading on
 //! a backup thread, not on the primary worklet thread.
 
 use app_units::Au;
+use canvas_traits::CanvasData;
 use dom::bindings::codegen::Bindings::RequestBinding::RequestCredentials;
 use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods;
 use dom::bindings::codegen::Bindings::WorkletBinding::WorkletMethods;
 use dom::bindings::codegen::Bindings::WorkletBinding::WorkletOptions;
 use dom::bindings::codegen::Bindings::WorkletBinding::Wrap;
 use dom::bindings::error::Error;
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::JS;
@@ -33,37 +34,36 @@ use dom::promise::Promise;
 use dom::testworkletglobalscope::TestWorkletTask;
 use dom::window::Window;
 use dom::workletglobalscope::WorkletGlobalScope;
 use dom::workletglobalscope::WorkletGlobalScopeInit;
 use dom::workletglobalscope::WorkletGlobalScopeType;
 use dom::workletglobalscope::WorkletTask;
 use dom_struct::dom_struct;
 use euclid::Size2D;
+use ipc_channel::ipc::IpcSender;
 use js::jsapi::JSGCParamKey;
 use js::jsapi::JSTracer;
 use js::jsapi::JS_GC;
 use js::jsapi::JS_GetGCParameter;
 use js::rust::Runtime;
 use msg::constellation_msg::PipelineId;
 use net_traits::IpcSend;
-use net_traits::image::base::Image;
 use net_traits::load_whole_resource;
 use net_traits::request::Destination;
 use net_traits::request::RequestInit;
 use net_traits::request::RequestMode;
 use net_traits::request::Type as RequestType;
 use script_runtime::CommonScriptMsg;
 use script_runtime::ScriptThreadEventCategory;
 use script_runtime::StackRootTLS;
 use script_runtime::new_rt_and_cx;
 use script_thread::MainThreadScriptMsg;
 use script_thread::Runnable;
 use script_thread::ScriptThread;
-use script_traits::PaintWorkletError;
 use script_traits::PaintWorkletExecutor;
 use servo_atoms::Atom;
 use servo_rand;
 use servo_url::ImmutableOrigin;
 use servo_url::ServoUrl;
 use std::cmp::max;
 use std::collections::HashMap;
 use std::collections::hash_map;
@@ -71,26 +71,24 @@ use std::rc::Rc;
 use std::sync::Arc;
 use std::sync::Mutex;
 use std::sync::atomic::AtomicIsize;
 use std::sync::atomic::Ordering;
 use std::sync::mpsc;
 use std::sync::mpsc::Receiver;
 use std::sync::mpsc::Sender;
 use std::thread;
-use std::time::Duration;
 use style::thread_state;
 use swapper::Swapper;
 use swapper::swapper;
 use uuid::Uuid;
 
 // Magic numbers
 const WORKLET_THREAD_POOL_SIZE: u32 = 3;
 const MIN_GC_THRESHOLD: u32 = 1_000_000;
-const PAINT_TIMEOUT_MILLISECONDS: u64 = 10;
 
 #[dom_struct]
 /// https://drafts.css-houdini.org/worklets/#worklet
 pub struct Worklet {
     reflector: Reflector,
     window: JS<Window>,
     worklet_id: WorkletId,
     global_type: WorkletGlobalScopeType,
@@ -158,16 +156,17 @@ impl WorkletMethods for Worklet {
                                                self.window.origin().immutable().clone(),
                                                global.api_base_url(),
                                                module_url_record,
                                                options.credentials.clone(),
                                                pending_tasks_struct,
                                                &promise);
 
         // Step 5.
+        debug!("Returning promise.");
         promise
     }
 }
 
 /// A guid for worklets.
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, JSTraceable)]
 pub struct WorkletId(Uuid);
 
@@ -229,16 +228,27 @@ impl PendingTasksStruct {
 /// is expected to be performed by whichever thread is currently
 /// primary. Control messages are targeted at a thread, for example
 /// adding a module is performed in every thread, even if they change roles
 /// in the middle of module loading.
 ///
 /// The thread pool lives in the script thread, and is initialized
 /// when a worklet adds a module. It is dropped when the script thread
 /// is dropped, and asks each of the worklet threads to quit.
+///
+/// The layout thread can end up blocking on the primary worklet thread
+/// (e.g. when invoking a paint callback), so it is important to avoid
+/// deadlock by making sure the primary worklet thread doesn't end up
+/// blocking waiting on layout. In particular, since the constellation
+/// can block waiting on layout, this means the primary worklet thread
+/// can't block waiting on the constellation. In general, the primary
+/// worklet thread shouldn't perform any blocking operations. If a worklet
+/// thread needs to do anything blocking, it should send a control
+/// message, to make sure that the blocking operation is performed
+/// by a backup thread, not by the primary thread.
 
 #[derive(Clone, JSTraceable)]
 pub struct WorkletThreadPool {
     // Channels to send data messages to the three roles.
     primary_sender: Sender<WorkletData>,
     hot_backup_sender: Sender<WorkletData>,
     cold_backup_sender: Sender<WorkletData>,
     // Channels to send control messages to the three threads.
@@ -546,27 +556,29 @@ impl WorkletThread {
                                 worklet_id: WorkletId,
                                 global_type: WorkletGlobalScopeType,
                                 base_url: ServoUrl)
                                 -> Root<WorkletGlobalScope>
     {
         match self.global_scopes.entry(worklet_id) {
             hash_map::Entry::Occupied(entry) => Root::from_ref(entry.get()),
             hash_map::Entry::Vacant(entry) => {
+                debug!("Creating new worklet global scope.");
                 let result = global_type.new(&self.runtime, pipeline_id, base_url, &self.global_init);
                 entry.insert(JS::from_ref(&*result));
                 result
             },
         }
     }
 
     /// Fetch and invoke a worklet script.
     /// https://drafts.css-houdini.org/worklets/#fetch-and-invoke-a-worklet-script
     fn fetch_and_invoke_a_worklet_script(&self,
                                          global_scope: &WorkletGlobalScope,
+                                         pipeline_id: PipelineId,
                                          origin: ImmutableOrigin,
                                          script_url: ServoUrl,
                                          credentials: RequestCredentials,
                                          pending_tasks_struct: PendingTasksStruct,
                                          promise: TrustedPromise)
     {
         debug!("Fetching from {}.", script_url);
         // Step 1.
@@ -607,17 +619,19 @@ impl WorkletThread {
             if old_counter > 0 {
                 self.run_in_script_thread(promise.reject_runnable(Error::Abort));
             }
         } else {
             // Step 5.
             debug!("Finished adding script.");
             let old_counter = pending_tasks_struct.decrement_counter_by(1);
             if old_counter == 1 {
-                // TODO: trigger a reflow?
+                debug!("Resolving promise.");
+                let msg = MainThreadScriptMsg::WorkletLoaded(pipeline_id);
+                self.script_sender.send(msg).expect("Worklet thread outlived script thread.");
                 self.run_in_script_thread(promise.resolve_runnable(()));
             }
         }
     }
 
     /// Perform a task.
     fn perform_a_worklet_task(&self, worklet_id: WorkletId, task: WorkletTask) {
         match self.global_scopes.get(&worklet_id) {
@@ -633,16 +647,17 @@ impl WorkletThread {
                 pipeline_id, worklet_id, global_type, origin, base_url,
                 script_url, credentials, pending_tasks_struct, promise,
             } => {
                 let global = self.get_worklet_global_scope(pipeline_id,
                                                            worklet_id,
                                                            global_type,
                                                            base_url);
                 self.fetch_and_invoke_a_worklet_script(&*global,
+                                                       pipeline_id,
                                                        origin,
                                                        script_url,
                                                        credentials,
                                                        pending_tasks_struct,
                                                        promise)
             }
         }
     }
@@ -673,18 +688,15 @@ impl WorkletExecutor {
             .send(WorkletData::Task(self.worklet_id, task));
     }
 }
 
 impl PaintWorkletExecutor for WorkletExecutor {
     /// https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image
     fn draw_a_paint_image(&self,
                           name: Atom,
-                          concrete_object_size: Size2D<Au>)
-                          -> Result<Image, PaintWorkletError>
+                          concrete_object_size: Size2D<Au>,
+                          sender: IpcSender<CanvasData>)
     {
-        let (sender, receiver) = mpsc::channel();
         let task = WorkletTask::Paint(PaintWorkletTask::DrawAPaintImage(name, concrete_object_size, sender));
-        let timeout = Duration::from_millis(PAINT_TIMEOUT_MILLISECONDS);
         self.schedule_a_worklet_task(task);
-        receiver.recv_timeout(timeout)?
     }
 }
--- a/servo/components/script/dom/workletglobalscope.rs
+++ b/servo/components/script/dom/workletglobalscope.rs
@@ -15,23 +15,25 @@ use ipc_channel::ipc;
 use ipc_channel::ipc::IpcSender;
 use js::jsapi::JSContext;
 use js::jsval::UndefinedValue;
 use js::rust::Runtime;
 use microtask::Microtask;
 use microtask::MicrotaskQueue;
 use msg::constellation_msg::PipelineId;
 use net_traits::ResourceThreads;
+use net_traits::image_cache::ImageCache;
 use profile_traits::mem;
 use profile_traits::time;
 use script_traits::ScriptMsg;
 use script_traits::TimerSchedulerMsg;
 use servo_url::ImmutableOrigin;
 use servo_url::MutableOrigin;
 use servo_url::ServoUrl;
+use std::sync::Arc;
 
 #[dom_struct]
 /// https://drafts.css-houdini.org/worklets/#workletglobalscope
 pub struct WorkletGlobalScope {
     /// The global for this worklet.
     globalscope: GlobalScope,
     /// The base URL for this worklet.
     base_url: ServoUrl,
@@ -118,16 +120,18 @@ pub struct WorkletGlobalScopeInit {
     /// Channel to the time profiler
     pub time_profiler_chan: time::ProfilerChan,
     /// Channel to devtools
     pub devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
     /// Messages to send to constellation
     pub constellation_chan: IpcSender<ScriptMsg>,
     /// Message to send to the scheduler
     pub scheduler_chan: IpcSender<TimerSchedulerMsg>,
+    /// The image cache
+    pub image_cache: Arc<ImageCache>,
 }
 
 /// https://drafts.css-houdini.org/worklets/#worklet-global-scope-type
 #[derive(Clone, Copy, Debug, HeapSizeOf, JSTraceable)]
 pub enum WorkletGlobalScopeType {
     /// A servo-specific testing worklet
     Test,
     /// A paint worklet
--- a/servo/components/script/script_thread.rs
+++ b/servo/components/script/script_thread.rs
@@ -251,16 +251,19 @@ pub enum MainThreadScriptMsg {
     /// Begins a content-initiated load on the specified pipeline (only
     /// dispatched to ScriptThread). Allows for a replace bool to be passed. If true,
     /// the current entry will be replaced instead of a new entry being added.
     Navigate(PipelineId, LoadData, bool),
     /// Tasks that originate from the DOM manipulation task source
     DOMManipulation(DOMManipulationTask),
     /// Tasks that originate from the user interaction task source
     UserInteraction(UserInteractionTask),
+    /// Notifies the script thread that a new worklet has been loaded, and thus the page should be
+    /// reflowed.
+    WorkletLoaded(PipelineId),
 }
 
 impl OpaqueSender<CommonScriptMsg> for Box<ScriptChan + Send> {
     fn send(&self, msg: CommonScriptMsg) {
         ScriptChan::send(&**self, msg).unwrap();
     }
 }
 
@@ -719,16 +722,17 @@ impl ScriptThread {
                 let chan = script_thread.chan.0.clone();
                 let init = WorkletGlobalScopeInit {
                     resource_threads: script_thread.resource_threads.clone(),
                     mem_profiler_chan: script_thread.mem_profiler_chan.clone(),
                     time_profiler_chan: script_thread.time_profiler_chan.clone(),
                     devtools_chan: script_thread.devtools_chan.clone(),
                     constellation_chan: script_thread.constellation_chan.clone(),
                     scheduler_chan: script_thread.scheduler_chan.clone(),
+                    image_cache: script_thread.image_cache.clone(),
                 };
                 Rc::new(WorkletThreadPool::spawn(chan, init))
             }).clone()
         })
     }
 
     /// Creates a new script thread.
     pub fn new(state: InitialScriptState,
@@ -823,16 +827,17 @@ impl ScriptThread {
     }
 
     /// Starts the script thread. After calling this method, the script thread will loop receiving
     /// messages on its port.
     pub fn start(&self) {
         debug!("Starting script thread.");
         while self.handle_msgs() {
             // Go on...
+            debug!("Running script thread.");
         }
         debug!("Stopped script thread.");
     }
 
     /// Handle incoming control messages.
     fn handle_msgs(&self) -> bool {
         use self::MixedMessage::{FromConstellation, FromDevtools, FromImageCache};
         use self::MixedMessage::{FromScheduler, FromScript};
@@ -851,16 +856,17 @@ impl ScriptThread {
         for (id, size, size_type) in resizes {
             self.handle_event(id, ResizeEvent(size, size_type));
         }
 
         // Store new resizes, and gather all other events.
         let mut sequential = vec![];
 
         // Receive at least one message so we don't spinloop.
+        debug!("Waiting for event.");
         let mut event = {
             let sel = Select::new();
             let mut script_port = sel.handle(&self.port);
             let mut control_port = sel.handle(&self.control_port);
             let mut timer_event_port = sel.handle(&self.timer_event_port);
             let mut devtools_port = sel.handle(&self.devtools_port);
             let mut image_cache_port = sel.handle(&self.image_cache_port);
             unsafe {
@@ -882,16 +888,17 @@ impl ScriptThread {
             } else if ret == devtools_port.id() {
                 FromDevtools(self.devtools_port.recv().unwrap())
             } else if ret == image_cache_port.id() {
                 FromImageCache(self.image_cache_port.recv().unwrap())
             } else {
                 panic!("unexpected select result")
             }
         };
+        debug!("Got event.");
 
         // Squash any pending resize, reflow, animation tick, and mouse-move events in the queue.
         let mut mouse_move_event_index = None;
         let mut animation_ticks = HashSet::new();
         loop {
             // https://html.spec.whatwg.org/multipage/#event-loop-processing-model step 7
             match event {
                 // This has to be handled before the ResizeMsg below,
@@ -978,16 +985,17 @@ impl ScriptThread {
                     },
                     Ok(ev) => event = FromScript(ev),
                 },
                 Ok(ev) => event = FromConstellation(ev),
             }
         }
 
         // Process the gathered events.
+        debug!("Processing events.");
         for msg in sequential {
             debug!("Processing event {:?}.", msg);
             let category = self.categorize_msg(&msg);
 
             let result = self.profile_event(category, move || {
                 match msg {
                     FromConstellation(ConstellationControlMsg::ExitScriptThread) => {
                         self.handle_exit_script_thread_msg();
@@ -1020,16 +1028,17 @@ impl ScriptThread {
             docs.clear();
         }
 
         // https://html.spec.whatwg.org/multipage/#event-loop-processing-model step 7.12
 
         // Issue batched reflows on any pages that require it (e.g. if images loaded)
         // TODO(gw): In the future we could probably batch other types of reflows
         // into this loop too, but for now it's only images.
+        debug!("Issuing batched reflows.");
         for (_, document) in self.documents.borrow().iter() {
             let window = document.window();
             let pending_reflows = window.get_pending_reflow_count();
             if pending_reflows > 0 {
                 window.reflow(ReflowGoal::ForDisplay,
                               ReflowQueryType::NoQuery,
                               ReflowReason::ImageLoaded);
             } else {
@@ -1184,21 +1193,26 @@ impl ScriptThread {
             MainThreadScriptMsg::Navigate(parent_pipeline_id, load_data, replace) =>
                 self.handle_navigate(parent_pipeline_id, None, load_data, replace),
             MainThreadScriptMsg::ExitWindow(id) =>
                 self.handle_exit_window_msg(id),
             MainThreadScriptMsg::Common(CommonScriptMsg::RunnableMsg(_, runnable)) => {
                 // The category of the runnable is ignored by the pattern, however
                 // it is still respected by profiling (see categorize_msg).
                 if !runnable.is_cancelled() {
+                    debug!("Running runnable.");
                     runnable.main_thread_handler(self)
+                } else {
+                    debug!("Not running cancelled runnable.");
                 }
             }
             MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(reports_chan)) =>
                 self.collect_reports(reports_chan),
+            MainThreadScriptMsg::WorkletLoaded(pipeline_id) =>
+                self.handle_worklet_loaded(pipeline_id),
             MainThreadScriptMsg::DOMManipulation(task) =>
                 task.handle_task(self),
             MainThreadScriptMsg::UserInteraction(task) =>
                 task.handle_task(self),
         }
     }
 
     fn handle_timer_event(&self, timer_event: TimerEvent) {
@@ -1754,16 +1768,24 @@ impl ScriptThread {
     /// Handles a Web font being loaded. Does nothing if the page no longer exists.
     fn handle_web_font_loaded(&self, pipeline_id: PipelineId) {
         let document = self.documents.borrow().find_document(pipeline_id);
         if let Some(document) = document {
             self.rebuild_and_force_reflow(&document, ReflowReason::WebFontLoaded);
         }
     }
 
+    /// Handles a worklet being loaded. Does nothing if the page no longer exists.
+    fn handle_worklet_loaded(&self, pipeline_id: PipelineId) {
+        let document = self.documents.borrow().find_document(pipeline_id);
+        if let Some(document) = document {
+            self.rebuild_and_force_reflow(&document, ReflowReason::WorkletLoaded);
+        }
+    }
+
     /// Notify a window of a storage event
     fn handle_storage_event(&self, pipeline_id: PipelineId, storage_type: StorageType, url: ServoUrl,
                             key: Option<String>, old_value: Option<String>, new_value: Option<String>) {
         let window = match { self.documents.borrow().find_window(pipeline_id) } {
             None => return warn!("Storage event sent to closed pipeline {}.", pipeline_id),
             Some(window) => window,
         };
 
--- a/servo/components/script_traits/lib.rs
+++ b/servo/components/script_traits/lib.rs
@@ -36,16 +36,17 @@ extern crate time;
 extern crate webrender_traits;
 extern crate webvr_traits;
 
 mod script_msg;
 pub mod webdriver_msg;
 
 use app_units::Au;
 use bluetooth_traits::BluetoothRequest;
+use canvas_traits::CanvasData;
 use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId};
 use euclid::{Size2D, Length, Point2D, Vector2D, Rect, ScaleFactor, TypedSize2D};
 use gfx_traits::Epoch;
 use heapsize::HeapSizeOf;
 use hyper::header::Headers;
 use hyper::method::Method;
 use ipc_channel::ipc::{IpcReceiver, IpcSender};
 use libc::c_void;
@@ -821,17 +822,17 @@ pub enum PaintWorkletError {
 }
 
 impl From<RecvTimeoutError> for PaintWorkletError {
     fn from(_: RecvTimeoutError) -> PaintWorkletError {
         PaintWorkletError::Timeout
     }
 }
 
-/// Execute paint code in the worklet thread pool.<
+/// Execute paint code in the worklet thread pool.
 pub trait PaintWorkletExecutor: Sync + Send {
     /// https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image
     fn draw_a_paint_image(&self,
                           name: Atom,
-                          concrete_object_size: Size2D<Au>)
-                          -> Result<Image, PaintWorkletError>;
+                          concrete_object_size: Size2D<Au>,
+                          sender: IpcSender<CanvasData>);
 }