servo: Merge #17634 - Implement drawing an image from a CSS style value into a canvas (from asajeffrey:canvas-image-css-style-value); r=jdm
authorAlan Jeffrey <ajeffrey@mozilla.com>
Fri, 21 Jul 2017 14:39:47 -0700
changeset 419023 38744dde9f2f41fe1e1e62d1456177cd663b5dd0
parent 419022 70e1925b92aecd2a0d6a8a2325e5d7fc3a5639e1
child 419024 8f0e9bc0c3a401f35a18bbabcfa59af7f5f6055e
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
bugs17634, 17364, 17432
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #17634 - Implement drawing an image from a CSS style value into a canvas (from asajeffrey:canvas-image-css-style-value); r=jdm <!-- Please describe your changes on the following line: --> Implemented drawing a CSS style value into a canvas, which is needed for the Houdini CSS Paint API. This PR is dependent on #17364. --- <!-- 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 fix #17432. - [X] These changes do not require tests because the existing CSS paint API wpt test cases test this behaviour. <!-- 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: 76ad913870cec3f01731d324967ed191fd5c6be3
servo/components/layout/display_list_builder.rs
servo/components/script/dom/canvasrenderingcontext2d.rs
servo/components/script/dom/cssstylevalue.rs
servo/components/script/dom/paintrenderingcontext2d.rs
servo/components/script/dom/paintworkletglobalscope.rs
servo/components/script/dom/webidls/CanvasRenderingContext2D.webidl
servo/components/script_traits/lib.rs
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -1168,17 +1168,16 @@ impl FragmentDisplayListBuilding for Fra
         // https://drafts.csswg.org/css-images-3/#concrete-object-size
         // Experimentally, chrome is using the size in px of the box,
         // including padding, but not border or margin, so we follow suit.
         // https://github.com/w3c/css-houdini-drafts/issues/417
         let unbordered_box = self.border_box - style.logical_border_width();
         let device_pixel_ratio = state.layout_context.style_context.device_pixel_ratio();
         let size_in_au = unbordered_box.size.to_physical(style.writing_mode);
         let size_in_px = TypedSize2D::new(size_in_au.width.to_f32_px(), size_in_au.height.to_f32_px());
-        let size_in_dpx = size_in_px * device_pixel_ratio;
         let name = paint_worklet.name.clone();
 
         // Get the painter, and the computed values for its properties.
         let (properties, painter) = match state.layout_context.registered_painters.read().get(&name) {
             Some(registered_painter) => (
                 registered_painter.properties
                     .iter()
                     .filter_map(|(name, id)| id.as_shorthand().err().map(|id| (name, id)))
@@ -1187,33 +1186,28 @@ impl FragmentDisplayListBuilding for Fra
                 registered_painter.painter.clone()
             ),
             None => return debug!("Worklet {} called before registration.", 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_in_px.width, size_in_px.height);
-        let (sender, receiver) = ipc::channel().unwrap();
-        painter.draw_a_paint_image(size_in_px, device_pixel_ratio, properties, sender);
+        let mut draw_result = painter.draw_a_paint_image(size_in_px, device_pixel_ratio, properties);
+        let webrender_image = WebRenderImageInfo {
+            width: draw_result.width,
+            height: draw_result.height,
+            format: draw_result.format,
+            key: draw_result.image_key,
+        };
 
-        // 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_in_dpx.width as u32,
-                    height: size_in_dpx.height 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),
-        };
+        for url in draw_result.missing_image_urls.drain(..) {
+            debug!("Requesting missing image URL {}.", url);
+            state.layout_context.get_webrender_image_for_url(self.node, url, UsePlaceholder::No);
+        }
 
         self.build_display_list_for_webrender_image(state,
                                                     style,
                                                     display_list_section,
                                                     absolute_bounds,
                                                     clip,
                                                     webrender_image,
                                                     index);
--- a/servo/components/script/dom/canvasrenderingcontext2d.rs
+++ b/servo/components/script/dom/canvasrenderingcontext2d.rs
@@ -7,48 +7,52 @@ use canvas_traits::{CompositionOrBlendin
 use canvas_traits::{LineCapStyle, LineJoinStyle, LinearGradientStyle};
 use canvas_traits::{RadialGradientStyle, RepetitionStyle, byte_swap_and_premultiply};
 use cssparser::{Parser, ParserInput, RGBA};
 use cssparser::Color as CSSColor;
 use dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
 use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding;
 use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasFillRule;
+use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasImageSource;
 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::ImageDataBinding::ImageDataMethods;
 use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
-use dom::bindings::codegen::UnionTypes::HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D;
 use dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern;
 use dom::bindings::error::{Error, ErrorResult, Fallible};
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::{JS, LayoutJS, Root};
 use dom::bindings::num::Finite;
 use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::canvasgradient::{CanvasGradient, CanvasGradientStyle, ToFillOrStrokeStyle};
 use dom::canvaspattern::CanvasPattern;
 use dom::globalscope::GlobalScope;
 use dom::htmlcanvaselement::HTMLCanvasElement;
-use dom::htmlcanvaselement::utils as canvas_utils;
-use dom::htmlimageelement::HTMLImageElement;
 use dom::imagedata::ImageData;
 use dom::node::{document_from_node, Node, NodeDamage, window_from_node};
 use dom_struct::dom_struct;
 use euclid::{Transform2D, Point2D, Vector2D, Rect, Size2D, vec2};
 use ipc_channel::ipc::{self, IpcSender};
 use net_traits::image::base::PixelFormat;
+use net_traits::image_cache::CanRequestImages;
+use net_traits::image_cache::ImageCache;
+use net_traits::image_cache::ImageOrMetadataAvailable;
 use net_traits::image_cache::ImageResponse;
+use net_traits::image_cache::ImageState;
+use net_traits::image_cache::UsePlaceholder;
 use num_traits::ToPrimitive;
 use script_traits::ScriptMsg as ConstellationMsg;
 use servo_url::ServoUrl;
-use std::{cmp, fmt};
+use std::{cmp, fmt, mem};
 use std::cell::Cell;
 use std::str::FromStr;
+use std::sync::Arc;
 use unpremultiplytable::UNPREMULTIPLY_TABLE;
 
 #[must_root]
 #[derive(JSTraceable, Clone, HeapSizeOf)]
 #[allow(dead_code)]
 enum CanvasFillOrStrokeStyle {
     Color(RGBA),
     Gradient(JS<CanvasGradient>),
@@ -56,19 +60,26 @@ 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>,
-    // For rendering contexts created by an HTML canvas element, this is Some,
-    // for ones created by a paint worklet, this is None.
+    /// 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>>,
+    #[ignore_heap_size_of = "Arc"]
+    image_cache: Arc<ImageCache>,
+    /// Any missing image URLs.
+    missing_image_urls: DOMRefCell<Vec<ServoUrl>>,
+    /// The base URL for resolving CSS image URL values.
+    /// Needed because of https://github.com/servo/servo/issues/17625
+    base_url: ServoUrl,
     state: DOMRefCell<CanvasContextState>,
     saved_states: DOMRefCell<Vec<CanvasContextState>>,
     origin_clean: Cell<bool>,
 }
 
 #[must_root]
 #[derive(JSTraceable, Clone, HeapSizeOf)]
 struct CanvasContextState {
@@ -108,42 +119,49 @@ impl CanvasContextState {
             shadow_color: RGBA::transparent(),
         }
     }
 }
 
 impl CanvasRenderingContext2D {
     pub fn new_inherited(global: &GlobalScope,
                          canvas: Option<&HTMLCanvasElement>,
+                         image_cache: Arc<ImageCache>,
+                         base_url: ServoUrl,
                          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: canvas.map(JS::from_ref),
+            image_cache: image_cache,
+            missing_image_urls: DOMRefCell::new(Vec::new()),
+            base_url: base_url,
             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, Some(canvas), size),
-                           global,
-                           CanvasRenderingContext2DBinding::Wrap)
+        let window = window_from_node(canvas);
+        let image_cache = window.image_cache();
+        let base_url = window.get_url();
+        let boxed = box CanvasRenderingContext2D::new_inherited(global, Some(canvas), image_cache, base_url, size);
+        reflect_dom_object(boxed, 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
             .send(CanvasMsg::Common(CanvasCommonMsg::Recreate(size)))
             .unwrap();
@@ -219,33 +237,34 @@ impl CanvasRenderingContext2D {
                                     Size2D::new(source_rect_clipped.size.width,
                                                 source_rect_clipped.size.height));
 
         (source_rect, dest_rect)
     }
 
     // https://html.spec.whatwg.org/multipage/#the-image-argument-is-not-origin-clean
     fn is_origin_clean(&self,
-                       image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D)
+                       image: CanvasImageSource)
                            -> bool {
         match image {
-            HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::HTMLCanvasElement(canvas) => {
+            CanvasImageSource::HTMLCanvasElement(canvas) => {
                 canvas.origin_is_clean()
             }
-            HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::CanvasRenderingContext2D(image) =>
+            CanvasImageSource::CanvasRenderingContext2D(image) =>
                 image.origin_is_clean(),
-            HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::HTMLImageElement(image) => {
+            CanvasImageSource::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(&**canvas);
                 document.url().clone().origin() == image_origin
             }
+            CanvasImageSource::CSSStyleValue(_) => true,
         }
     }
 
     //
     // drawImage coordinates explained
     //
     //  Source Image      Destination Canvas
     // +-------------+     +-------------+
@@ -261,59 +280,51 @@ impl CanvasRenderingContext2D {
     // +-------------+     +-------------+
     //
     //
     // The rectangle (sx, sy, sw, sh) from the source image
     // is copied on the rectangle (dx, dy, dh, dw) of the destination canvas
     //
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
     fn draw_image(&self,
-                  image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
+                  image: CanvasImageSource,
                   sx: f64,
                   sy: f64,
                   sw: Option<f64>,
                   sh: Option<f64>,
                   dx: f64,
                   dy: f64,
                   dw: Option<f64>,
                   dh: Option<f64>)
                   -> ErrorResult {
         let result = match image {
-            HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::HTMLCanvasElement(ref canvas) => {
+            CanvasImageSource::HTMLCanvasElement(ref canvas) => {
                 self.draw_html_canvas_element(&canvas,
                                               sx, sy, sw, sh,
                                               dx, dy, dw, dh)
             }
-            HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::CanvasRenderingContext2D(ref image) => {
+            CanvasImageSource::CanvasRenderingContext2D(ref image) => {
                 self.draw_html_canvas_element(&image.Canvas(),
                                               sx, sy, sw, sh,
                                               dx, dy, dw, dh)
             }
-            HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::HTMLImageElement(ref image) => {
+            CanvasImageSource::HTMLImageElement(ref image) => {
                 // https://html.spec.whatwg.org/multipage/#img-error
                 // If the image argument is an HTMLImageElement object that is in the broken state,
                 // then throw an InvalidStateError exception
-                let (image_data, image_size) = match self.fetch_image_data(image) {
-                    Some((mut data, size)) => {
-                        // Pixels come from cache in BGRA order and drawImage expects RGBA so we
-                        // have to swap the color values
-                        byte_swap_and_premultiply(&mut data);
-                        let size = Size2D::new(size.width as f64, size.height as f64);
-                        (data, size)
-                    },
-                    None => return Err(Error::InvalidState),
-                };
-                let dw = dw.unwrap_or(image_size.width);
-                let dh = dh.unwrap_or(image_size.height);
-                let sw = sw.unwrap_or(image_size.width);
-                let sh = sh.unwrap_or(image_size.height);
-                self.draw_image_data(image_data,
-                                     image_size,
-                                     sx, sy, sw, sh,
-                                     dx, dy, dw, dh)
+                let url = image.get_url().ok_or(Error::InvalidState)?;
+                self.fetch_and_draw_image_data(url,
+                                               sx, sy, sw, sh,
+                                               dx, dy, dw, dh)
+            }
+            CanvasImageSource::CSSStyleValue(ref value) => {
+                let url = value.get_url(self.base_url.clone()).ok_or(Error::InvalidState)?;
+                self.fetch_and_draw_image_data(url,
+                                               sx, sy, sw, sh,
+                                               dx, dy, dw, dh)
             }
         };
 
         if result.is_ok() && !self.is_origin_clean(image) {
             self.set_origin_unclean()
         }
         result
     }
@@ -381,16 +392,51 @@ impl CanvasRenderingContext2D {
             renderer.send(msg).unwrap();
             receiver.recv().unwrap();
         };
 
         self.mark_as_dirty();
         Ok(())
     }
 
+    fn fetch_and_draw_image_data(&self,
+                                 url: ServoUrl,
+                                 sx: f64,
+                                 sy: f64,
+                                 sw: Option<f64>,
+                                 sh: Option<f64>,
+                                 dx: f64,
+                                 dy: f64,
+                                 dw: Option<f64>,
+                                 dh: Option<f64>)
+                                 -> ErrorResult {
+        debug!("Fetching image {}.", url);
+        // https://html.spec.whatwg.org/multipage/#img-error
+        // If the image argument is an HTMLImageElement object that is in the broken state,
+        // then throw an InvalidStateError exception
+        let (image_data, image_size) = match self.fetch_image_data(url) {
+            Some((mut data, size)) => {
+                // Pixels come from cache in BGRA order and drawImage expects RGBA so we
+                // have to swap the color values
+                byte_swap_and_premultiply(&mut data);
+                let size = Size2D::new(size.width as f64, size.height as f64);
+                (data, size)
+            },
+            None => return Err(Error::InvalidState),
+        };
+        let dw = dw.unwrap_or(image_size.width);
+        let dh = dh.unwrap_or(image_size.height);
+        let sw = sw.unwrap_or(image_size.width);
+        let sh = sh.unwrap_or(image_size.height);
+        self.draw_image_data(image_data,
+                             image_size,
+                             sx, sy, sw, sh,
+                             dx, dy, dw, dh)
+    }
+
     fn draw_image_data(&self,
                        image_data: Vec<u8>,
                        image_size: Size2D<f64>,
                        sx: f64,
                        sy: f64,
                        sw: f64,
                        sh: f64,
                        dx: f64,
@@ -420,22 +466,17 @@ impl CanvasRenderingContext2D {
                                                              dest_rect,
                                                              source_rect,
                                                              smoothing_enabled)))
             .unwrap();
         self.mark_as_dirty();
         Ok(())
     }
 
-    fn fetch_image_data(&self, image_element: &HTMLImageElement) -> Option<(Vec<u8>, Size2D<i32>)> {
-        let url = match image_element.get_url() {
-            Some(url) => url,
-            None => return None,
-        };
-
+    fn fetch_image_data(&self, url: ServoUrl) -> Option<(Vec<u8>, Size2D<i32>)> {
         let img = match self.request_image_from_cache(url) {
             ImageResponse::Loaded(img, _) => img,
             ImageResponse::PlaceholderLoaded(_, _) |
             ImageResponse::None |
             ImageResponse::MetadataLoaded(_) => {
                 return None;
             }
         };
@@ -448,20 +489,36 @@ 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 {
-        self.canvas.as_ref()
-            .map(|canvas| window_from_node(&**canvas))
-            .map(|window| canvas_utils::request_image_from_cache(&window, url))
-            .unwrap_or(ImageResponse::None)
+        let response = self.image_cache
+            .find_image_or_metadata(url.clone(),
+                                    UsePlaceholder::No,
+                                    CanRequestImages::No);
+        match response {
+            Ok(ImageOrMetadataAvailable::ImageAvailable(image, url)) =>
+                ImageResponse::Loaded(image, url),
+            Err(ImageState::Pending(_)) =>
+                ImageResponse::None,
+            _ => {
+                // Rather annoyingly, we get the same response back from
+                // A load which really failed and from a load which hasn't started yet.
+                self.missing_image_urls.borrow_mut().push(url);
+                ImageResponse::None
+            },
+        }
+    }
+
+    pub fn take_missing_image_urls(&self) -> Vec<ServoUrl> {
+        mem::replace(&mut self.missing_image_urls.borrow_mut(), vec![])
     }
 
     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 {
@@ -744,45 +801,45 @@ impl CanvasRenderingContext2DMethods for
         self.ipc_renderer
             .send(CanvasMsg::Canvas2d(Canvas2dMsg::IsPointInPath(x, y, fill_rule, sender)))
             .unwrap();
         receiver.recv().unwrap()
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
     fn DrawImage(&self,
-                 image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
+                 image: CanvasImageSource,
                  dx: f64,
                  dy: f64)
                  -> ErrorResult {
         if !(dx.is_finite() && dy.is_finite()) {
             return Ok(());
         }
 
         self.draw_image(image, 0f64, 0f64, None, None, dx, dy, None, None)
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
     fn DrawImage_(&self,
-                  image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
+                  image: CanvasImageSource,
                   dx: f64,
                   dy: f64,
                   dw: f64,
                   dh: f64)
                   -> ErrorResult {
         if !(dx.is_finite() && dy.is_finite() && dw.is_finite() && dh.is_finite()) {
             return Ok(());
         }
 
         self.draw_image(image, 0f64, 0f64, None, None, dx, dy, Some(dw), Some(dh))
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
     fn DrawImage__(&self,
-                   image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
+                   image: CanvasImageSource,
                    sx: f64,
                    sy: f64,
                    sw: f64,
                    sh: f64,
                    dx: f64,
                    dy: f64,
                    dw: f64,
                    dh: f64)
@@ -1147,37 +1204,44 @@ impl CanvasRenderingContext2DMethods for
                                                                                     *x1,
                                                                                     *y1,
                                                                                     *r1,
                                                                                     Vec::new()))))
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-createpattern
     fn CreatePattern(&self,
-                     image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D,
+                     image: CanvasImageSource,
                      mut repetition: DOMString)
                      -> Fallible<Root<CanvasPattern>> {
         let (image_data, image_size) = match image {
-            HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::HTMLImageElement(ref image) => {
+            CanvasImageSource::HTMLImageElement(ref image) => {
                 // https://html.spec.whatwg.org/multipage/#img-error
                 // If the image argument is an HTMLImageElement object that is in the broken state,
                 // then throw an InvalidStateError exception
-                self.fetch_image_data(image).ok_or(Error::InvalidState)?
+                image.get_url()
+                    .and_then(|url| self.fetch_image_data(url))
+                    .ok_or(Error::InvalidState)?
             },
-            HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::HTMLCanvasElement(ref canvas) => {
+            CanvasImageSource::HTMLCanvasElement(ref canvas) => {
                 let _ = canvas.get_or_init_2d_context();
 
                 canvas.fetch_all_data().ok_or(Error::InvalidState)?
             },
-            HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::CanvasRenderingContext2D(ref context) => {
+            CanvasImageSource::CanvasRenderingContext2D(ref context) => {
                 let canvas = context.Canvas();
                 let _ = canvas.get_or_init_2d_context();
 
                 canvas.fetch_all_data().ok_or(Error::InvalidState)?
             }
+            CanvasImageSource::CSSStyleValue(ref value) => {
+                value.get_url(self.base_url.clone())
+                    .and_then(|url| self.fetch_image_data(url))
+                    .ok_or(Error::InvalidState)?
+            }
         };
 
         if repetition.is_empty() {
             repetition.push_str("repeat");
         }
 
         if let Ok(rep) = RepetitionStyle::from_str(&repetition) {
             Ok(CanvasPattern::new(&self.global(),
--- a/servo/components/script/dom/cssstylevalue.rs
+++ b/servo/components/script/dom/cssstylevalue.rs
@@ -1,20 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+use cssparser::Parser;
+use cssparser::ParserInput;
 use dom::bindings::codegen::Bindings::CSSStyleValueBinding::CSSStyleValueMethods;
 use dom::bindings::codegen::Bindings::CSSStyleValueBinding::Wrap;
 use dom::bindings::js::Root;
 use dom::bindings::reflector::Reflector;
 use dom::bindings::reflector::reflect_dom_object;
 use dom::bindings::str::DOMString;
 use dom::globalscope::GlobalScope;
 use dom_struct::dom_struct;
+use servo_url::ServoUrl;
 
 #[dom_struct]
 pub struct CSSStyleValue {
     reflector: Reflector,
     value: String,
 }
 
 impl CSSStyleValue {
@@ -39,8 +42,21 @@ impl CSSStyleValueMethods for CSSStyleVa
     /// This attribute is no longer part of the `CSSStyleValue` interface,
     /// but is still used in some examples.
     /// https://github.com/GoogleChrome/houdini-samples/issues/16
     // check-tidy: no specs after this line
     fn CssText(&self) -> DOMString {
         self.Stringifier()
     }
 }
+
+impl CSSStyleValue {
+    /// Parse the value as a `url()`.
+    /// TODO: This should really always be an absolute URL, but we currently
+    /// return relative URLs for computed values, so we pass in a base.
+    /// https://github.com/servo/servo/issues/17625
+    pub fn get_url(&self, base_url: ServoUrl) -> Option<ServoUrl> {
+        let mut input = ParserInput::new(&*self.value);
+        let mut parser = Parser::new(&mut input);
+        parser.expect_url().ok()
+            .and_then(|string| base_url.join(&*string).ok())
+    }
+}
--- a/servo/components/script/dom/paintrenderingcontext2d.rs
+++ b/servo/components/script/dom/paintrenderingcontext2d.rs
@@ -6,64 +6,72 @@ 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::HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2DOrCSSStyleValue;
 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::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::workletglobalscope::WorkletGlobalScope;
 use dom_struct::dom_struct;
 use euclid::ScaleFactor;
 use euclid::Size2D;
 use euclid::TypedSize2D;
 use ipc_channel::ipc::IpcSender;
+use servo_url::ServoUrl;
 use std::cell::Cell;
 use style_traits::CSSPixel;
 use style_traits::DevicePixel;
 
 #[dom_struct]
 pub struct PaintRenderingContext2D {
     context: CanvasRenderingContext2D,
     device_pixel_ratio: Cell<ScaleFactor<f32, CSSPixel, DevicePixel>>,
 }
 
 impl PaintRenderingContext2D {
     fn new_inherited(global: &PaintWorkletGlobalScope) -> PaintRenderingContext2D {
         let size = Size2D::zero();
+        let image_cache = global.image_cache();
+        let base_url = global.upcast::<WorkletGlobalScope>().base_url();
         PaintRenderingContext2D {
-            context: CanvasRenderingContext2D::new_inherited(global.upcast(), None, size),
+            context: CanvasRenderingContext2D::new_inherited(global.upcast(), None, image_cache, base_url, size),
             device_pixel_ratio: Cell::new(ScaleFactor::new(1.0)),
         }
     }
 
     pub fn new(global: &PaintWorkletGlobalScope) -> Root<PaintRenderingContext2D> {
         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 take_missing_image_urls(&self) -> Vec<ServoUrl> {
+        self.context.take_missing_image_urls()
+    }
+
     pub fn set_bitmap_dimensions(&self,
                                  size: TypedSize2D<f32, CSSPixel>,
                                  device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>)
     {
         let size = size * device_pixel_ratio;
         self.device_pixel_ratio.set(device_pixel_ratio);
         self.context.set_bitmap_dimensions(size.to_untyped().to_i32());
         self.scale_by_device_pixel_ratio();
@@ -182,37 +190,37 @@ impl PaintRenderingContext2DMethods for 
 
     // 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,
+                 image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2DOrCSSStyleValue,
                  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,
+                  image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2DOrCSSStyleValue,
                   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,
+                   image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2DOrCSSStyleValue,
                    sx: f64,
                    sy: f64,
                    sw: f64,
                    sh: f64,
                    dx: f64,
                    dy: f64,
                    dw: f64,
                    dh: f64)
@@ -304,17 +312,17 @@ impl PaintRenderingContext2DMethods for 
                             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,
+                     image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2DOrCSSStyleValue,
                      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()
--- a/servo/components/script/dom/paintworkletglobalscope.rs
+++ b/servo/components/script/dom/paintworkletglobalscope.rs
@@ -1,14 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use 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::get_property;
 use dom::bindings::conversions::get_property_jsval;
 use dom::bindings::error::Error;
@@ -23,56 +22,57 @@ use dom::paintsize::PaintSize;
 use dom::stylepropertymapreadonly::StylePropertyMapReadOnly;
 use dom::worklet::WorkletExecutor;
 use dom::workletglobalscope::WorkletGlobalScope;
 use dom::workletglobalscope::WorkletGlobalScopeInit;
 use dom::workletglobalscope::WorkletTask;
 use dom_struct::dom_struct;
 use euclid::ScaleFactor;
 use euclid::TypedSize2D;
-use ipc_channel::ipc::IpcSender;
-use ipc_channel::ipc::IpcSharedMemory;
+use ipc_channel::ipc;
 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;
 use js::jsapi::JSAutoCompartment;
 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 net_traits::image_cache::ImageCache;
 use script_layout_interface::message::Msg;
+use script_traits::DrawAPaintImageResult;
 use script_traits::Painter;
 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::Arc;
 use std::sync::Mutex;
+use std::sync::mpsc;
+use std::sync::mpsc::Sender;
 use style_traits::CSSPixel;
 use style_traits::DevicePixel;
 
 /// 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).
+    /// The image cache
     #[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>>>>,
 }
 
@@ -89,45 +89,50 @@ impl PaintWorkletGlobalScope {
             worklet_global: WorkletGlobalScope::new_inherited(pipeline_id, base_url, executor, init),
             image_cache: init.image_cache.clone(),
             paint_definitions: Default::default(),
             paint_class_instances: Default::default(),
         };
         unsafe { PaintWorkletGlobalScopeBinding::Wrap(runtime.cx(), global) }
     }
 
+    pub fn image_cache(&self) -> Arc<ImageCache> {
+        self.image_cache.clone()
+    }
+
     pub fn perform_a_worklet_task(&self, task: PaintWorkletTask) {
         match task {
-            PaintWorkletTask::DrawAPaintImage(name, size, device_pixel_ratio, properties, sender) => {
+            PaintWorkletTask::DrawAPaintImage(name, size_in_px, device_pixel_ratio, properties, sender) => {
                 let properties = StylePropertyMapReadOnly::from_iter(self.upcast(), properties);
-                self.draw_a_paint_image(name, size, device_pixel_ratio, &*properties, sender);
+                let result = self.draw_a_paint_image(name, size_in_px, device_pixel_ratio, &*properties);
+                let _ = sender.send(result);
             }
         }
     }
 
     /// https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image
     fn draw_a_paint_image(&self,
                           name: Atom,
                           size_in_px: TypedSize2D<f32, CSSPixel>,
                           device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>,
-                          properties: &StylePropertyMapReadOnly,
-                          sender: IpcSender<CanvasData>)
+                          properties: &StylePropertyMapReadOnly)
+                          -> DrawAPaintImageResult
     {
         // TODO: document paint definitions.
-        self.invoke_a_paint_callback(name, size_in_px, device_pixel_ratio, properties, sender);
+        self.invoke_a_paint_callback(name, size_in_px, device_pixel_ratio, properties)
     }
 
     /// https://drafts.css-houdini.org/css-paint-api/#invoke-a-paint-callback
     #[allow(unsafe_code)]
     fn invoke_a_paint_callback(&self,
                                name: Atom,
                                size_in_px: TypedSize2D<f32, CSSPixel>,
                                device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>,
-                               properties: &StylePropertyMapReadOnly,
-                               sender: IpcSender<CanvasData>)
+                               properties: &StylePropertyMapReadOnly)
+                               -> DrawAPaintImageResult
     {
         let size_in_dpx = size_in_px * device_pixel_ratio;
         let size_in_dpx = TypedSize2D::new(size_in_dpx.width.abs() as u32, size_in_dpx.height.abs() as u32);
         debug!("Invoking a paint callback {}({},{}) at {}.",
                name, size_in_px.width, size_in_px.height, device_pixel_ratio);
 
         let cx = self.worklet_global.get_cx();
         let _ac = JSAutoCompartment::new(cx, self.worklet_global.reflector().get_jsobject().get());
@@ -135,23 +140,23 @@ impl PaintWorkletGlobalScope {
         // 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());
         let rendering_context = match self.paint_definitions.borrow().get(&name) {
             None => {
                 // Step 2.2.
                 warn!("Drawing un-registered paint definition {}.", name);
-                return self.send_invalid_image(size_in_dpx, sender);
+                return self.invalid_image(size_in_dpx, vec![]);
             }
             Some(definition) => {
                 // Step 5.1
                 if !definition.constructor_valid_flag.get() {
                     debug!("Drawing invalid paint definition {}.", name);
-                    return self.send_invalid_image(size_in_dpx, sender);
+                    return self.invalid_image(size_in_dpx, vec![]);
                 }
                 class_constructor.set(definition.class_constructor.get());
                 paint_function.set(definition.paint_function.get());
                 Root::from_ref(&*definition.context)
             }
         };
 
         // Steps 5.2-5.4
@@ -169,17 +174,17 @@ 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);
-                    return self.send_invalid_image(size_in_dpx, sender);
+                    return self.invalid_image(size_in_dpx, vec![]);
                 }
                 // Step 5.4
                 entry.insert(Box::new(Heap::default())).set(paint_instance.get());
             }
         };
 
         // TODO: Steps 6-7
         // Step 8
@@ -197,65 +202,70 @@ impl PaintWorkletGlobalScope {
             ObjectValue(rendering_context.reflector().get_jsobject().get()),
             ObjectValue(paint_size.reflector().get_jsobject().get()),
             ObjectValue(properties.reflector().get_jsobject().get()),
         ];
         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()); }
+        let missing_image_urls = rendering_context.take_missing_image_urls();
 
         // Step 13.
         if unsafe { JS_IsExceptionPending(cx) } {
             debug!("Paint function threw an exception {}.", name);
             unsafe { JS_ClearPendingException(cx); }
-            return self.send_invalid_image(size_in_dpx, sender);
+            return self.invalid_image(size_in_dpx, missing_image_urls);
         }
 
+        let (sender, receiver) = ipc::channel().expect("IPC channel creation.");
         rendering_context.send_data(sender);
+        let image_key = match receiver.recv() {
+            Ok(CanvasData::Image(data)) => Some(data.image_key),
+            _ => None,
+        };
+
+        DrawAPaintImageResult {
+            width: size_in_dpx.width,
+            height: size_in_dpx.height,
+            format: PixelFormat::BGRA8,
+            image_key: image_key,
+            missing_image_urls: missing_image_urls,
+        }
     }
 
-    fn send_invalid_image(&self,
-                          size: TypedSize2D<u32, DevicePixel>,
-                          sender: IpcSender<CanvasData>)
-    {
-        debug!("Sending an invalid image.");
-        let width = size.width as u32;
-        let height = size.height as u32;
-        let len = (width as usize) * (height as usize) * 4;
-        let pixel = [0x00, 0x00, 0x00, 0x00];
-        let bytes: Vec<u8> = pixel.iter().cloned().cycle().take(len).collect();
-        let mut image = Image {
-            width: width,
-            height: height,
+    // https://drafts.csswg.org/css-images-4/#invalid-image
+    fn invalid_image(&self, size: TypedSize2D<u32, DevicePixel>, missing_image_urls: Vec<ServoUrl>)
+                     -> DrawAPaintImageResult {
+        debug!("Returning an invalid image.");
+        DrawAPaintImageResult {
+            width: size.width as u32,
+            height: size.height as u32,
             format: PixelFormat::BGRA8,
-            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);
+            image_key: None,
+            missing_image_urls: missing_image_urls,
+        }
     }
 
     fn painter(&self, name: Atom) -> Arc<Painter> {
         // Rather annoyingly we have to use a mutex here to make the painter Sync.
         struct WorkletPainter(Atom, Mutex<WorkletExecutor>);
         impl Painter for WorkletPainter {
             fn draw_a_paint_image(&self,
                                   size: TypedSize2D<f32, CSSPixel>,
                                   device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>,
-                                  properties: Vec<(Atom, String)>,
-                                  sender: IpcSender<CanvasData>)
+                                  properties: Vec<(Atom, String)>)
+                                  -> DrawAPaintImageResult
             {
                 let name = self.0.clone();
+                let (sender, receiver) = mpsc::channel();
                 let task = PaintWorkletTask::DrawAPaintImage(name, size, device_pixel_ratio, properties, sender);
                 self.1.lock().expect("Locking a painter.")
                     .schedule_a_worklet_task(WorkletTask::Paint(task));
+                receiver.recv().expect("Worklet thread died?")
             }
         }
         Arc::new(WorkletPainter(name, Mutex::new(self.worklet_global.executor())))
     }
 }
 
 impl PaintWorkletGlobalScopeMethods for PaintWorkletGlobalScope {
     #[allow(unsafe_code)]
@@ -341,17 +351,17 @@ impl PaintWorkletGlobalScopeMethods for 
 }
 
 /// Tasks which can be peformed by a paint worklet
 pub enum PaintWorkletTask {
     DrawAPaintImage(Atom,
                     TypedSize2D<f32, CSSPixel>,
                     ScaleFactor<f32, CSSPixel, DevicePixel>,
                     Vec<(Atom, String)>,
-                    IpcSender<CanvasData>)
+                    Sender<DrawAPaintImageResult>)
 }
 
 /// 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]
--- a/servo/components/script/dom/webidls/CanvasRenderingContext2D.webidl
+++ b/servo/components/script/dom/webidls/CanvasRenderingContext2D.webidl
@@ -3,18 +3,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 enum CanvasFillRule { "nonzero", "evenodd" };
 
 // https://html.spec.whatwg.org/multipage/#2dcontext
 typedef (HTMLImageElement or
          /* HTMLVideoElement or */
          HTMLCanvasElement or
-         CanvasRenderingContext2D /* or
-         ImageBitmap */) CanvasImageSource;
+         CanvasRenderingContext2D or
+         /* ImageBitmap or */
+         // This should probably be a CSSImageValue
+         // https://github.com/w3c/css-houdini-drafts/issues/416
+         CSSStyleValue) CanvasImageSource;
 
 //[Constructor(optional unsigned long width, unsigned long height)]
 interface CanvasRenderingContext2D {
 
   // back-reference to the canvas
   readonly attribute HTMLCanvasElement canvas;
 
   // canvas dimensions
--- a/servo/components/script_traits/lib.rs
+++ b/servo/components/script_traits/lib.rs
@@ -34,29 +34,29 @@ extern crate style_traits;
 extern crate time;
 extern crate webrender_api;
 extern crate webvr_traits;
 
 mod script_msg;
 pub mod webdriver_msg;
 
 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;
 use msg::constellation_msg::{BrowsingContextId, TopLevelBrowsingContextId, FrameType, Key, KeyModifiers, KeyState};
 use msg::constellation_msg::{PipelineId, PipelineNamespaceId, TraversalDirection};
 use net_traits::{FetchResponseMsg, ReferrerPolicy, ResourceThreads};
 use net_traits::image::base::Image;
+use net_traits::image::base::PixelFormat;
 use net_traits::image_cache::ImageCache;
 use net_traits::response::HttpsState;
 use net_traits::storage_thread::StorageType;
 use profile_traits::mem;
 use profile_traits::time as profile_time;
 use serde::{Deserialize, Deserializer, Serialize, Serializer};
 use servo_atoms::Atom;
 use servo_url::ImmutableOrigin;
@@ -64,16 +64,17 @@ use servo_url::ServoUrl;
 use std::collections::HashMap;
 use std::fmt;
 use std::sync::Arc;
 use std::sync::mpsc::{Receiver, Sender, RecvTimeoutError};
 use style_traits::CSSPixel;
 use style_traits::DevicePixel;
 use webdriver_msg::{LoadStatus, WebDriverScriptCommand};
 use webrender_api::ClipId;
+use webrender_api::ImageKey;
 use webvr_traits::{WebVREvent, WebVRMsg};
 
 pub use script_msg::{LayoutMsg, ScriptMsg, EventResult, LogEntry};
 pub use script_msg::{ServiceWorkerMsg, ScopeThings, SWManagerMsg, SWManagerSenders, DOMMessage};
 
 /// The address of a node. Layout sends these back. They must be validated via
 /// `from_untrusted_node_address` before they can be used, because we do not trust layout.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
@@ -818,12 +819,27 @@ impl From<RecvTimeoutError> for PaintWor
 }
 
 /// Execute paint code in the worklet thread pool.
 pub trait Painter: Sync + Send {
     /// https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image
     fn draw_a_paint_image(&self,
                           size: TypedSize2D<f32, CSSPixel>,
                           zoom: ScaleFactor<f32, CSSPixel, DevicePixel>,
-                          properties: Vec<(Atom, String)>,
-                          sender: IpcSender<CanvasData>);
+                          properties: Vec<(Atom, String)>)
+                          -> DrawAPaintImageResult;
 }
 
+/// The result of executing paint code: the image together with any image URLs that need to be loaded.
+/// TODO: this should return a WR display list. https://github.com/servo/servo/issues/17497
+#[derive(Debug, Deserialize, Serialize, Clone)]
+pub struct DrawAPaintImageResult {
+    /// The image height
+    pub width: u32,
+    /// The image width
+    pub height: u32,
+    /// The image format
+    pub format: PixelFormat,
+    /// The image drawn, or None if an invalid paint image was drawn
+    pub image_key: Option<ImageKey>,
+    /// Drawing the image might have requested loading some image URLs.
+    pub missing_image_urls: Vec<ServoUrl>,
+}