servo: Merge #20447 - NCSU Canvas Rendering Project Initial Steps (from Brody-Eastwood:master); r=jdm
authorBrody Eastwood <baeastwo@ncsu.edu>
Mon, 02 Apr 2018 22:24:34 -0400
changeset 411411 35f5e13e1098d838580b83f63bc3a52547ebd213
parent 411410 eab2985673a56ee09f5da6ae62b43a1dfc7e737d
child 411412 ae4d46495f5e78721e9cd68dc46e11b13592b875
push id33755
push useraiakab@mozilla.com
push dateTue, 03 Apr 2018 09:29:11 +0000
treeherdermozilla-central@2ee160335e15 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
milestone61.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 #20447 - NCSU Canvas Rendering Project Initial Steps (from Brody-Eastwood:master); r=jdm <!-- Please describe your changes on the following line: --> Implements the initial steps from: https://github.com/servo/servo/wiki/Canvas-rendering-project --- <!-- 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 - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [X] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- 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: 9cd60c8e78af7b80e7578612ae442de26d1a325b
servo/components/canvas/canvas_paint_thread.rs
servo/components/canvas_traits/canvas.rs
servo/components/constellation/constellation.rs
servo/components/layout/display_list/builder.rs
servo/components/layout/fragment.rs
servo/components/script/dom/bindings/trace.rs
servo/components/script/dom/canvasrenderingcontext2d.rs
servo/components/script/dom/htmlcanvaselement.rs
servo/components/script/dom/paintrenderingcontext2d.rs
servo/components/script_layout_interface/lib.rs
servo/components/script_traits/script_msg.rs
servo/tests/html/test_canvas_drawimage_canvas.html
servo/tests/html/test_canvas_drawimage_canvas_timed.html
--- a/servo/components/canvas/canvas_paint_thread.rs
+++ b/servo/components/canvas/canvas_paint_thread.rs
@@ -59,16 +59,17 @@ pub struct CanvasPaintThread<'a> {
     state: CanvasPaintState<'a>,
     saved_states: Vec<CanvasPaintState<'a>>,
     webrender_api: webrender_api::RenderApi,
     image_key: Option<webrender_api::ImageKey>,
     /// An old webrender image key that can be deleted when the next epoch ends.
     old_image_key: Option<webrender_api::ImageKey>,
     /// An old webrender image key that can be deleted when the current epoch ends.
     very_old_image_key: Option<webrender_api::ImageKey>,
+    canvas_id: CanvasId,
 }
 
 #[derive(Clone)]
 struct CanvasPaintState<'a> {
     draw_options: DrawOptions,
     fill_style: Pattern,
     stroke_style: Pattern,
     stroke_opts: StrokeOptions<'a>,
@@ -94,50 +95,54 @@ impl<'a> CanvasPaintState<'a> {
             shadow_color: Color::transparent(),
         }
     }
 }
 
 impl<'a> CanvasPaintThread<'a> {
     fn new(size: Size2D<i32>,
            webrender_api_sender: webrender_api::RenderApiSender,
-           antialias: AntialiasMode) -> CanvasPaintThread<'a> {
+           antialias: AntialiasMode,
+           canvas_id: CanvasId) -> CanvasPaintThread<'a> {
         let draw_target = CanvasPaintThread::create(size);
         let path_builder = draw_target.create_path_builder();
         let webrender_api = webrender_api_sender.create_api();
         CanvasPaintThread {
             drawtarget: draw_target,
             path_builder: path_builder,
             state: CanvasPaintState::new(antialias),
             saved_states: vec![],
             webrender_api: webrender_api,
             image_key: None,
             old_image_key: None,
             very_old_image_key: None,
+            canvas_id: canvas_id,
         }
     }
 
     /// Creates a new `CanvasPaintThread` and returns an `IpcSender` to
     /// communicate with it.
     pub fn start(size: Size2D<i32>,
                  webrender_api_sender: webrender_api::RenderApiSender,
-                 antialias: bool)
+                 antialias: bool,
+                 canvas_id: CanvasId)
                  -> IpcSender<CanvasMsg> {
         let (sender, receiver) = ipc::channel::<CanvasMsg>().unwrap();
         let antialias = if antialias {
             AntialiasMode::Default
         } else {
             AntialiasMode::None
         };
         thread::Builder::new().name("CanvasThread".to_owned()).spawn(move || {
-            let mut painter = CanvasPaintThread::new(size, webrender_api_sender, antialias);
+            let mut painter = CanvasPaintThread::new(size, webrender_api_sender, antialias, canvas_id);
             loop {
                 let msg = receiver.recv();
                 match msg.unwrap() {
-                    CanvasMsg::Canvas2d(message) => {
+                    CanvasMsg::Canvas2d(message, canvas_id) => {
+                        assert!(canvas_id == painter.canvas_id);
                         match message {
                             Canvas2dMsg::FillText(text, x, y, max_width) => painter.fill_text(text, x, y, max_width),
                             Canvas2dMsg::FillRect(ref rect) => painter.fill_rect(rect),
                             Canvas2dMsg::StrokeRect(ref rect) => painter.stroke_rect(rect),
                             Canvas2dMsg::ClearRect(ref rect) => painter.clear_rect(rect),
                             Canvas2dMsg::BeginPath => painter.begin_path(),
                             Canvas2dMsg::ClosePath => painter.close_path(),
                             Canvas2dMsg::Fill => painter.fill(),
@@ -160,20 +165,20 @@ impl<'a> CanvasPaintThread<'a> {
                                     source_rect,
                                     smoothing_enabled,
                                 )
                             }
                             Canvas2dMsg::DrawImageSelf(image_size, dest_rect, source_rect, smoothing_enabled) => {
                                 painter.draw_image_self(image_size, dest_rect, source_rect, smoothing_enabled)
                             }
                             Canvas2dMsg::DrawImageInOther(
-                                renderer, image_size, dest_rect, source_rect, smoothing, sender
+                                renderer, other_canvas_id, image_size, dest_rect, source_rect, smoothing, sender
                             ) => {
                                 painter.draw_image_in_other(
-                                    renderer, image_size, dest_rect, source_rect, smoothing, sender)
+                                    renderer, other_canvas_id, image_size, dest_rect, source_rect, smoothing, sender)
                             }
                             Canvas2dMsg::MoveTo(ref point) => painter.move_to(point),
                             Canvas2dMsg::LineTo(ref point) => painter.line_to(point),
                             Canvas2dMsg::Rect(ref rect) => painter.rect(rect),
                             Canvas2dMsg::QuadraticCurveTo(ref cp, ref pt) => {
                                 painter.quadratic_curve_to(cp, pt)
                             }
                             Canvas2dMsg::BezierCurveTo(ref cp1, ref cp2, ref pt) => {
@@ -215,32 +220,40 @@ impl<'a> CanvasPaintThread<'a> {
                                 )
                             }
                             Canvas2dMsg::SetShadowOffsetX(value) => painter.set_shadow_offset_x(value),
                             Canvas2dMsg::SetShadowOffsetY(value) => painter.set_shadow_offset_y(value),
                             Canvas2dMsg::SetShadowBlur(value) => painter.set_shadow_blur(value),
                             Canvas2dMsg::SetShadowColor(ref color) => painter.set_shadow_color(color.to_azure_style()),
                         }
                     },
-                    CanvasMsg::Close => break,
-                    CanvasMsg::Recreate(size) => painter.recreate(size),
-                    CanvasMsg::FromScript(message) => {
+                    CanvasMsg::Close(canvas_id) =>{
+                        assert!(canvas_id == painter.canvas_id);
+                        break;
+                    },
+                    CanvasMsg::Recreate(size, canvas_id) =>{
+                        assert!(canvas_id == painter.canvas_id);
+                        painter.recreate(size);
+                    },
+                    CanvasMsg::FromScript(message, canvas_id) => {
+                        assert!(canvas_id == painter.canvas_id);
                         match message {
                             FromScriptMsg::SendPixels(chan) => {
                                 painter.send_pixels(chan)
                             }
                         }
-                    }
-                    CanvasMsg::FromLayout(message) => {
+                    },
+                    CanvasMsg::FromLayout(message, canvas_id) => {
+                        assert!(canvas_id == painter.canvas_id);
                         match message {
                             FromLayoutMsg::SendData(chan) => {
                                 painter.send_data(chan)
                             }
                         }
-                    }
+                    },
                 }
             }
         }).expect("Thread spawning failed");
 
         sender
     }
 
     fn save_context_state(&mut self) {
@@ -410,32 +423,35 @@ impl<'a> CanvasPaintThread<'a> {
             write_image(&self.drawtarget, image_data, image_size, dest_rect,
                         smoothing_enabled, self.state.draw_options.composition,
                         self.state.draw_options.alpha);
         }
     }
 
     fn draw_image_in_other(&self,
                            renderer: IpcSender<CanvasMsg>,
+                           other_canvas_id: CanvasId,
                            image_size: Size2D<f64>,
                            dest_rect: Rect<f64>,
                            source_rect: Rect<f64>,
                            smoothing_enabled: bool,
                            sender: IpcSender<()>) {
         let mut image_data = self.read_pixels(source_rect.to_i32(), image_size);
         // TODO: avoid double byte_swap.
         byte_swap(&mut image_data);
 
         let msg = CanvasMsg::Canvas2d(Canvas2dMsg::DrawImage(
             image_data.into(),
             source_rect.size,
             dest_rect,
             source_rect,
             smoothing_enabled,
-        ));
+            ),
+            other_canvas_id,
+        );
         renderer.send(msg).unwrap();
         // We acknowledge to the caller here that the data was sent to the
         // other canvas so that if JS immediately afterwards try to get the
         // pixels of the other one, it won't retrieve the other values.
         sender.send(()).unwrap();
     }
 
     fn move_to(&self, point: &Point2D<AzFloat>) {
--- a/servo/components/canvas_traits/canvas.rs
+++ b/servo/components/canvas_traits/canvas.rs
@@ -11,38 +11,41 @@ use std::str::FromStr;
 use webrender_api;
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum FillRule {
     Nonzero,
     Evenodd,
 }
 
+#[derive(Clone, Deserialize, MallocSizeOf, PartialEq, Serialize)]
+pub struct CanvasId(pub u64);
+
 #[derive(Clone, Deserialize, Serialize)]
 pub enum CanvasMsg {
-    Canvas2d(Canvas2dMsg),
-    FromLayout(FromLayoutMsg),
-    FromScript(FromScriptMsg),
-    Recreate(Size2D<i32>),
-    Close,
+    Canvas2d(Canvas2dMsg, CanvasId),
+    FromLayout(FromLayoutMsg, CanvasId),
+    FromScript(FromScriptMsg, CanvasId),
+    Recreate(Size2D<i32>, CanvasId),
+    Close(CanvasId),
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub struct CanvasImageData {
     pub image_key: webrender_api::ImageKey,
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum Canvas2dMsg {
     Arc(Point2D<f32>, f32, f32, f32, bool),
     ArcTo(Point2D<f32>, Point2D<f32>, f32),
     DrawImage(ByteBuf, Size2D<f64>, Rect<f64>, Rect<f64>, bool),
     DrawImageSelf(Size2D<f64>, Rect<f64>, Rect<f64>, bool),
     DrawImageInOther(
-        IpcSender<CanvasMsg>, Size2D<f64>, Rect<f64>, Rect<f64>, bool, IpcSender<()>),
+        IpcSender<CanvasMsg>, CanvasId, Size2D<f64>, Rect<f64>, Rect<f64>, bool, IpcSender<()>),
     BeginPath,
     BezierCurveTo(Point2D<f32>, Point2D<f32>, Point2D<f32>),
     ClearRect(Rect<f32>),
     Clip,
     ClosePath,
     Ellipse(Point2D<f32>, f32, f32, f32, f32, f32, bool),
     Fill,
     FillText(String, f64, f64, Option<f64>),
--- a/servo/components/constellation/constellation.rs
+++ b/servo/components/constellation/constellation.rs
@@ -90,16 +90,17 @@
 //! See https://github.com/servo/servo/issues/14704
 
 use backtrace::Backtrace;
 use bluetooth_traits::BluetoothRequest;
 use browsingcontext::{BrowsingContext, SessionHistoryChange, SessionHistoryEntry};
 use browsingcontext::{FullyActiveBrowsingContextsIterator, AllBrowsingContextsIterator};
 use canvas::canvas_paint_thread::CanvasPaintThread;
 use canvas::webgl_thread::WebGLThreads;
+use canvas_traits::canvas::CanvasId;
 use canvas_traits::canvas::CanvasMsg;
 use clipboard::{ClipboardContext, ClipboardProvider};
 use compositing::SendableFrameTree;
 use compositing::compositor_thread::{CompositorProxy, EmbedderMsg, EmbedderProxy};
 use compositing::compositor_thread::Msg as ToCompositorMsg;
 use debugger;
 use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg};
 use euclid::{Size2D, TypedSize2D, TypedScale};
@@ -324,16 +325,19 @@ pub struct Constellation<Message, LTF, S
     /// Phantom data that keeps the Rust type system happy.
     phantom: PhantomData<(Message, LTF, STF)>,
 
     /// Entry point to create and get channels to a WebGLThread.
     webgl_threads: Option<WebGLThreads>,
 
     /// A channel through which messages can be sent to the webvr thread.
     webvr_chan: Option<IpcSender<WebVRMsg>>,
+
+    /// An Id for the next canvas to use.
+    canvas_id: CanvasId,
 }
 
 /// State needed to construct a constellation.
 pub struct InitialConstellationState {
     /// A channel through which messages can be sent to the embedder.
     pub embedder_proxy: EmbedderProxy,
 
     /// A channel through which messages can be sent to the compositor.
@@ -621,16 +625,17 @@ impl<Message, LTF, STF> Constellation<Me
                     let seed = opts::get().random_pipeline_closure_seed.unwrap_or_else(random);
                     let rng = ServoRng::from_seed(&[seed]);
                     warn!("Randomly closing pipelines.");
                     info!("Using seed {} for random pipeline closure.", seed);
                     (rng, prob)
                 }),
                 webgl_threads: state.webgl_threads,
                 webvr_chan: state.webvr_chan,
+                canvas_id: CanvasId(0),
             };
 
             constellation.run();
         }).expect("Thread spawning failed");
 
         (compositor_sender, swmanager_sender)
     }
 
@@ -2198,21 +2203,22 @@ impl<Message, LTF, STF> Constellation<Me
                 self.handle_send_error(parent_pipeline_id, e);
             }
         }
     }
 
     fn handle_create_canvas_paint_thread_msg(
             &mut self,
             size: &Size2D<i32>,
-            response_sender: IpcSender<IpcSender<CanvasMsg>>) {
+            response_sender: IpcSender<(IpcSender<CanvasMsg>, CanvasId)>) {
+        self.canvas_id.0 += 1;
         let webrender_api = self.webrender_api_sender.clone();
         let sender = CanvasPaintThread::start(*size, webrender_api,
-                                              opts::get().enable_canvas_antialiasing);
-        if let Err(e) = response_sender.send(sender) {
+                                              opts::get().enable_canvas_antialiasing, self.canvas_id.clone());
+        if let Err(e) = response_sender.send((sender, self.canvas_id.clone())) {
             warn!("Create canvas paint thread response failed ({})", e);
         }
     }
 
     fn handle_webdriver_msg(&mut self, msg: WebDriverCommandMsg) {
         // Find the script channel for the given parent pipeline,
         // and pass the event to that script thread.
         match msg {
--- a/servo/components/layout/display_list/builder.rs
+++ b/servo/components/layout/display_list/builder.rs
@@ -1795,17 +1795,18 @@ impl FragmentDisplayListBuilding for Fra
 
                 let (image_key, format) = match canvas_fragment_info.source {
                     CanvasFragmentSource::WebGL(image_key) => (image_key, PixelFormat::BGRA8),
                     CanvasFragmentSource::Image(ref ipc_renderer) => match *ipc_renderer {
                         Some(ref ipc_renderer) => {
                             let ipc_renderer = ipc_renderer.lock().unwrap();
                             let (sender, receiver) = ipc::channel().unwrap();
                             ipc_renderer
-                                .send(CanvasMsg::FromLayout(FromLayoutMsg::SendData(sender)))
+                                .send(CanvasMsg::FromLayout(FromLayoutMsg::SendData(sender),
+                                canvas_fragment_info.canvas_id.clone()))
                                 .unwrap();
                             (receiver.recv().unwrap().image_key, PixelFormat::BGRA8)
                         },
                         None => return,
                     },
                 };
 
                 let base = create_base_display_item(state);
--- a/servo/components/layout/fragment.rs
+++ b/servo/components/layout/fragment.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! The `Fragment` type, which represents the leaves of the layout tree.
 
 #![deny(unsafe_code)]
 
 use ServoArc;
 use app_units::Au;
-use canvas_traits::canvas::CanvasMsg;
+use canvas_traits::canvas::{CanvasMsg, CanvasId};
 use context::{LayoutContext, with_thread_local_font_context};
 use display_list::ToLayout;
 use euclid::{Point2D, Vector2D, Rect, Size2D};
 use floats::ClearType;
 use flow::{GetBaseFlow, ImmutableFlowUtils};
 use flow_ref::FlowRef;
 use gfx;
 use gfx::display_list::{BLUR_INFLATION_FACTOR, OpaqueNode};
@@ -340,16 +340,17 @@ pub enum CanvasFragmentSource {
     Image(Option<Arc<Mutex<IpcSender<CanvasMsg>>>>)
 }
 
 #[derive(Clone)]
 pub struct CanvasFragmentInfo {
     pub source: CanvasFragmentSource,
     pub dom_width: Au,
     pub dom_height: Au,
+    pub canvas_id: CanvasId,
 }
 
 impl CanvasFragmentInfo {
     pub fn new(data: HTMLCanvasData) -> CanvasFragmentInfo {
         let source = match data.source {
             HTMLCanvasDataSource::WebGL(texture_id) => {
                 CanvasFragmentSource::WebGL(texture_id)
             },
@@ -357,16 +358,17 @@ impl CanvasFragmentInfo {
                 CanvasFragmentSource::Image(ipc_sender.map(|renderer| Arc::new(Mutex::new(renderer))))
             }
         };
 
         CanvasFragmentInfo {
             source: source,
             dom_width: Au::from_px(data.width as i32),
             dom_height: Au::from_px(data.height as i32),
+            canvas_id: data.canvas_id,
         }
     }
 }
 
 #[derive(Clone)]
 pub struct SvgFragmentInfo {
     pub dom_width: Au,
     pub dom_height: Au,
--- a/servo/components/script/dom/bindings/trace.rs
+++ b/servo/components/script/dom/bindings/trace.rs
@@ -25,17 +25,17 @@
 //!    will add the object to the graph, and will trace that object as well.
 //! 5. When the GC finishes tracing, it [`finalizes`](../index.html#destruction)
 //!    any reflectors that were not reachable.
 //!
 //! The `unsafe_no_jsmanaged_fields!()` macro adds an empty implementation of
 //! `JSTraceable` to a datatype.
 
 use app_units::Au;
-use canvas_traits::canvas::{CanvasGradientStop, LinearGradientStyle, RadialGradientStyle};
+use canvas_traits::canvas::{CanvasGradientStop, CanvasId, LinearGradientStyle, RadialGradientStyle};
 use canvas_traits::canvas::{CompositionOrBlending, LineCapStyle, LineJoinStyle, RepetitionStyle};
 use canvas_traits::webgl::{WebGLBufferId, WebGLFramebufferId, WebGLProgramId, WebGLRenderbufferId};
 use canvas_traits::webgl::{WebGLChan, WebGLContextShareMode, WebGLError, WebGLPipeline, WebGLMsgSender};
 use canvas_traits::webgl::{WebGLReceiver, WebGLSender, WebGLShaderId, WebGLTextureId, WebGLVertexArrayId};
 use canvas_traits::webgl::{WebGLSLVersion, WebGLVersion};
 use cssparser::RGBA;
 use devtools_traits::{CSSError, TimelineMarkerType, WorkerId};
 use dom::abstractworker::SharedRt;
@@ -418,16 +418,17 @@ unsafe_no_jsmanaged_fields!(WebGLTexture
 unsafe_no_jsmanaged_fields!(WebGLVertexArrayId);
 unsafe_no_jsmanaged_fields!(WebGLVersion);
 unsafe_no_jsmanaged_fields!(WebGLSLVersion);
 unsafe_no_jsmanaged_fields!(MediaList);
 unsafe_no_jsmanaged_fields!(WebVRGamepadHand);
 unsafe_no_jsmanaged_fields!(ScriptToConstellationChan);
 unsafe_no_jsmanaged_fields!(InteractiveMetrics);
 unsafe_no_jsmanaged_fields!(InteractiveWindow);
+unsafe_no_jsmanaged_fields!(CanvasId);
 
 unsafe impl<'a> JSTraceable for &'a str {
     #[inline]
     unsafe fn trace(&self, _: *mut JSTracer) {
         // Do nothing
     }
 }
 
--- a/servo/components/script/dom/canvasrenderingcontext2d.rs
+++ b/servo/components/script/dom/canvasrenderingcontext2d.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use canvas_traits::canvas::{Canvas2dMsg, CanvasMsg};
+use canvas_traits::canvas::{Canvas2dMsg, CanvasMsg, CanvasId};
 use canvas_traits::canvas::{CompositionOrBlending, FillOrStrokeStyle, FillRule};
 use canvas_traits::canvas::{LineCapStyle, LineJoinStyle, LinearGradientStyle};
 use canvas_traits::canvas::{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::CanvasRenderingContext2DBinding;
 use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasFillRule;
@@ -73,16 +73,17 @@ pub struct CanvasRenderingContext2D {
     /// 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>,
+    canvas_id: CanvasId,
 }
 
 #[must_root]
 #[derive(Clone, JSTraceable, MallocSizeOf)]
 struct CanvasContextState {
     global_alpha: f64,
     global_composition: CompositionOrBlending,
     image_smoothing_enabled: bool,
@@ -128,28 +129,29 @@ impl CanvasRenderingContext2D {
                          base_url: ServoUrl,
                          size: Size2D<i32>)
                          -> CanvasRenderingContext2D {
         debug!("Creating new canvas rendering context.");
         let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
         let script_to_constellation_chan = global.script_to_constellation_chan();
         debug!("Asking constellation to create new canvas thread.");
         script_to_constellation_chan.send(ScriptMsg::CreateCanvasPaintThread(size, sender)).unwrap();
-        let ipc_renderer = receiver.recv().unwrap();
+        let (ipc_renderer, canvas_id) = receiver.recv().unwrap();
         debug!("Done.");
         CanvasRenderingContext2D {
             reflector_: Reflector::new(),
             ipc_renderer: ipc_renderer,
             canvas: canvas.map(Dom::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),
+            canvas_id: canvas_id,
         }
     }
 
     pub fn new(global: &GlobalScope,
                canvas: &HTMLCanvasElement,
                size: Size2D<i32>)
                -> DomRoot<CanvasRenderingContext2D> {
         let window = window_from_node(canvas);
@@ -160,36 +162,34 @@ impl CanvasRenderingContext2D {
         ));
         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::Recreate(size))
+            .send(CanvasMsg::Recreate(size, self.get_canvas_id()))
             .unwrap();
     }
 
     // https://html.spec.whatwg.org/multipage/#reset-the-rendering-context-to-its-default-state
     fn reset_to_initial_state(&self) {
         self.saved_states.borrow_mut().clear();
         *self.state.borrow_mut() = CanvasContextState::new();
     }
 
     fn mark_as_dirty(&self) {
         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()
+        self.send_canvas_2d_msg(Canvas2dMsg::SetTransform(self.state.borrow().transform))
     }
 
     // It is used by DrawImage to calculate the size of the source and destination rectangles based
     // on the drawImage call arguments
     // source rectangle = area of the original image to be copied
     // destination rectangle = area of the destination canvas where the source image is going to be drawn
     fn adjust_source_dest_rects(&self,
                                 image_size: Size2D<f64>,
@@ -358,33 +358,34 @@ impl CanvasRenderingContext2D {
 
         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.as_ref().map_or(false, |c| &**c == canvas) {
-            let msg = CanvasMsg::Canvas2d(Canvas2dMsg::DrawImageSelf(
+            self.send_canvas_2d_msg(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),
             };
 
             let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
             let msg = CanvasMsg::Canvas2d(Canvas2dMsg::DrawImageInOther(
                 self.ipc_renderer.clone(),
+                self.get_canvas_id(),
                 image_size,
                 dest_rect,
                 source_rect,
                 smoothing_enabled,
-                sender));
+                sender),
+                context.get_canvas_id());
 
             let renderer = context.get_ipc_renderer();
             renderer.send(msg).unwrap();
             receiver.recv().unwrap();
         };
 
         self.mark_as_dirty();
         Ok(())
@@ -448,23 +449,23 @@ impl CanvasRenderingContext2D {
                                                                      dw,
                                                                      dh);
 
         if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
             return Ok(());
         }
 
         let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
-        self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::DrawImage(
+        self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
             image_data.into(),
             image_size,
             dest_rect,
             source_rect,
             smoothing_enabled,
-        ))).unwrap();
+        ));
         self.mark_as_dirty();
         Ok(())
     }
 
     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(_, _) |
@@ -552,39 +553,54 @@ impl CanvasRenderingContext2D {
                 },
                 _ => Err(())
             }
         } else {
             Err(())
         }
     }
 
+    pub fn get_canvas_id(&self) -> CanvasId {
+        self.canvas_id.clone()
+    }
+
+    pub fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) {
+        self.ipc_renderer.send(CanvasMsg::Canvas2d(msg, self.get_canvas_id())).unwrap()
+    }
+
     pub fn get_ipc_renderer(&self) -> IpcSender<CanvasMsg> {
         self.ipc_renderer.clone()
     }
 
     pub fn origin_is_clean(&self) -> bool {
         self.origin_clean.get()
     }
 
     fn set_origin_unclean(&self) {
         self.origin_clean.set(false)
     }
 }
 
 pub trait LayoutCanvasRenderingContext2DHelpers {
     #[allow(unsafe_code)]
     unsafe fn get_ipc_renderer(&self) -> IpcSender<CanvasMsg>;
+    #[allow(unsafe_code)]
+    unsafe fn get_canvas_id(&self) -> CanvasId;
 }
 
 impl LayoutCanvasRenderingContext2DHelpers for LayoutDom<CanvasRenderingContext2D> {
     #[allow(unsafe_code)]
     unsafe fn get_ipc_renderer(&self) -> IpcSender<CanvasMsg> {
         (*self.unsafe_get()).ipc_renderer.clone()
     }
+
+    #[allow(unsafe_code)]
+    unsafe fn get_canvas_id(&self) -> CanvasId {
+        (*self.unsafe_get()).canvas_id.clone()
+    }
 }
 
 // We add a guard to each of methods by the spec:
 // http://www.w3.org/html/wg/drafts/2dcontext/html5_canvas_CR/
 //
 // > Except where otherwise specified, for the 2D context interface,
 // > any method call with a numeric argument whose value is infinite or a NaN value must be ignored.
 //
@@ -597,26 +613,26 @@ impl CanvasRenderingContext2DMethods for
         // This method is not called from a paint worklet rendering context,
         // so it's OK to panic if self.canvas is None.
         DomRoot::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();
+        self.send_canvas_2d_msg(Canvas2dMsg::SaveContext);
     }
 
     #[allow(unrooted_must_root)]
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-restore
     fn Restore(&self) {
         let mut saved_states = self.saved_states.borrow_mut();
         if let Some(state) = saved_states.pop() {
             self.state.borrow_mut().clone_from(&state);
-            self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::RestoreContext)).unwrap();
+            self.send_canvas_2d_msg(Canvas2dMsg::RestoreContext);
         }
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-scale
     fn Scale(&self, x: f64, y: f64) {
         if !(x.is_finite() && y.is_finite()) {
             return;
         }
@@ -691,114 +707,104 @@ impl CanvasRenderingContext2DMethods for
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalalpha
     fn SetGlobalAlpha(&self, alpha: f64) {
         if !alpha.is_finite() || alpha > 1.0 || alpha < 0.0 {
             return;
         }
 
         self.state.borrow_mut().global_alpha = alpha;
-        self.ipc_renderer
-            .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetGlobalAlpha(alpha as f32)))
-            .unwrap()
+        self.send_canvas_2d_msg(Canvas2dMsg::SetGlobalAlpha(alpha as f32))
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalcompositeoperation
     fn GlobalCompositeOperation(&self) -> DOMString {
         let state = self.state.borrow();
         match state.global_composition {
             CompositionOrBlending::Composition(op) => DOMString::from(op.to_str()),
             CompositionOrBlending::Blending(op) => DOMString::from(op.to_str()),
         }
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalcompositeoperation
     fn SetGlobalCompositeOperation(&self, op_str: DOMString) {
         if let Ok(op) = CompositionOrBlending::from_str(&op_str) {
             self.state.borrow_mut().global_composition = op;
-            self.ipc_renderer
-                .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetGlobalComposition(op)))
-                .unwrap()
+            self.send_canvas_2d_msg(Canvas2dMsg::SetGlobalComposition(op))
         }
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-fillrect
     fn FillRect(&self, x: f64, y: f64, width: f64, height: f64) {
         if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
-            self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::FillRect(rect))).unwrap();
+            self.send_canvas_2d_msg(Canvas2dMsg::FillRect(rect));
             self.mark_as_dirty();
         }
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-clearrect
     fn ClearRect(&self, x: f64, y: f64, width: f64, height: f64) {
         if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
-            self.ipc_renderer
-                .send(CanvasMsg::Canvas2d(Canvas2dMsg::ClearRect(rect)))
-                .unwrap();
+            self.send_canvas_2d_msg(Canvas2dMsg::ClearRect(rect));
             self.mark_as_dirty();
         }
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokerect
     fn StrokeRect(&self, x: f64, y: f64, width: f64, height: f64) {
         if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
-            self.ipc_renderer
-                .send(CanvasMsg::Canvas2d(Canvas2dMsg::StrokeRect(rect)))
-                .unwrap();
+            self.send_canvas_2d_msg(Canvas2dMsg::StrokeRect(rect));
             self.mark_as_dirty();
         }
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-beginpath
     fn BeginPath(&self) {
-        self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::BeginPath)).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::BeginPath);
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-closepath
     fn ClosePath(&self) {
-        self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::ClosePath)).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::ClosePath);
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-fill
     fn Fill(&self, _: CanvasFillRule) {
         // TODO: Process fill rule
-        self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::Fill)).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::Fill);
         self.mark_as_dirty();
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke
     fn Stroke(&self) {
-        self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::Stroke)).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::Stroke);
         self.mark_as_dirty();
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
     fn Clip(&self, _: CanvasFillRule) {
         // TODO: Process fill rule
-        self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::Clip)).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::Clip);
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath
     fn IsPointInPath(&self, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool {
         let fill_rule = match fill_rule {
             CanvasFillRule::Nonzero => FillRule::Nonzero,
             CanvasFillRule::Evenodd => FillRule::Evenodd,
         };
         let (sender, receiver) = ipc::channel::<bool>(self.global().time_profiler_chan().clone()).unwrap();
-        self.ipc_renderer
-            .send(CanvasMsg::Canvas2d(Canvas2dMsg::IsPointInPath(x, y, fill_rule, sender)))
-            .unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::IsPointInPath(x, y, fill_rule, sender));
         receiver.recv().unwrap()
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-filltext
     fn FillText(&self, text: DOMString, x: f64, y: f64, max_width: Option<f64>) {
         let parsed_text: String = text.into();
-        self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::FillText(parsed_text, x, y, max_width))).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::FillText(parsed_text, x, y, max_width));
         self.mark_as_dirty();
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
     fn DrawImage(&self,
                  image: CanvasImageSource,
                  dx: f64,
                  dy: f64)
@@ -853,122 +859,109 @@ impl CanvasRenderingContext2DMethods for
                         Some(dh))
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-moveto
     fn MoveTo(&self, x: f64, y: f64) {
         if !(x.is_finite() && y.is_finite()) {
             return;
         }
-
-        let msg = CanvasMsg::Canvas2d(Canvas2dMsg::MoveTo(Point2D::new(x as f32, y as f32)));
-        self.ipc_renderer.send(msg).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::MoveTo(Point2D::new(x as f32, y as f32)));
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-lineto
     fn LineTo(&self, x: f64, y: f64) {
         if !(x.is_finite() && y.is_finite()) {
             return;
         }
-
-        let msg = CanvasMsg::Canvas2d(Canvas2dMsg::LineTo(Point2D::new(x as f32, y as f32)));
-        self.ipc_renderer.send(msg).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::LineTo(Point2D::new(x as f32, y as f32)));
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-rect
     fn Rect(&self, x: f64, y: f64, width: f64, height: f64) {
         if [x, y, width, height].iter().all(|val| val.is_finite()) {
             let rect = Rect::new(Point2D::new(x as f32, y as f32),
                                  Size2D::new(width as f32, height as f32));
-            let msg = CanvasMsg::Canvas2d(Canvas2dMsg::Rect(rect));
-            self.ipc_renderer.send(msg).unwrap();
+            self.send_canvas_2d_msg(Canvas2dMsg::Rect(rect));
         }
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-quadraticcurveto
     fn QuadraticCurveTo(&self, cpx: f64, cpy: f64, x: f64, y: f64) {
         if !(cpx.is_finite() && cpy.is_finite() && x.is_finite() && y.is_finite()) {
             return;
         }
-
-        let msg = CanvasMsg::Canvas2d(Canvas2dMsg::QuadraticCurveTo(Point2D::new(cpx as f32,
-                                                                                 cpy as f32),
-                                                                    Point2D::new(x as f32,
-                                                                                 y as f32)));
-        self.ipc_renderer.send(msg).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::QuadraticCurveTo(Point2D::new(cpx as f32,
+                                                                           cpy as f32),
+                                                              Point2D::new(x as f32,
+                                                                           y as f32)));
     }
 
     // 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) {
         if !(cp1x.is_finite() && cp1y.is_finite() && cp2x.is_finite() && cp2y.is_finite() &&
              x.is_finite() && y.is_finite()) {
             return;
         }
-
-        let msg = CanvasMsg::Canvas2d(Canvas2dMsg::BezierCurveTo(Point2D::new(cp1x as f32,
-                                                                              cp1y as f32),
-                                                                 Point2D::new(cp2x as f32,
-                                                                              cp2y as f32),
-                                                                 Point2D::new(x as f32, y as f32)));
-        self.ipc_renderer.send(msg).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::BezierCurveTo(Point2D::new(cp1x as f32,
+                                                                        cp1y as f32),
+                                                           Point2D::new(cp2x as f32,
+                                                                        cp2y as f32),
+                                                           Point2D::new(x as f32, y as f32)));
     }
 
     // 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 {
         if !([x, y, r, start, end].iter().all(|x| x.is_finite())) {
             return Ok(());
         }
 
         if r < 0.0 {
             return Err(Error::IndexSize);
         }
 
-        let msg = CanvasMsg::Canvas2d(Canvas2dMsg::Arc(Point2D::new(x as f32, y as f32),
-                                                       r as f32,
-                                                       start as f32,
-                                                       end as f32,
-                                                       ccw));
-
-        self.ipc_renderer.send(msg).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::Arc(Point2D::new(x as f32, y as f32),
+                                                 r as f32,
+                                                 start as f32,
+                                                 end as f32,
+                                                 ccw));
         Ok(())
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-arcto
     fn ArcTo(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, r: f64) -> ErrorResult {
         if !([cp1x, cp1y, cp2x, cp2y, r].iter().all(|x| x.is_finite())) {
             return Ok(());
         }
         if r < 0.0 {
             return Err(Error::IndexSize);
         }
 
-        let msg = CanvasMsg::Canvas2d(Canvas2dMsg::ArcTo(Point2D::new(cp1x as f32, cp1y as f32),
-                                                         Point2D::new(cp2x as f32, cp2y as f32),
-                                                         r as f32));
-        self.ipc_renderer.send(msg).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::ArcTo(Point2D::new(cp1x as f32, cp1y as f32),
+                                                   Point2D::new(cp2x as f32, cp2y as f32),
+                                                   r as f32));
         Ok(())
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-ellipse
     fn Ellipse(&self, x: f64, y: f64, rx: f64, ry: f64, rotation: f64, start: f64, end: f64, ccw: bool) -> ErrorResult {
         if !([x, y, rx, ry, rotation, start, end].iter().all(|x| x.is_finite())) {
             return Ok(());
         }
         if rx < 0.0 || ry < 0.0 {
             return Err(Error::IndexSize);
         }
 
-        let msg = CanvasMsg::Canvas2d(Canvas2dMsg::Ellipse(Point2D::new(x as f32, y as f32),
-                                                       rx as f32,
-                                                       ry as f32,
-                                                       rotation as f32,
-                                                       start as f32,
-                                                       end as f32,
-                                                       ccw));
-        self.ipc_renderer.send(msg).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::Ellipse(Point2D::new(x as f32, y as f32),
+                                                     rx as f32,
+                                                     ry as f32,
+                                                     rotation as f32,
+                                                     start as f32,
+                                                     end as f32,
+                                                     ccw));
         Ok(())
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-imagesmoothingenabled
     fn ImageSmoothingEnabled(&self) -> bool {
         let state = self.state.borrow();
         state.image_smoothing_enabled
     }
@@ -996,35 +989,29 @@ impl CanvasRenderingContext2DMethods for
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle
     fn SetStrokeStyle(&self, value: StringOrCanvasGradientOrCanvasPattern) {
         match value {
             StringOrCanvasGradientOrCanvasPattern::String(string) => {
                 if let Ok(rgba) = self.parse_color(&string) {
                     self.state.borrow_mut().stroke_style = CanvasFillOrStrokeStyle::Color(rgba);
-                    self.ipc_renderer
-                        .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetStrokeStyle(
-                                    FillOrStrokeStyle::Color(rgba))))
-                        .unwrap();
+                    self.send_canvas_2d_msg(Canvas2dMsg::SetStrokeStyle(
+                        FillOrStrokeStyle::Color(rgba)));
                 }
             },
             StringOrCanvasGradientOrCanvasPattern::CanvasGradient(gradient) => {
                 self.state.borrow_mut().stroke_style =
                     CanvasFillOrStrokeStyle::Gradient(Dom::from_ref(&*gradient));
-                let msg = CanvasMsg::Canvas2d(
-                    Canvas2dMsg::SetStrokeStyle(gradient.to_fill_or_stroke_style()));
-                self.ipc_renderer.send(msg).unwrap();
+                self.send_canvas_2d_msg(Canvas2dMsg::SetStrokeStyle(gradient.to_fill_or_stroke_style()));
             },
             StringOrCanvasGradientOrCanvasPattern::CanvasPattern(pattern) => {
                 self.state.borrow_mut().stroke_style =
                     CanvasFillOrStrokeStyle::Pattern(Dom::from_ref(&*pattern));
-                let msg = CanvasMsg::Canvas2d(
-                    Canvas2dMsg::SetStrokeStyle(pattern.to_fill_or_stroke_style()));
-                self.ipc_renderer.send(msg).unwrap();
+                self.send_canvas_2d_msg(Canvas2dMsg::SetStrokeStyle(pattern.to_fill_or_stroke_style()));
                 if !pattern.origin_is_clean() {
                     self.set_origin_unclean();
                 }
             }
         }
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle
@@ -1045,35 +1032,29 @@ impl CanvasRenderingContext2DMethods for
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle
     fn SetFillStyle(&self, value: StringOrCanvasGradientOrCanvasPattern) {
         match value {
             StringOrCanvasGradientOrCanvasPattern::String(string) => {
                 if let Ok(rgba) = self.parse_color(&string) {
                     self.state.borrow_mut().fill_style = CanvasFillOrStrokeStyle::Color(rgba);
-                    self.ipc_renderer
-                        .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetFillStyle(
-                                    FillOrStrokeStyle::Color(rgba))))
-                        .unwrap()
+                    self.send_canvas_2d_msg(Canvas2dMsg::SetFillStyle(
+                        FillOrStrokeStyle::Color(rgba)))
                 }
             }
             StringOrCanvasGradientOrCanvasPattern::CanvasGradient(gradient) => {
                 self.state.borrow_mut().fill_style =
                     CanvasFillOrStrokeStyle::Gradient(Dom::from_ref(&*gradient));
-                let msg = CanvasMsg::Canvas2d(
-                    Canvas2dMsg::SetFillStyle(gradient.to_fill_or_stroke_style()));
-                self.ipc_renderer.send(msg).unwrap();
+                self.send_canvas_2d_msg(Canvas2dMsg::SetFillStyle(gradient.to_fill_or_stroke_style()));
             }
             StringOrCanvasGradientOrCanvasPattern::CanvasPattern(pattern) => {
                 self.state.borrow_mut().fill_style =
                     CanvasFillOrStrokeStyle::Pattern(Dom::from_ref(&*pattern));
-                let msg = CanvasMsg::Canvas2d(
-                    Canvas2dMsg::SetFillStyle(pattern.to_fill_or_stroke_style()));
-                self.ipc_renderer.send(msg).unwrap();
+                self.send_canvas_2d_msg(Canvas2dMsg::SetFillStyle(pattern.to_fill_or_stroke_style()));
                 if !pattern.origin_is_clean() {
                     self.set_origin_unclean();
                 }
             }
         }
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-createimagedata
@@ -1127,30 +1108,28 @@ 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(self.global().time_profiler_chan().clone()).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.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 = Vec::from(receiver.recv().unwrap());
+        self.send_canvas_2d_msg(Canvas2dMsg::GetImageData(dest_rect, canvas_size, sender));
+        let mut data = receiver.recv().unwrap();
 
         // Un-premultiply alpha
         for chunk in data.chunks_mut(4) {
             let alpha = chunk[3] as usize;
             chunk[0] = UNPREMULTIPLY_TABLE[256 * alpha + chunk[0] as usize];
             chunk[1] = UNPREMULTIPLY_TABLE[256 * alpha + chunk[1] as usize];
             chunk[2] = UNPREMULTIPLY_TABLE[256 * alpha + chunk[2] as usize];
         }
 
-        ImageData::new(&self.global(), sw, sh, Some(data))
+        ImageData::new(&self.global(), sw, sh, Some(data.to_vec()))
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata
     fn PutImageData(&self, imagedata: &ImageData, dx: Finite<f64>, dy: Finite<f64>) {
         self.PutImageData_(imagedata,
                            dx,
                            dy,
                            Finite::wrap(0f64),
@@ -1169,23 +1148,20 @@ impl CanvasRenderingContext2DMethods for
                      dirty_width: Finite<f64>,
                      dirty_height: Finite<f64>) {
         let data = imagedata.get_data_array();
         let offset = Vector2D::new(*dx, *dy);
         let image_data_size = Size2D::new(imagedata.Width() as f64, imagedata.Height() as f64);
 
         let dirty_rect = Rect::new(Point2D::new(*dirty_x, *dirty_y),
                                    Size2D::new(*dirty_width, *dirty_height));
-        let msg = CanvasMsg::Canvas2d(Canvas2dMsg::PutImageData(
-            data.into(),
-            offset,
-            image_data_size,
-            dirty_rect,
-        ));
-        self.ipc_renderer.send(msg).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::PutImageData(data.into(),
+                                                                offset,
+                                                                image_data_size,
+                                                                dirty_rect));
         self.mark_as_dirty();
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-createlineargradient
     fn CreateLinearGradient(&self,
                             x0: Finite<f64>,
                             y0: Finite<f64>,
                             x1: Finite<f64>,
@@ -1277,19 +1253,17 @@ impl CanvasRenderingContext2DMethods for
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth
     fn SetLineWidth(&self, width: f64) {
         if !width.is_finite() || width <= 0.0 {
             return;
         }
 
         self.state.borrow_mut().line_width = width;
-        self.ipc_renderer
-            .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetLineWidth(width as f32)))
-            .unwrap()
+        self.send_canvas_2d_msg(Canvas2dMsg::SetLineWidth(width as f32))
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap
     fn LineCap(&self) -> CanvasLineCap {
         match self.state.borrow().line_cap {
             LineCapStyle::Butt => CanvasLineCap::Butt,
             LineCapStyle::Round => CanvasLineCap::Round,
             LineCapStyle::Square => CanvasLineCap::Square,
@@ -1299,17 +1273,17 @@ impl CanvasRenderingContext2DMethods for
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap
     fn SetLineCap(&self, cap: CanvasLineCap) {
         let line_cap = match cap {
             CanvasLineCap::Butt => LineCapStyle::Butt,
             CanvasLineCap::Round => LineCapStyle::Round,
             CanvasLineCap::Square => LineCapStyle::Square,
         };
         self.state.borrow_mut().line_cap = line_cap;
-        self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::SetLineCap(line_cap))).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::SetLineCap(line_cap));
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin
     fn LineJoin(&self) -> CanvasLineJoin {
         match self.state.borrow().line_join {
             LineJoinStyle::Round => CanvasLineJoin::Round,
             LineJoinStyle::Bevel => CanvasLineJoin::Bevel,
             LineJoinStyle::Miter => CanvasLineJoin::Miter,
@@ -1319,100 +1293,96 @@ impl CanvasRenderingContext2DMethods for
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin
     fn SetLineJoin(&self, join: CanvasLineJoin) {
         let line_join = match join {
             CanvasLineJoin::Round => LineJoinStyle::Round,
             CanvasLineJoin::Bevel => LineJoinStyle::Bevel,
             CanvasLineJoin::Miter => LineJoinStyle::Miter,
         };
         self.state.borrow_mut().line_join = line_join;
-        self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::SetLineJoin(line_join))).unwrap();
+        self.send_canvas_2d_msg(Canvas2dMsg::SetLineJoin(line_join));
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit
     fn MiterLimit(&self) -> f64 {
         let state = self.state.borrow();
         state.miter_limit
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit
     fn SetMiterLimit(&self, limit: f64) {
         if !limit.is_finite() || limit <= 0.0 {
             return;
         }
 
         self.state.borrow_mut().miter_limit = limit;
-        self.ipc_renderer
-            .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetMiterLimit(limit as f32)))
-            .unwrap()
+        self.send_canvas_2d_msg(Canvas2dMsg::SetMiterLimit(limit as f32))
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsetx
     fn ShadowOffsetX(&self) -> f64 {
         self.state.borrow().shadow_offset_x
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsetx
     fn SetShadowOffsetX(&self, value: f64) {
         if !value.is_finite() || value == self.state.borrow().shadow_offset_x {
             return;
         }
         self.state.borrow_mut().shadow_offset_x = value;
-        self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::SetShadowOffsetX(value))).unwrap()
+        self.send_canvas_2d_msg(Canvas2dMsg::SetShadowOffsetX(value))
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety
     fn ShadowOffsetY(&self) -> f64 {
         self.state.borrow().shadow_offset_y
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety
     fn SetShadowOffsetY(&self, value: f64) {
         if !value.is_finite() || value == self.state.borrow().shadow_offset_y {
             return;
         }
         self.state.borrow_mut().shadow_offset_y = value;
-        self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::SetShadowOffsetY(value))).unwrap()
+        self.send_canvas_2d_msg(Canvas2dMsg::SetShadowOffsetY(value))
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur
     fn ShadowBlur(&self) -> f64 {
         self.state.borrow().shadow_blur
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur
     fn SetShadowBlur(&self, value: f64) {
         if !value.is_finite() || value < 0f64 || value == self.state.borrow().shadow_blur {
             return;
         }
         self.state.borrow_mut().shadow_blur = value;
-        self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::SetShadowBlur(value))).unwrap()
+        self.send_canvas_2d_msg(Canvas2dMsg::SetShadowBlur(value))
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor
     fn ShadowColor(&self) -> DOMString {
         let mut result = String::new();
         serialize(&self.state.borrow().shadow_color, &mut result).unwrap();
         DOMString::from(result)
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor
     fn SetShadowColor(&self, value: DOMString) {
         if let Ok(color) = parse_color(&value) {
             self.state.borrow_mut().shadow_color = color;
-            self.ipc_renderer
-                .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetShadowColor(color)))
-                .unwrap()
+            self.send_canvas_2d_msg(Canvas2dMsg::SetShadowColor(color))
         }
     }
 }
 
 impl Drop for CanvasRenderingContext2D {
     fn drop(&mut self) {
-        if let Err(err) = self.ipc_renderer.send(CanvasMsg::Close) {
+        if let Err(err) = self.ipc_renderer.send(CanvasMsg::Close(self.get_canvas_id())) {
             warn!("Could not close canvas: {}", err)
         }
     }
 }
 
 pub fn parse_color(string: &str) -> Result<RGBA, ()> {
     let mut input = ParserInput::new(string);
     let mut parser = Parser::new(&mut input);
--- a/servo/components/script/dom/htmlcanvaselement.rs
+++ b/servo/components/script/dom/htmlcanvaselement.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use base64;
-use canvas_traits::canvas::{CanvasMsg, FromScriptMsg};
+use canvas_traits::canvas::{CanvasMsg, CanvasId, FromScriptMsg};
 use canvas_traits::webgl::WebGLVersion;
 use dom::attr::Attr;
 use dom::bindings::cell::DomRefCell;
 use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods;
 use dom::bindings::codegen::Bindings::HTMLCanvasElementBinding;
 use dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::{HTMLCanvasElementMethods, RenderingContext};
 use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLContextAttributes;
 use dom::bindings::conversions::ConversionResult;
@@ -100,16 +100,17 @@ impl HTMLCanvasElement {
         }
     }
 }
 
 pub trait LayoutHTMLCanvasElementHelpers {
     fn data(&self) -> HTMLCanvasData;
     fn get_width(&self) -> LengthOrPercentageOrAuto;
     fn get_height(&self) -> LengthOrPercentageOrAuto;
+    fn get_canvas_id_for_layout(&self) -> CanvasId;
 }
 
 impl LayoutHTMLCanvasElementHelpers for LayoutDom<HTMLCanvasElement> {
     #[allow(unsafe_code)]
     fn data(&self) -> HTMLCanvasData {
         unsafe {
             let canvas = &*self.unsafe_get();
             let source = match canvas.context.borrow_for_layout().as_ref() {
@@ -128,16 +129,17 @@ impl LayoutHTMLCanvasElementHelpers for 
             };
 
             let width_attr = canvas.upcast::<Element>().get_attr_for_layout(&ns!(), &local_name!("width"));
             let height_attr = canvas.upcast::<Element>().get_attr_for_layout(&ns!(), &local_name!("height"));
             HTMLCanvasData {
                 source: source,
                 width: width_attr.map_or(DEFAULT_WIDTH, |val| val.as_uint()),
                 height: height_attr.map_or(DEFAULT_HEIGHT, |val| val.as_uint()),
+                canvas_id: self.get_canvas_id_for_layout(),
             }
         }
     }
 
     #[allow(unsafe_code)]
     fn get_width(&self) -> LengthOrPercentageOrAuto {
         unsafe {
             (&*self.upcast::<Element>().unsafe_get())
@@ -151,16 +153,28 @@ impl LayoutHTMLCanvasElementHelpers for 
     fn get_height(&self) -> LengthOrPercentageOrAuto {
         unsafe {
             (&*self.upcast::<Element>().unsafe_get())
                 .get_attr_for_layout(&ns!(), &local_name!("height"))
                 .map(AttrValue::as_uint_px_dimension)
                 .unwrap_or(LengthOrPercentageOrAuto::Auto)
         }
     }
+
+    #[allow(unsafe_code)]
+    fn get_canvas_id_for_layout(&self) -> CanvasId {
+        unsafe {
+            let canvas = &*self.unsafe_get();
+            if let &Some(CanvasContext::Context2d(ref context)) = canvas.context.borrow_for_layout() {
+                context.to_layout().get_canvas_id()
+            } else {
+                CanvasId(0)
+            }
+        }
+    }
 }
 
 
 impl HTMLCanvasElement {
     pub fn get_or_init_2d_context(&self) -> Option<DomRoot<CanvasRenderingContext2D>> {
         if self.context.borrow().is_none() {
             let window = window_from_node(self);
             let size = self.get_size();
@@ -256,17 +270,17 @@ impl HTMLCanvasElement {
 
         if size.width == 0 || size.height == 0 {
             return None
         }
 
         let data = match self.context.borrow().as_ref() {
             Some(&CanvasContext::Context2d(ref context)) => {
                 let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
-                let msg = CanvasMsg::FromScript(FromScriptMsg::SendPixels(sender));
+                let msg = CanvasMsg::FromScript(FromScriptMsg::SendPixels(sender), context.get_canvas_id());
                 context.get_ipc_renderer().send(msg).unwrap();
 
                 receiver.recv().unwrap()?.into()
             },
             Some(&CanvasContext::WebGL(_)) => {
                 // TODO: add a method in WebGLRenderingContext to get the pixels.
                 return None;
             },
--- a/servo/components/script/dom/paintrenderingcontext2d.rs
+++ b/servo/components/script/dom/paintrenderingcontext2d.rs
@@ -54,17 +54,17 @@ impl PaintRenderingContext2D {
 
     pub fn new(global: &PaintWorkletGlobalScope) -> DomRoot<PaintRenderingContext2D> {
         reflect_dom_object(Box::new(PaintRenderingContext2D::new_inherited(global)),
                            global,
                            PaintRenderingContext2DBinding::Wrap)
     }
 
     pub fn send_data(&self, sender: IpcSender<CanvasImageData>) {
-        let msg = CanvasMsg::FromLayout(FromLayoutMsg::SendData(sender));
+        let msg = CanvasMsg::FromLayout(FromLayoutMsg::SendData(sender), self.context.get_canvas_id());
         let _ = self.context.get_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,
--- a/servo/components/script_layout_interface/lib.rs
+++ b/servo/components/script_layout_interface/lib.rs
@@ -36,17 +36,17 @@ extern crate style;
 extern crate webrender_api;
 
 pub mod message;
 pub mod reporter;
 pub mod rpc;
 pub mod wrapper_traits;
 
 use atomic_refcell::AtomicRefCell;
-use canvas_traits::canvas::CanvasMsg;
+use canvas_traits::canvas::{CanvasMsg, CanvasId};
 use ipc_channel::ipc::IpcSender;
 use libc::c_void;
 use net_traits::image_cache::PendingImageId;
 use script_traits::UntrustedNodeAddress;
 use servo_url::ServoUrl;
 use std::ptr::NonNull;
 use std::sync::atomic::AtomicIsize;
 use style::data::ElementData;
@@ -128,16 +128,17 @@ pub enum HTMLCanvasDataSource {
     WebGL(webrender_api::ImageKey),
     Image(Option<IpcSender<CanvasMsg>>)
 }
 
 pub struct HTMLCanvasData {
     pub source: HTMLCanvasDataSource,
     pub width: u32,
     pub height: u32,
+    pub canvas_id: CanvasId,
 }
 
 pub struct SVGSVGData {
     pub width: u32,
     pub height: u32,
 }
 
 /// The address of a node known to be valid. These are sent from script to layout.
--- a/servo/components/script_traits/script_msg.rs
+++ b/servo/components/script_traits/script_msg.rs
@@ -6,17 +6,17 @@ use AnimationState;
 use CompositorEvent;
 use DocumentState;
 use IFrameLoadInfo;
 use IFrameLoadInfoWithData;
 use LayoutControlMsg;
 use LoadData;
 use WorkerGlobalScopeInit;
 use WorkerScriptLoadOrigin;
-use canvas_traits::canvas::CanvasMsg;
+use canvas_traits::canvas::{CanvasMsg, CanvasId};
 use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
 use euclid::{Size2D, TypedSize2D};
 use gfx_traits::Epoch;
 use ipc_channel::ipc::{IpcReceiver, IpcSender};
 use msg::constellation_msg::{BrowsingContextId, PipelineId, TraversalDirection};
 use msg::constellation_msg::{Key, KeyModifiers, KeyState};
 use net_traits::CoreResourceMsg;
 use net_traits::request::RequestInit;
@@ -74,17 +74,17 @@ pub enum ScriptMsg {
     InitiateNavigateRequest(RequestInit, /* cancellation_chan */ IpcReceiver<()>),
     /// Broadcast a storage event to every same-origin pipeline.
     /// The strings are key, old value and new value.
     BroadcastStorageEvent(StorageType, ServoUrl, Option<String>, Option<String>, Option<String>),
     /// Indicates whether this pipeline is currently running animations.
     ChangeRunningAnimationsState(AnimationState),
     /// Requests that a new 2D canvas thread be created. (This is done in the constellation because
     /// 2D canvases may use the GPU and we don't want to give untrusted content access to the GPU.)
-    CreateCanvasPaintThread(Size2D<i32>, IpcSender<IpcSender<CanvasMsg>>),
+    CreateCanvasPaintThread(Size2D<i32>, IpcSender<(IpcSender<CanvasMsg>, CanvasId)>),
     /// Notifies the constellation that this frame has received focus.
     Focus,
     /// Forward an event that was sent to the parent window.
     ForwardEvent(PipelineId, CompositorEvent),
     /// Requests that the constellation retrieve the current contents of the clipboard
     GetClipboardContents(IpcSender<String>),
     /// Get the browsing context id for a given pipeline.
     GetBrowsingContextId(PipelineId, IpcSender<Option<BrowsingContextId>>),
--- a/servo/tests/html/test_canvas_drawimage_canvas.html
+++ b/servo/tests/html/test_canvas_drawimage_canvas.html
@@ -1,9 +1,9 @@
-<!DOCTYPE html>
+<!DOCTYPE html>
 <html>
 <head>
   <meta charset="utf-8">
 </head>
 <style type=''>
 html {
   font-family: helvetica, sans-serif;
 }
@@ -125,17 +125,17 @@ imageSource = {
   height: 50
 };
 
 destCanvas = {
   width: 100,
   height: 50
 };
 
-drawImage(0, 0);
+    drawImage(0, 0);
 
 function renderExample(title) {
   var container = document.createElement('div');
   container.id = 'example' + examplesNum++;
   container.setAttribute('class', 'example');
 
   var h2 = document.createElement('h2');
   h2.textContent = title;
new file mode 100644
--- /dev/null
+++ b/servo/tests/html/test_canvas_drawimage_canvas_timed.html
@@ -0,0 +1,165 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+</head>
+<style type=''>
+html {
+  font-family: helvetica, sans-serif;
+}
+
+canvas {
+  padding: 15px;
+  margin: 10px;
+}
+
+.example {
+  display: inline-block;
+  width: 280px;
+  margin-top: 50px;
+  margin-right: 50px;
+}
+
+.title {
+  text-align: center;
+}
+
+.description {
+  width: 100%;
+}
+
+.description div {
+  display: inline-block;
+  text-align: center;
+  font-size: 13px;
+}
+
+.description .source {
+  width: 100px;
+}
+
+.description .target {
+  width: 100px;
+}
+
+.example-title {
+  text-align: center;
+  font-size: 16px;
+}
+
+.error {
+  color: red;
+}
+
+</style>
+<body>
+    <h1 class="title">Timed DrawImage canvas to canvas</h1>
+    <h4 class="title", id="Test Result">This text is where the timing result will go...</h4>
+</body>
+
+<script type="text/javascript">
+
+var smoothingEnabled = false;
+var examplesNum = 0;
+
+var imageSource = {
+  width: 50,
+  height: 50
+};
+
+var destCanvas = {
+  width: 100,
+  height: 100
+};
+
+    var t0 = performance.now();     //save starting time
+    drawImage(25, 25);            // The source canvas will copied to the 25,25 position of the destination canvas
+    var t1 = performance.now();     //save ending time
+    document.getElementById('Test Result').innerHTML = "DrawImage took " + (t1 - t0) + " milliseconds."; //print to browser
+    console.log("DrawImage took " + (t1 - t0) + " milliseconds.");  //print to console
+
+function renderExample(title) {
+  var container = document.createElement('div');
+  container.id = 'example' + examplesNum++;
+  container.setAttribute('class', 'example');
+
+  var h2 = document.createElement('h2');
+  h2.textContent = title;
+  h2.setAttribute('class', 'example-title');
+  container.appendChild(h2);
+
+  var div1 = document.createElement('div');
+  var canvas1 = document.createElement('canvas');
+  var canvas2 = document.createElement('canvas');
+  div1.appendChild(canvas1);
+  div1.appendChild(canvas2);
+  container.appendChild(div1);
+
+  var div2 = document.createElement('div');
+  div2.setAttribute('class', 'description');
+  var source = document.createElement('div');
+  source.textContent = ' Source (' + imageSource.width + ',' + imageSource.height + ')';
+  source.setAttribute('class', 'source');
+
+  var arrow = document.createElement('div');
+  arrow.textContent = ' -> ';
+  arrow.setAttribute('class', 'arrow');
+
+  var target = document.createElement('div');
+  target.textContent = 'Target (' + destCanvas.width + ',' + destCanvas.height + ')';
+  target.setAttribute('class', 'target');
+
+  div2.appendChild(source);
+  div2.appendChild(arrow);
+  div2.appendChild(target);
+  container.appendChild(div2);
+
+  return container;
+}
+
+function drawImage() {
+  var args = Array.prototype.slice.call(arguments);
+
+  var div = renderExample('drawImage(' + args.toString() + ')');
+  var canvasEls = div.querySelectorAll('canvas');
+  var canvas1 = canvasEls[0];
+  var canvas2 = canvasEls[1];
+
+  canvas1.width = imageSource.width;
+  canvas1.height = imageSource.height;
+
+  var ctx1 = canvas1.getContext('2d');
+  ctx1.fillStyle = "#00FFFF";
+  ctx1.fillRect(0, 0, imageSource.width, imageSource.height);
+
+  ctx1.fillStyle = "#000000";
+  ctx1.fillRect(5,5,40,40);
+  ctx1.clearRect(10,10,30,30);
+  ctx1.strokeRect(15,15,20,20);
+
+  canvas2.width = destCanvas.width;
+  canvas2.height = destCanvas.height;
+
+  var ctx2 = canvas2.getContext('2d');
+  ctx2.fillStyle = "#FF0000";
+  ctx2.fillRect(0, 0,  destCanvas.width, destCanvas.height);
+  ctx2.imageSmoothingEnabled = smoothingEnabled;
+
+  args.unshift(canvas1);
+  try {
+    ctx2.drawImage.apply(ctx2, args);
+  }
+  catch(err) {
+    var title = div.querySelector('.example-title');
+    var error = document.createElement('h2');
+    error.setAttribute('class', 'example-title error');
+    div.insertBefore(error, title);
+    error.textContent += "Call Failed: " + err.message;
+  }
+
+  document.body.appendChild(div);
+};
+
+</script>
+</body>
+</html>