servo: Merge #17810 - Script paint worklets speculative evaluation (from asajeffrey:script-paint-worklets-speculative-evaluation); r=emilio
authorAlan Jeffrey <ajeffrey@mozilla.com>
Mon, 31 Jul 2017 13:13:26 -0500
changeset 420746 2da66805c3d13861f55c84ddcb48b06da090a443
parent 420745 9a5b55eec89f8545a5a6d9c692311c6d13adb866
child 420747 7ac289e62574bc5c0758874c7f14d84edd5bd178
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)
reviewersemilio
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 #17810 - Script paint worklets speculative evaluation (from asajeffrey:script-paint-worklets-speculative-evaluation); r=emilio <!-- Please describe your changes on the following line: --> This PR speculatively calls paint worklets during style, which increases the concurrency, since it increases the chance that the cache will have the right result in it when it comes to layout. The speculation is wasted effort if the size of the element has changed, but this is often not the case. --- <!-- 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 #17486 and #17369. - [X] These changes do not require tests because it's a performance improvement <!-- 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: cf5602e84f17d14b6a9abb77f1de6460db070921
servo/Cargo.lock
servo/components/layout/context.rs
servo/components/layout/display_list_builder.rs
servo/components/layout_thread/Cargo.toml
servo/components/layout_thread/lib.rs
servo/components/script/dom/bindings/trace.rs
servo/components/script/dom/paintworkletglobalscope.rs
servo/components/script_layout_interface/message.rs
servo/components/script_traits/lib.rs
servo/components/style/context.rs
servo/components/style/traversal.rs
servo/components/style_traits/Cargo.toml
servo/components/style_traits/lib.rs
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -1515,16 +1515,17 @@ dependencies = [
  "selectors 0.19.0",
  "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_arc 0.0.1",
  "servo_atoms 0.0.1",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "style 0.0.1",
+ "style_traits 0.0.1",
  "webrender_api 0.48.0 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "layout_traits"
 version = "0.0.1"
 dependencies = [
  "gfx 0.0.1",
@@ -3082,16 +3083,17 @@ dependencies = [
  "app_units 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.19.0",
  "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "servo_atoms 0.0.1",
  "webrender_api 0.48.0 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "stylo_tests"
 version = "0.0.1"
 dependencies = [
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
--- a/servo/components/layout/context.rs
+++ b/servo/components/layout/context.rs
@@ -1,15 +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/. */
 
 //! Data needed by the layout thread.
 
-use fnv::FnvHashMap;
 use fnv::FnvHasher;
 use gfx::display_list::{WebRenderImageInfo, OpaqueNode};
 use gfx::font_cache_thread::FontCacheThread;
 use gfx::font_context::FontContext;
 use heapsize::HeapSizeOf;
 use msg::constellation_msg::PipelineId;
 use net_traits::image_cache::{CanRequestImages, ImageCache, ImageState};
 use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder};
@@ -20,18 +19,18 @@ use script_traits::Painter;
 use script_traits::UntrustedNodeAddress;
 use servo_atoms::Atom;
 use servo_url::ServoUrl;
 use std::cell::{RefCell, RefMut};
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
 use std::sync::{Arc, Mutex};
 use std::thread;
+use style::context::RegisteredSpeculativePainter;
 use style::context::SharedStyleContext;
-use style::properties::PropertyId;
 
 thread_local!(static FONT_CONTEXT_KEY: RefCell<Option<FontContext>> = RefCell::new(None));
 
 pub fn with_thread_local_font_context<F, R>(layout_context: &LayoutContext, f: F) -> R
     where F: FnOnce(&mut FontContext) -> R
 {
     FONT_CONTEXT_KEY.with(|k| {
         let mut font_context = k.borrow_mut();
@@ -68,17 +67,17 @@ pub struct LayoutContext<'a> {
     pub font_cache_thread: Mutex<FontCacheThread>,
 
     /// A cache of WebRender image info.
     pub webrender_image_cache: Arc<RwLock<HashMap<(ServoUrl, UsePlaceholder),
                                                   WebRenderImageInfo,
                                                   BuildHasherDefault<FnvHasher>>>>,
 
     /// Paint worklets
-    pub registered_painters: Arc<RwLock<FnvHashMap<Atom, RegisteredPainter>>>,
+    pub registered_painters: &'a RegisteredPainters,
 
     /// A list of in-progress image loads to be shared with the script thread.
     /// A None value means that this layout was not initiated by the script thread.
     pub pending_images: Option<Mutex<Vec<PendingImage>>>,
 
     /// A list of nodes that have just initiated a CSS transition.
     /// A None value means that this layout was not initiated by the script thread.
     pub newly_transitioning_nodes: Option<Mutex<Vec<UntrustedNodeAddress>>>,
@@ -174,14 +173,16 @@ impl<'a> LayoutContext<'a> {
                     Some(image_info)
                 }
             }
             None | Some(ImageOrMetadataAvailable::MetadataAvailable(_)) => None,
         }
     }
 }
 
-/// A registered paint worklet.
-pub struct RegisteredPainter {
-    pub name: Atom,
-    pub properties: FnvHashMap<Atom, PropertyId>,
-    pub painter: Arc<Painter>,
+/// A registered painter
+pub trait RegisteredPainter: RegisteredSpeculativePainter + Painter {}
+
+/// A set of registered painters
+pub trait RegisteredPainters: Sync {
+    /// Look up a painter
+    fn get(&self, name: &Atom) -> Option<&RegisteredPainter>;
 }
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -1176,34 +1176,28 @@ impl FragmentDisplayListBuilding for Fra
         let size_in_px = TypedSize2D::new(size_in_au.width.to_f32_px(), size_in_au.height.to_f32_px());
 
         // TODO: less copying.
         let name = paint_worklet.name.clone();
         let arguments = paint_worklet.arguments.iter()
             .map(|argument| argument.to_css_string())
             .collect();
 
-        // Get the painter, and the computed values for its properties.
-        // TODO: less copying.
-        let (properties, painter) = match state.layout_context.registered_painters.read().get(&name) {
-            Some(registered_painter) => (
-                registered_painter.properties
-                    .iter()
+        let mut draw_result = match state.layout_context.registered_painters.get(&name) {
+            Some(painter) => {
+                debug!("Drawing a paint image {}({},{}).", name, size_in_px.width, size_in_px.height);
+                let properties = painter.properties().iter()
                     .filter_map(|(name, id)| id.as_shorthand().err().map(|id| (name, id)))
                     .map(|(name, id)| (name.clone(), style.computed_value_to_string(id)))
-                    .collect(),
-                registered_painter.painter.clone()
-            ),
+                    .collect();
+                painter.draw_a_paint_image(size_in_px, device_pixel_ratio, properties, arguments)
+            },
             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 mut draw_result = painter.draw_a_paint_image(size_in_px, device_pixel_ratio, properties, arguments);
         let webrender_image = WebRenderImageInfo {
             width: draw_result.width,
             height: draw_result.height,
             format: draw_result.format,
             key: draw_result.image_key,
         };
 
         for url in draw_result.missing_image_urls.drain(..) {
--- a/servo/components/layout_thread/Cargo.toml
+++ b/servo/components/layout_thread/Cargo.toml
@@ -36,9 +36,10 @@ script_traits = {path = "../script_trait
 selectors = { path = "../selectors" }
 serde_json = "1.0"
 servo_arc = {path = "../servo_arc"}
 servo_atoms = {path = "../atoms"}
 servo_config = {path = "../config"}
 servo_geometry = {path = "../geometry"}
 servo_url = {path = "../url"}
 style = {path = "../style"}
+style_traits = {path = "../style_traits"}
 webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]}
--- a/servo/components/layout_thread/lib.rs
+++ b/servo/components/layout_thread/lib.rs
@@ -41,37 +41,39 @@ extern crate script_traits;
 extern crate selectors;
 extern crate serde_json;
 extern crate servo_arc;
 extern crate servo_atoms;
 extern crate servo_config;
 extern crate servo_geometry;
 extern crate servo_url;
 extern crate style;
+extern crate style_traits;
 extern crate webrender_api;
 
 mod dom_wrapper;
 
 use app_units::Au;
 use dom_wrapper::{ServoLayoutElement, ServoLayoutDocument, ServoLayoutNode};
 use dom_wrapper::drop_style_and_layout_data;
-use euclid::{Point2D, Rect, Size2D, ScaleFactor};
+use euclid::{Point2D, Rect, Size2D, ScaleFactor, TypedSize2D};
 use fnv::FnvHashMap;
 use gfx::display_list::{OpaqueNode, WebRenderImageInfo};
 use gfx::font;
 use gfx::font_cache_thread::FontCacheThread;
 use gfx::font_context;
 use gfx_traits::{Epoch, node_id_from_clip_id};
 use heapsize::HeapSizeOf;
 use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
 use ipc_channel::router::ROUTER;
 use layout::animation;
 use layout::construct::ConstructionResult;
 use layout::context::LayoutContext;
 use layout::context::RegisteredPainter;
+use layout::context::RegisteredPainters;
 use layout::context::heap_size_of_persistent_local_context;
 use layout::display_list_builder::ToGfxColor;
 use layout::flow::{self, Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils};
 use layout::flow_ref::FlowRef;
 use layout::incremental::{LayoutDamageComputation, REFLOW_ENTIRE_DOCUMENT, RelayoutMode};
 use layout::layout_debug;
 use layout::opaque_node::OpaqueNodeMethods;
 use layout::parallel;
@@ -94,16 +96,18 @@ use profile_traits::time::{self, TimerMe
 use profile_traits::time::{TimerMetadataFrameType, TimerMetadataReflowType};
 use script_layout_interface::message::{Msg, NewLayoutThreadInfo, Reflow, ReflowQueryType};
 use script_layout_interface::message::{ScriptReflow, ReflowComplete};
 use script_layout_interface::rpc::{LayoutRPC, MarginStyleResponse, NodeOverflowResponse, OffsetParentResponse};
 use script_layout_interface::rpc::TextIndexResponse;
 use script_layout_interface::wrapper_traits::LayoutNode;
 use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg};
 use script_traits::{ScrollState, UntrustedNodeAddress};
+use script_traits::DrawAPaintImageResult;
+use script_traits::Painter;
 use selectors::Element;
 use servo_arc::Arc as ServoArc;
 use servo_atoms::Atom;
 use servo_config::opts;
 use servo_config::prefs::PREFS;
 use servo_config::resource_files::read_resource_file;
 use servo_geometry::max_rect;
 use servo_url::ServoUrl;
@@ -117,31 +121,36 @@ use std::process;
 use std::slice;
 use std::sync::{Arc, Mutex, MutexGuard};
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::mpsc::{Receiver, Sender, channel};
 use std::thread;
 use style::animation::Animation;
 use style::context::{QuirksMode, ReflowGoal, SharedStyleContext};
 use style::context::{StyleSystemOptions, ThreadLocalStyleContextCreationInfo};
+use style::context::RegisteredSpeculativePainter;
+use style::context::RegisteredSpeculativePainters;
 use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TElement, TNode};
 use style::error_reporting::{NullReporter, RustLogReporter};
 use style::invalidation::element::restyle_hints::RestyleHint;
 use style::logical_geometry::LogicalPoint;
 use style::media_queries::{Device, MediaList, MediaType};
 use style::properties::PropertyId;
 use style::selector_parser::SnapshotMap;
 use style::servo::restyle_damage::{REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, REPOSITION, STORE_OVERFLOW};
 use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards};
 use style::stylesheets::{Origin, Stylesheet, StylesheetInDocument, UserAgentStylesheets};
 use style::stylist::{ExtraStyleData, Stylist};
 use style::thread_state;
 use style::timer::Timer;
 use style::traversal::{DomTraversal, TraversalDriver};
 use style::traversal_flags::TraversalFlags;
+use style_traits::CSSPixel;
+use style_traits::DevicePixel;
+use style_traits::SpeculativePainter;
 
 /// Information needed by the layout thread.
 pub struct LayoutThread {
     /// The ID of the pipeline that we belong to.
     id: PipelineId,
 
     /// The ID of the top-level browsing context that we belong to.
     top_level_browsing_context_id: TopLevelBrowsingContextId,
@@ -230,19 +239,19 @@ pub struct LayoutThread {
     /// A mutex to allow for fast, read-only RPC of layout's internal data
     /// structures, while still letting the LayoutThread modify them.
     ///
     /// All the other elements of this struct are read-only.
     rw_data: Arc<Mutex<LayoutThreadData>>,
 
     webrender_image_cache: Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder),
                                                  WebRenderImageInfo>>>,
-    /// The executor for paint worklets.
-    /// Will be None if the script thread hasn't added any paint worklet modules.
-    registered_painters: Arc<RwLock<FnvHashMap<Atom, RegisteredPainter>>>,
+
+    /// The executors for paint worklets.
+    registered_painters: RegisteredPaintersImpl,
 
     /// Webrender interface.
     webrender_api: webrender_api::RenderApi,
 
     /// Webrender document.
     webrender_document: webrender_api::DocumentId,
 
     /// The timer object to control the timing of the animations. This should
@@ -515,17 +524,17 @@ impl LayoutThread {
             url: url,
             is_iframe: is_iframe,
             port: port,
             pipeline_port: pipeline_receiver,
             script_chan: script_chan.clone(),
             constellation_chan: constellation_chan.clone(),
             time_profiler_chan: time_profiler_chan,
             mem_profiler_chan: mem_profiler_chan,
-            registered_painters: Arc::new(RwLock::new(FnvHashMap::default())),
+            registered_painters: RegisteredPaintersImpl(FnvHashMap::default()),
             image_cache: image_cache.clone(),
             font_cache_thread: font_cache_thread,
             first_reflow: Cell::new(true),
             font_cache_receiver: font_cache_receiver,
             font_cache_sender: ipc_font_cache_sender,
             parallel_traversal: parallel_traversal,
             parallel_flag: true,
             generation: Cell::new(0),
@@ -600,28 +609,29 @@ impl LayoutThread {
             id: self.id,
             style_context: SharedStyleContext {
                 stylist: &self.stylist,
                 options: StyleSystemOptions::default(),
                 guards: guards,
                 visited_styles_enabled: false,
                 running_animations: self.running_animations.clone(),
                 expired_animations: self.expired_animations.clone(),
+                registered_speculative_painters: &self.registered_painters,
                 local_context_creation_data: Mutex::new(thread_local_style_context_creation_data),
                 timer: self.timer.clone(),
                 quirks_mode: self.quirks_mode.unwrap(),
                 traversal_flags: TraversalFlags::empty(),
                 snapshot_map: snapshot_map,
             },
             image_cache: self.image_cache.clone(),
             font_cache_thread: Mutex::new(self.font_cache_thread.clone()),
             webrender_image_cache: self.webrender_image_cache.clone(),
             pending_images: if script_initiated_layout { Some(Mutex::new(vec![])) } else { None },
             newly_transitioning_nodes: if script_initiated_layout { Some(Mutex::new(vec![])) } else { None },
-            registered_painters: self.registered_painters.clone(),
+            registered_painters: &self.registered_painters,
         }
     }
 
     /// Receives and dispatches messages from the script and constellation threads
     fn handle_request<'a, 'b>(&mut self, possibly_locked_rw_data: &mut RwData<'a, 'b>) -> bool {
         enum Request {
             FromPipeline(LayoutControlMsg),
             FromScript(Msg),
@@ -733,23 +743,22 @@ impl LayoutThread {
                 self.url = final_url;
             },
             Msg::RegisterPaint(name, mut properties, painter) => {
                 debug!("Registering the painter");
                 let properties = properties.drain(..)
                     .filter_map(|name| PropertyId::parse(&*name).ok().map(|id| (name.clone(), id)))
                     .filter(|&(_, ref id)| id.as_shorthand().is_err())
                     .collect();
-                let registered_painter = RegisteredPainter {
+                let registered_painter = RegisteredPainterImpl {
                     name: name.clone(),
                     properties: properties,
                     painter: painter,
                 };
-                self.registered_painters.write()
-                    .insert(name, registered_painter);
+                self.registered_painters.0.insert(name, registered_painter);
             },
             Msg::PrepareToExit(response_chan) => {
                 self.prepare_to_exit(response_chan);
                 return false
             },
             Msg::ExitNow => {
                 debug!("layout: ExitNow received");
                 self.exit_now();
@@ -1785,8 +1794,57 @@ lazy_static! {
             Ok(stylesheets) => stylesheets,
             Err(filename) => {
                 error!("Failed to load UA stylesheet {}!", filename);
                 process::exit(1);
             }
         }
     };
 }
+
+struct RegisteredPainterImpl {
+    painter: Box<Painter>,
+    name: Atom,
+    properties: FnvHashMap<Atom, PropertyId>,
+}
+
+impl SpeculativePainter for RegisteredPainterImpl {
+    fn speculatively_draw_a_paint_image(&self, properties: Vec<(Atom, String)>, arguments: Vec<String>) {
+        self.painter.speculatively_draw_a_paint_image(properties, arguments);
+    }
+}
+
+impl RegisteredSpeculativePainter for RegisteredPainterImpl {
+    fn properties(&self) -> &FnvHashMap<Atom, PropertyId> {
+        &self.properties
+    }
+    fn name(&self) -> Atom {
+        self.name.clone()
+    }
+}
+
+impl Painter for RegisteredPainterImpl {
+    fn draw_a_paint_image(&self,
+                          size: TypedSize2D<f32, CSSPixel>,
+                          device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>,
+                          properties: Vec<(Atom, String)>,
+                          arguments: Vec<String>)
+                          -> DrawAPaintImageResult
+    {
+        self.painter.draw_a_paint_image(size, device_pixel_ratio, properties, arguments)
+    }
+}
+
+impl RegisteredPainter for RegisteredPainterImpl {}
+
+struct RegisteredPaintersImpl(FnvHashMap<Atom, RegisteredPainterImpl>);
+
+impl RegisteredSpeculativePainters for RegisteredPaintersImpl  {
+    fn get(&self, name: &Atom) -> Option<&RegisteredSpeculativePainter> {
+        self.0.get(&name).map(|painter| painter as &RegisteredSpeculativePainter)
+    }
+}
+
+impl RegisteredPainters for RegisteredPaintersImpl {
+    fn get(&self, name: &Atom) -> Option<&RegisteredPainter> {
+        self.0.get(&name).map(|painter| painter as &RegisteredPainter)
+    }
+}
--- a/servo/components/script/dom/bindings/trace.rs
+++ b/servo/components/script/dom/bindings/trace.rs
@@ -38,17 +38,17 @@ use dom::abstractworker::SharedRt;
 use dom::bindings::cell::DOMRefCell;
 use dom::bindings::js::{JS, Root};
 use dom::bindings::refcounted::{Trusted, TrustedPromise};
 use dom::bindings::reflector::{DomObject, Reflector};
 use dom::bindings::str::{DOMString, USVString};
 use dom::bindings::utils::WindowProxyHandler;
 use dom::document::PendingRestyle;
 use encoding::types::EncodingRef;
-use euclid::{Transform2D, Transform3D, Point2D, Vector2D, Rect, Size2D, ScaleFactor};
+use euclid::{Transform2D, Transform3D, Point2D, Vector2D, Rect, TypedSize2D, ScaleFactor};
 use euclid::Length as EuclidLength;
 use html5ever::{Prefix, LocalName, Namespace, QualName};
 use html5ever::buffer_queue::BufferQueue;
 use html5ever::tendril::IncompleteUtf8;
 use hyper::header::Headers;
 use hyper::method::Method;
 use hyper::mime::Mime;
 use hyper::status::StatusCode;
@@ -70,16 +70,17 @@ use offscreen_gl_context::GLLimits;
 use parking_lot::RwLock;
 use profile_traits::mem::ProfilerChan as MemProfilerChan;
 use profile_traits::time::ProfilerChan as TimeProfilerChan;
 use script_layout_interface::OpaqueStyleAndLayoutData;
 use script_layout_interface::reporter::CSSErrorReporter;
 use script_layout_interface::rpc::LayoutRPC;
 use script_traits::{DocumentActivity, TimerEventId, TimerSource, TouchpadPressurePhase};
 use script_traits::{UntrustedNodeAddress, WindowSizeData, WindowSizeType};
+use script_traits::DrawAPaintImageResult;
 use selectors::matching::ElementSelectorFlags;
 use serde::{Deserialize, Serialize};
 use servo_arc::Arc as ServoArc;
 use servo_atoms::Atom;
 use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
 use smallvec::SmallVec;
 use std::cell::{Cell, RefCell, UnsafeCell};
 use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
@@ -384,16 +385,17 @@ unsafe_no_jsmanaged_fields!(ResponseBody
 unsafe_no_jsmanaged_fields!(ResourceThreads);
 unsafe_no_jsmanaged_fields!(StatusCode);
 unsafe_no_jsmanaged_fields!(SystemTime);
 unsafe_no_jsmanaged_fields!(Instant);
 unsafe_no_jsmanaged_fields!(RelativePos);
 unsafe_no_jsmanaged_fields!(OpaqueStyleAndLayoutData);
 unsafe_no_jsmanaged_fields!(PathBuf);
 unsafe_no_jsmanaged_fields!(CSSErrorReporter);
+unsafe_no_jsmanaged_fields!(DrawAPaintImageResult);
 unsafe_no_jsmanaged_fields!(WebGLBufferId);
 unsafe_no_jsmanaged_fields!(WebGLFramebufferId);
 unsafe_no_jsmanaged_fields!(WebGLProgramId);
 unsafe_no_jsmanaged_fields!(WebGLRenderbufferId);
 unsafe_no_jsmanaged_fields!(WebGLShaderId);
 unsafe_no_jsmanaged_fields!(WebGLTextureId);
 unsafe_no_jsmanaged_fields!(WebGLVertexArrayId);
 unsafe_no_jsmanaged_fields!(MediaList);
@@ -514,17 +516,24 @@ unsafe impl JSTraceable for Rect<Au> {
 
 unsafe impl JSTraceable for Rect<f32> {
     #[inline]
     unsafe fn trace(&self, _trc: *mut JSTracer) {
         // Do nothing
     }
 }
 
-unsafe impl JSTraceable for Size2D<i32> {
+unsafe impl<U> JSTraceable for TypedSize2D<i32, U> {
+    #[inline]
+    unsafe fn trace(&self, _trc: *mut JSTracer) {
+        // Do nothing
+    }
+}
+
+unsafe impl<U> JSTraceable for TypedSize2D<f32, U> {
     #[inline]
     unsafe fn trace(&self, _trc: *mut JSTracer) {
         // Do nothing
     }
 }
 
 unsafe impl JSTraceable for Mutex<Option<SharedRt>> {
     unsafe fn trace(&self, _trc: *mut JSTracer) {
--- a/servo/components/script/dom/paintworkletglobalscope.rs
+++ b/servo/components/script/dom/paintworkletglobalscope.rs
@@ -58,103 +58,165 @@ 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;
+use style_traits::SpeculativePainter;
 
 /// 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
     #[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>>>>,
+    /// The most recent name the worklet was called with
+    cached_name: DOMRefCell<Atom>,
+    /// The most recent size the worklet was drawn at
+    cached_size: Cell<TypedSize2D<f32, CSSPixel>>,
+    /// The most recent device pixel ratio the worklet was drawn at
+    cached_device_pixel_ratio: Cell<ScaleFactor<f32, CSSPixel, DevicePixel>>,
+    /// The most recent properties the worklet was drawn at
+    cached_properties: DOMRefCell<Vec<(Atom, String)>>,
+    /// The most recent arguments the worklet was drawn at
+    cached_arguments: DOMRefCell<Vec<String>>,
+    /// The most recent result
+    cached_result: DOMRefCell<DrawAPaintImageResult>,
 }
 
 impl PaintWorkletGlobalScope {
     #[allow(unsafe_code)]
     pub fn new(runtime: &Runtime,
                pipeline_id: PipelineId,
                base_url: ServoUrl,
                executor: WorkletExecutor,
                init: &WorkletGlobalScopeInit)
                -> Root<PaintWorkletGlobalScope> {
         debug!("Creating paint worklet global scope for pipeline {}.", pipeline_id);
         let global = box PaintWorkletGlobalScope {
             worklet_global: WorkletGlobalScope::new_inherited(pipeline_id, base_url, executor, init),
             image_cache: init.image_cache.clone(),
             paint_definitions: Default::default(),
             paint_class_instances: Default::default(),
+            cached_name: DOMRefCell::new(Atom::from("")),
+            cached_size: Cell::new(TypedSize2D::zero()),
+            cached_device_pixel_ratio: Cell::new(ScaleFactor::new(1.0)),
+            cached_properties: Default::default(),
+            cached_arguments: Default::default(),
+            cached_result: DOMRefCell::new(DrawAPaintImageResult {
+                width: 0,
+                height: 0,
+                format: PixelFormat::BGRA8,
+                image_key: None,
+                missing_image_urls: Vec::new(),
+            }),
         };
         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_in_px, device_pixel_ratio, properties, arguments, sender) => {
-                let properties = StylePropertyMapReadOnly::from_iter(self.upcast(), properties);
-                let result = self.draw_a_paint_image(name, size_in_px, device_pixel_ratio, &*properties, arguments);
+            PaintWorkletTask::DrawAPaintImage(name, size, device_pixel_ratio, properties, arguments, sender) => {
+                let cache_hit = (&*self.cached_name.borrow() == &name) &&
+                    (self.cached_size.get() == size) &&
+                    (self.cached_device_pixel_ratio.get() == device_pixel_ratio) &&
+                    (&*self.cached_properties.borrow() == &properties) &&
+                    (&*self.cached_arguments.borrow() == &arguments);
+                let result = if cache_hit {
+                    debug!("Cache hit on paint worklet {}!", name);
+                    self.cached_result.borrow().clone()
+                } else {
+                    debug!("Cache miss on paint worklet {}!", name);
+                    let map = StylePropertyMapReadOnly::from_iter(self.upcast(), properties.iter().cloned());
+                    let result = self.draw_a_paint_image(&name, size, device_pixel_ratio, &*map, &*arguments);
+                    if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) {
+                        *self.cached_name.borrow_mut() = name;
+                        self.cached_size.set(size);
+                        self.cached_device_pixel_ratio.set(device_pixel_ratio);
+                        *self.cached_properties.borrow_mut() = properties;
+                        *self.cached_arguments.borrow_mut() = arguments;
+                        *self.cached_result.borrow_mut() = result.clone();
+                    }
+                    result
+                };
                 let _ = sender.send(result);
             }
+            PaintWorkletTask::SpeculativelyDrawAPaintImage(name, properties, arguments) => {
+                let should_speculate = (&*self.cached_name.borrow() != &name) ||
+                    (&*self.cached_properties.borrow() != &properties) ||
+                    (&*self.cached_arguments.borrow() != &arguments);
+                if should_speculate {
+                    let size = self.cached_size.get();
+                    let device_pixel_ratio = self.cached_device_pixel_ratio.get();
+                    let map = StylePropertyMapReadOnly::from_iter(self.upcast(), properties.iter().cloned());
+                    let result = self.draw_a_paint_image(&name, size, device_pixel_ratio, &*map, &*arguments);
+                    if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) {
+                        *self.cached_name.borrow_mut() = name;
+                        *self.cached_properties.borrow_mut() = properties;
+                        *self.cached_arguments.borrow_mut() = arguments;
+                        *self.cached_result.borrow_mut() = result;
+                    }
+                }
+            }
         }
     }
 
     /// https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image
     fn draw_a_paint_image(&self,
-                          name: Atom,
+                          name: &Atom,
                           size_in_px: TypedSize2D<f32, CSSPixel>,
                           device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>,
                           properties: &StylePropertyMapReadOnly,
-                          arguments: Vec<String>)
+                          arguments: &[String])
                           -> 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);
 
         // TODO: Steps 1-5.
 
         // TODO: document paint definitions.
         self.invoke_a_paint_callback(name, size_in_px, size_in_dpx, device_pixel_ratio, properties, arguments)
     }
 
     /// https://drafts.css-houdini.org/css-paint-api/#invoke-a-paint-callback
     #[allow(unsafe_code)]
     fn invoke_a_paint_callback(&self,
-                               name: Atom,
+                               name: &Atom,
                                size_in_px: TypedSize2D<f32, CSSPixel>,
                                size_in_dpx: TypedSize2D<u32, DevicePixel>,
                                device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>,
                                properties: &StylePropertyMapReadOnly,
-                               mut arguments: Vec<String>)
+                               arguments: &[String])
                                -> DrawAPaintImageResult
     {
         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());
 
         // 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) {
+        let rendering_context = match self.paint_definitions.borrow().get(name) {
             None => {
                 // Step 2.2.
                 warn!("Drawing un-registered paint definition {}.", name);
                 return self.invalid_image(size_in_dpx, vec![]);
             }
             Some(definition) => {
                 // Step 5.1
                 if !definition.constructor_valid_flag.get() {
@@ -179,17 +241,17 @@ impl PaintWorkletGlobalScope {
                 // Step 5.2-5.3
                 let args = HandleValueArray::new();
                 rooted!(in(cx) let mut result = null_mut());
                 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)
+                    self.paint_definitions.borrow_mut().get_mut(name)
                         .expect("Vanishing paint definition.")
                         .constructor_valid_flag.set(false);
                     return self.invalid_image(size_in_dpx, vec![]);
                 }
                 // Step 5.4
                 entry.insert(Box::new(Heap::default())).set(paint_instance.get());
             }
         };
@@ -201,17 +263,17 @@ impl PaintWorkletGlobalScope {
         rendering_context.set_bitmap_dimensions(size_in_px, device_pixel_ratio);
 
         // Step 9
         let paint_size = PaintSize::new(self, size_in_px);
 
         // TODO: Step 10
         // Steps 11-12
         debug!("Invoking paint function {}.", name);
-        rooted_vec!(let arguments_values <- arguments.drain(..)
+        rooted_vec!(let arguments_values <- arguments.iter().cloned()
                     .map(|argument| CSSStyleValue::new(self.upcast(), argument)));
         let arguments_value_vec: Vec<JSVal> = arguments_values.iter()
             .map(|argument| ObjectValue(argument.reflector().get_jsobject().get()))
             .collect();
         let arguments_value_array = unsafe { HandleValueArray::from_rooted_slice(&*arguments_value_vec) };
         rooted!(in(cx) let argument_object = unsafe { JS_NewArrayObject(cx, &arguments_value_array) });
 
         let args_slice = [
@@ -257,40 +319,56 @@ impl PaintWorkletGlobalScope {
             width: size.width as u32,
             height: size.height as u32,
             format: PixelFormat::BGRA8,
             image_key: None,
             missing_image_urls: missing_image_urls,
         }
     }
 
-    fn painter(&self, name: Atom) -> Arc<Painter> {
+    fn painter(&self, name: Atom) -> Box<Painter> {
         // Rather annoyingly we have to use a mutex here to make the painter Sync.
-        struct WorkletPainter(Atom, Mutex<WorkletExecutor>);
+        struct WorkletPainter {
+            name: Atom,
+            executor: Mutex<WorkletExecutor>,
+        }
+        impl SpeculativePainter for WorkletPainter {
+            fn speculatively_draw_a_paint_image(&self,
+                                                properties: Vec<(Atom, String)>,
+                                                arguments: Vec<String>) {
+                let name = self.name.clone();
+                let task = PaintWorkletTask::SpeculativelyDrawAPaintImage(name, properties, arguments);
+                self.executor.lock().expect("Locking a painter.")
+                    .schedule_a_worklet_task(WorkletTask::Paint(task));
+            }
+        }
         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)>,
                                   arguments: Vec<String>)
                                   -> DrawAPaintImageResult {
-                let name = self.0.clone();
+                let name = self.name.clone();
                 let (sender, receiver) = mpsc::channel();
                 let task = PaintWorkletTask::DrawAPaintImage(name,
                                                              size,
                                                              device_pixel_ratio,
                                                              properties,
                                                              arguments,
                                                              sender);
-                self.1.lock().expect("Locking a painter.")
+                self.executor.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())))
+        Box::new(WorkletPainter {
+            name: name,
+            executor: Mutex::new(self.worklet_global.executor()),
+        })
     }
 }
 
 impl PaintWorkletGlobalScopeMethods for PaintWorkletGlobalScope {
     #[allow(unsafe_code)]
     #[allow(unrooted_must_root)]
     /// https://drafts.css-houdini.org/css-paint-api/#dom-paintworkletglobalscope-registerpaint
     fn RegisterPaint(&self, name: DOMString, paint_ctor: Rc<VoidFunction>) -> Fallible<()> {
@@ -375,17 +453,20 @@ 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)>,
                     Vec<String>,
-                    Sender<DrawAPaintImageResult>)
+                    Sender<DrawAPaintImageResult>),
+    SpeculativelyDrawAPaintImage(Atom,
+                                 Vec<(Atom, String)>,
+                                 Vec<String>),
 }
 
 /// 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_layout_interface/message.rs
+++ b/servo/components/script_layout_interface/message.rs
@@ -84,17 +84,17 @@ pub enum Msg {
     /// Tells layout about the new scrolling offsets of each scrollable stacking context.
     SetScrollStates(Vec<ScrollState>),
 
     /// Tells layout about a single new scrolling offset from the script. The rest will
     /// remain untouched and layout won't forward this back to script.
     UpdateScrollStateFromScript(ScrollState),
 
     /// Tells layout that script has added some paint worklet modules.
-    RegisterPaint(Atom, Vec<Atom>, Arc<Painter>),
+    RegisterPaint(Atom, Vec<Atom>, Box<Painter>),
 
     /// Send to layout the precise time when the navigation started.
     SetNavigationStart(f64),
 }
 
 
 /// Any query to perform with this reflow.
 #[derive(Debug, PartialEq)]
--- a/servo/components/script_traits/lib.rs
+++ b/servo/components/script_traits/lib.rs
@@ -61,16 +61,17 @@ use serde::{Deserialize, Deserializer, S
 use servo_atoms::Atom;
 use servo_url::ImmutableOrigin;
 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::SpeculativePainter;
 use webdriver_msg::{LoadStatus, WebDriverScriptCommand};
 use webrender_api::{ClipId, DevicePixel, 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
@@ -812,29 +813,29 @@ pub enum PaintWorkletError {
 
 impl From<RecvTimeoutError> for PaintWorkletError {
     fn from(_: RecvTimeoutError) -> PaintWorkletError {
         PaintWorkletError::Timeout
     }
 }
 
 /// Execute paint code in the worklet thread pool.
-pub trait Painter: Sync + Send {
+pub trait Painter: SpeculativePainter {
     /// 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)>,
                           arguments: Vec<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)]
+#[derive(Debug, Deserialize, Serialize, Clone, HeapSizeOf)]
 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
--- a/servo/components/style/context.rs
+++ b/servo/components/style/context.rs
@@ -13,28 +13,31 @@ use data::{EagerPseudoStyles, ElementDat
 use dom::{OpaqueNode, TNode, TElement, SendElement};
 use euclid::ScaleFactor;
 use euclid::Size2D;
 use fnv::FnvHashMap;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")] use gecko_bindings::structs;
 #[cfg(feature = "servo")] use parking_lot::RwLock;
 use properties::ComputedValues;
+#[cfg(feature = "servo")] use properties::PropertyId;
 use rule_tree::StrongRuleNode;
 use selector_parser::{EAGER_PSEUDO_COUNT, SnapshotMap};
 use selectors::matching::ElementSelectorFlags;
 use servo_arc::Arc;
+#[cfg(feature = "servo")] use servo_atoms::Atom;
 use shared_lock::StylesheetGuards;
 use sharing::StyleSharingCandidateCache;
 use std::fmt;
 use std::ops;
 #[cfg(feature = "servo")] use std::sync::Mutex;
 #[cfg(feature = "servo")] use std::sync::mpsc::Sender;
 use style_traits::CSSPixel;
 use style_traits::DevicePixel;
+#[cfg(feature = "servo")] use style_traits::SpeculativePainter;
 use stylist::Stylist;
 use thread_state;
 use time;
 use timer::Timer;
 use traversal::DomTraversal;
 use traversal_flags::TraversalFlags;
 
 pub use selectors::matching::QuirksMode;
@@ -146,16 +149,20 @@ pub struct SharedStyleContext<'a> {
     /// The animations that are currently running.
     #[cfg(feature = "servo")]
     pub running_animations: Arc<RwLock<FnvHashMap<OpaqueNode, Vec<Animation>>>>,
 
     /// The list of animations that have expired since the last style recalculation.
     #[cfg(feature = "servo")]
     pub expired_animations: Arc<RwLock<FnvHashMap<OpaqueNode, Vec<Animation>>>>,
 
+    /// Paint worklets
+    #[cfg(feature = "servo")]
+    pub registered_speculative_painters: &'a RegisteredSpeculativePainters,
+
     /// Data needed to create the thread-local style context from the shared one.
     #[cfg(feature = "servo")]
     pub local_context_creation_data: Mutex<ThreadLocalStyleContextCreationInfo>,
 
 }
 
 impl<'a> SharedStyleContext<'a> {
     /// Return a suitable viewport size in order to be used for viewport units.
@@ -672,8 +679,24 @@ pub struct StyleContext<'a, E: TElement 
 /// Why we're doing reflow.
 #[derive(PartialEq, Copy, Clone, Debug)]
 pub enum ReflowGoal {
     /// We're reflowing in order to send a display list to the screen.
     ForDisplay,
     /// We're reflowing in order to satisfy a script query. No display list will be created.
     ForScriptQuery,
 }
+
+/// A registered painter
+#[cfg(feature = "servo")]
+pub trait RegisteredSpeculativePainter: SpeculativePainter {
+    /// The name it was registered with
+    fn name(&self) -> Atom;
+    /// The properties it was registered with
+    fn properties(&self) -> &FnvHashMap<Atom, PropertyId>;
+}
+
+/// A set of registered painters
+#[cfg(feature = "servo")]
+pub trait RegisteredSpeculativePainters: Sync {
+    /// Look up a speculative painter
+    fn get(&self, name: &Atom) -> Option<&RegisteredSpeculativePainter>;
+}
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -7,18 +7,21 @@
 use context::{ElementCascadeInputs, StyleContext, SharedStyleContext};
 use data::{ElementData, ElementStyles};
 use dom::{NodeInfo, OpaqueNode, TElement, TNode};
 use invalidation::element::restyle_hints::{RECASCADE_SELF, RECASCADE_DESCENDANTS, RestyleHint};
 use matching::{ChildCascadeRequirement, MatchMethods};
 use sharing::StyleSharingTarget;
 use smallvec::SmallVec;
 use style_resolver::StyleResolverForElement;
+#[cfg(feature = "servo")] use style_traits::ToCss;
 use stylist::RuleInclusion;
 use traversal_flags::{TraversalFlags, self};
+#[cfg(feature = "servo")] use values::Either;
+#[cfg(feature = "servo")] use values::generics::image::Image;
 
 /// A per-traversal-level chunk of data. This is sent down by the traversal, and
 /// currently only holds the dom depth for the bloom filter.
 ///
 /// NB: Keep this as small as possible, please!
 #[derive(Clone, Debug)]
 pub struct PerLevelTraversalData {
     /// The current dom depth.
@@ -513,16 +516,21 @@ where
 
         // If we're restyling this element to display:none, throw away all style
         // data in the subtree, notify the caller to early-return.
         if data.styles.is_display_none() {
             debug!("{:?} style is display:none - clearing data from descendants.",
                    element);
             clear_descendant_data(element)
         }
+
+        // Inform any paint worklets of changed style, to speculatively
+        // evaluate the worklet code. In the case that the size hasn't changed,
+        // this will result in increased concurrency between script and layout.
+        notify_paint_worklet(context, data);
     }
 
     // Now that matching and cascading is done, clear the bits corresponding to
     // those operations and compute the propagated restyle hint.
     let mut propagated_hint = {
         debug_assert!(context.shared.traversal_flags.for_animation_only() ||
                       !data.restyle.hint.has_animation_hint(),
                       "animation restyle hint should be handled during \
@@ -710,16 +718,56 @@ where
     element.finish_restyle(
         context,
         data,
         new_styles,
         important_rules_changed
     )
 }
 
+#[cfg(feature = "servo")]
+fn notify_paint_worklet<E>(context: &StyleContext<E>, data: &ElementData)
+where
+    E: TElement,
+{
+    // We speculatively evaluate any paint worklets during styling.
+    // This allows us to run paint worklets in parallel with style and layout.
+    // Note that this is wasted effort if the size of the node has
+    // changed, but in may cases it won't have.
+    if let Some(ref values) = data.styles.primary {
+        for image in &values.get_background().background_image.0 {
+            let (name, arguments) = match *image {
+                Either::Second(Image::PaintWorklet(ref worklet)) => (&worklet.name, &worklet.arguments),
+                _ => continue,
+            };
+            let painter = match context.shared.registered_speculative_painters.get(name) {
+                Some(painter) => painter,
+                None => continue,
+            };
+            let properties = painter.properties().iter()
+                .filter_map(|(name, id)| id.as_shorthand().err().map(|id| (name, id)))
+                .map(|(name, id)| (name.clone(), values.computed_value_to_string(id)))
+                .collect();
+            let arguments = arguments.iter()
+                .map(|argument| argument.to_css_string())
+                .collect();
+            debug!("Notifying paint worklet {}.", painter.name());
+            painter.speculatively_draw_a_paint_image(properties, arguments);
+        }
+    }
+}
+
+#[cfg(feature = "gecko")]
+fn notify_paint_worklet<E>(_context: &StyleContext<E>, _data: &ElementData)
+where
+    E: TElement,
+{
+    // The CSS paint API is Servo-only at the moment
+}
+
 fn note_children<E, D, F>(
     context: &mut StyleContext<E>,
     element: E,
     data: &ElementData,
     propagated_hint: RestyleHint,
     reconstructed_ancestor: bool,
     mut note_child: F,
 )
--- a/servo/components/style_traits/Cargo.toml
+++ b/servo/components/style_traits/Cargo.toml
@@ -5,21 +5,22 @@ authors = ["The Servo Project Developers
 license = "MPL-2.0"
 publish = false
 
 [lib]
 name = "style_traits"
 path = "lib.rs"
 
 [features]
-servo = ["heapsize", "heapsize_derive", "serde", "cssparser/heapsize", "cssparser/serde", "webrender_api"]
+servo = ["heapsize", "heapsize_derive", "serde", "servo_atoms", "cssparser/heapsize", "cssparser/serde", "webrender_api"]
 gecko = []
 
 [dependencies]
 app_units = "0.5"
 bitflags = "0.7"
 cssparser = "0.18"
 euclid = "0.15"
 heapsize = {version = "0.4", optional = true}
 heapsize_derive = {version = "0.1", optional = true}
 selectors = { path = "../selectors" }
 serde = {version = "1.0", optional = true}
 webrender_api = {git = "https://github.com/servo/webrender", optional = true}
+servo_atoms = {path = "../atoms", optional = true}
--- a/servo/components/style_traits/lib.rs
+++ b/servo/components/style_traits/lib.rs
@@ -17,21 +17,23 @@ extern crate app_units;
 #[macro_use] extern crate bitflags;
 #[macro_use] extern crate cssparser;
 extern crate euclid;
 #[cfg(feature = "servo")] extern crate heapsize;
 #[cfg(feature = "servo")] #[macro_use] extern crate heapsize_derive;
 extern crate selectors;
 #[cfg(feature = "servo")] #[macro_use] extern crate serde;
 #[cfg(feature = "servo")] extern crate webrender_api;
+#[cfg(feature = "servo")] extern crate servo_atoms;
 
 #[cfg(feature = "servo")] pub use webrender_api::DevicePixel;
 
 use cssparser::{CowRcStr, Token};
 use selectors::parser::SelectorParseError;
+#[cfg(feature = "servo")] use servo_atoms::Atom;
 
 /// One hardware pixel.
 ///
 /// This unit corresponds to the smallest addressable element of the display hardware.
 #[cfg(not(feature = "servo"))]
 #[derive(Copy, Clone, Debug)]
 pub enum DevicePixel {}
 
@@ -179,8 +181,14 @@ impl ParsingMode {
     }
 
     /// Whether the parsing mode allows all numeric values.
     pub fn allows_all_numeric_values(&self) -> bool {
         self.intersects(PARSING_MODE_ALLOW_ALL_NUMERIC_VALUES)
     }
 }
 
+#[cfg(feature = "servo")]
+/// Speculatively execute paint code in the worklet thread pool.
+pub trait SpeculativePainter: Send + Sync {
+    /// https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image
+    fn speculatively_draw_a_paint_image(&self, properties: Vec<(Atom, String)>, arguments: Vec<String>);
+}