diff --git a/Cargo.lock b/Cargo.lock index 57a4a104ec2..50dd6e1a785 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1520,6 +1520,7 @@ dependencies = [ "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)", ] @@ -3087,6 +3088,7 @@ dependencies = [ "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)", ] diff --git a/components/layout/context.rs b/components/layout/context.rs index cda3811421a..76d32eebca9 100644 --- a/components/layout/context.rs +++ b/components/layout/context.rs @@ -4,7 +4,6 @@ //! Data needed by the layout thread. -use fnv::FnvHashMap; use fnv::FnvHasher; use gfx::display_list::{WebRenderImageInfo, OpaqueNode}; use gfx::font_cache_thread::FontCacheThread; @@ -25,8 +24,8 @@ 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> = RefCell::new(None)); @@ -73,7 +72,7 @@ pub struct LayoutContext<'a> { BuildHasherDefault>>>, /// Paint worklets - pub registered_painters: Arc>>, + 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. @@ -179,9 +178,11 @@ impl<'a> LayoutContext<'a> { } } -/// A registered paint worklet. -pub struct RegisteredPainter { - pub name: Atom, - pub properties: FnvHashMap, - pub painter: Arc, +/// 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>; } diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index d99b485bd32..f8b26899b57 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -1181,24 +1181,18 @@ impl FragmentDisplayListBuilding for Fragment { .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, diff --git a/components/layout_thread/Cargo.toml b/components/layout_thread/Cargo.toml index de3fd19e224..5513521c743 100644 --- a/components/layout_thread/Cargo.toml +++ b/components/layout_thread/Cargo.toml @@ -41,4 +41,5 @@ 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"]} diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 3e59ac55742..ecfa17ff1d2 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -46,6 +46,7 @@ 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; @@ -53,7 +54,7 @@ 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; @@ -67,6 +68,7 @@ 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}; @@ -99,6 +101,8 @@ 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; @@ -122,6 +126,8 @@ 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; @@ -137,6 +143,9 @@ 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 { @@ -235,9 +244,9 @@ pub struct LayoutThread { webrender_image_cache: Arc>>, - /// The executor for paint worklets. - /// Will be None if the script thread hasn't added any paint worklet modules. - registered_painters: Arc>>, + + /// The executors for paint worklets. + registered_painters: RegisteredPaintersImpl, /// Webrender interface. webrender_api: webrender_api::RenderApi, @@ -520,7 +529,7 @@ impl LayoutThread { 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), @@ -605,6 +614,7 @@ impl LayoutThread { 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(), @@ -616,7 +626,7 @@ impl LayoutThread { 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, } } @@ -738,13 +748,12 @@ impl LayoutThread { .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); @@ -1790,3 +1799,52 @@ lazy_static! { } }; } + +struct RegisteredPainterImpl { + painter: Box, + name: Atom, + properties: FnvHashMap, +} + +impl SpeculativePainter for RegisteredPainterImpl { + fn speculatively_draw_a_paint_image(&self, properties: Vec<(Atom, String)>, arguments: Vec) { + self.painter.speculatively_draw_a_paint_image(properties, arguments); + } +} + +impl RegisteredSpeculativePainter for RegisteredPainterImpl { + fn properties(&self) -> &FnvHashMap { + &self.properties + } + fn name(&self) -> Atom { + self.name.clone() + } +} + +impl Painter for RegisteredPainterImpl { + fn draw_a_paint_image(&self, + size: TypedSize2D, + device_pixel_ratio: ScaleFactor, + properties: Vec<(Atom, String)>, + arguments: Vec) + -> DrawAPaintImageResult + { + self.painter.draw_a_paint_image(size, device_pixel_ratio, properties, arguments) + } +} + +impl RegisteredPainter for RegisteredPainterImpl {} + +struct RegisteredPaintersImpl(FnvHashMap); + +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) + } +} diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 6ddeb49acf7..e16cd4dc571 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -43,7 +43,7 @@ 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; @@ -75,6 +75,7 @@ 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; @@ -389,6 +390,7 @@ 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); @@ -519,7 +521,14 @@ unsafe impl JSTraceable for Rect { } } -unsafe impl JSTraceable for Size2D { +unsafe impl JSTraceable for TypedSize2D { + #[inline] + unsafe fn trace(&self, _trc: *mut JSTracer) { + // Do nothing + } +} + +unsafe impl JSTraceable for TypedSize2D { #[inline] unsafe fn trace(&self, _trc: *mut JSTracer) { // Do nothing diff --git a/components/script/dom/paintworkletglobalscope.rs b/components/script/dom/paintworkletglobalscope.rs index a3557d87d61..b0a43cbf25d 100644 --- a/components/script/dom/paintworkletglobalscope.rs +++ b/components/script/dom/paintworkletglobalscope.rs @@ -63,6 +63,7 @@ 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] @@ -76,6 +77,18 @@ pub struct PaintWorkletGlobalScope { paint_definitions: DOMRefCell>>, /// https://drafts.css-houdini.org/css-paint-api/#paint-class-instances paint_class_instances: DOMRefCell>>>, + /// The most recent name the worklet was called with + cached_name: DOMRefCell, + /// The most recent size the worklet was drawn at + cached_size: Cell>, + /// The most recent device pixel ratio the worklet was drawn at + cached_device_pixel_ratio: Cell>, + /// The most recent properties the worklet was drawn at + cached_properties: DOMRefCell>, + /// The most recent arguments the worklet was drawn at + cached_arguments: DOMRefCell>, + /// The most recent result + cached_result: DOMRefCell, } impl PaintWorkletGlobalScope { @@ -92,6 +105,18 @@ impl PaintWorkletGlobalScope { 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) } } @@ -102,21 +127,58 @@ impl PaintWorkletGlobalScope { 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, device_pixel_ratio: ScaleFactor, properties: &StylePropertyMapReadOnly, - arguments: Vec) + arguments: &[String]) -> DrawAPaintImageResult { let size_in_dpx = size_in_px * device_pixel_ratio; @@ -131,12 +193,12 @@ impl PaintWorkletGlobalScope { /// 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, size_in_dpx: TypedSize2D, device_pixel_ratio: ScaleFactor, properties: &StylePropertyMapReadOnly, - mut arguments: Vec) + arguments: &[String]) -> DrawAPaintImageResult { debug!("Invoking a paint callback {}({},{}) at {}.", @@ -149,7 +211,7 @@ impl PaintWorkletGlobalScope { // 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); @@ -184,7 +246,7 @@ impl PaintWorkletGlobalScope { 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![]); @@ -206,7 +268,7 @@ impl PaintWorkletGlobalScope { // 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 = arguments_values.iter() .map(|argument| ObjectValue(argument.reflector().get_jsobject().get())) @@ -262,9 +324,22 @@ impl PaintWorkletGlobalScope { } } - fn painter(&self, name: Atom) -> Arc { + fn painter(&self, name: Atom) -> Box { // Rather annoyingly we have to use a mutex here to make the painter Sync. - struct WorkletPainter(Atom, Mutex); + struct WorkletPainter { + name: Atom, + executor: Mutex, + } + impl SpeculativePainter for WorkletPainter { + fn speculatively_draw_a_paint_image(&self, + properties: Vec<(Atom, String)>, + arguments: Vec) { + 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, @@ -272,7 +347,7 @@ impl PaintWorkletGlobalScope { properties: Vec<(Atom, String)>, arguments: Vec) -> DrawAPaintImageResult { - let name = self.0.clone(); + let name = self.name.clone(); let (sender, receiver) = mpsc::channel(); let task = PaintWorkletTask::DrawAPaintImage(name, size, @@ -280,12 +355,15 @@ impl PaintWorkletGlobalScope { 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()), + }) } } @@ -380,7 +458,10 @@ pub enum PaintWorkletTask { ScaleFactor, Vec<(Atom, String)>, Vec, - Sender) + Sender), + SpeculativelyDrawAPaintImage(Atom, + Vec<(Atom, String)>, + Vec), } /// A paint definition diff --git a/components/script_layout_interface/message.rs b/components/script_layout_interface/message.rs index 9c8a3fbbd47..e74447ade26 100644 --- a/components/script_layout_interface/message.rs +++ b/components/script_layout_interface/message.rs @@ -89,7 +89,7 @@ pub enum Msg { UpdateScrollStateFromScript(ScrollState), /// Tells layout that script has added some paint worklet modules. - RegisterPaint(Atom, Vec, Arc), + RegisterPaint(Atom, Vec, Box), /// Send to layout the precise time when the navigation started. SetNavigationStart(f64), diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 829d14d3b4b..a9023e3c2a3 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -66,6 +66,7 @@ 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}; @@ -817,7 +818,7 @@ impl From for PaintWorkletError { } /// 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, @@ -829,7 +830,7 @@ pub trait Painter: Sync + Send { /// 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, diff --git a/components/style/context.rs b/components/style/context.rs index 3a17f5d5b58..bd7e640e048 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -18,10 +18,12 @@ 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; @@ -30,6 +32,7 @@ use std::ops; #[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; @@ -151,6 +154,10 @@ pub struct SharedStyleContext<'a> { #[cfg(feature = "servo")] pub expired_animations: Arc>>>, + /// 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, @@ -677,3 +684,19 @@ pub enum ReflowGoal { /// 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; +} + +/// A set of registered painters +#[cfg(feature = "servo")] +pub trait RegisteredSpeculativePainters: Sync { + /// Look up a speculative painter + fn get(&self, name: &Atom) -> Option<&RegisteredSpeculativePainter>; +} diff --git a/components/style/traversal.rs b/components/style/traversal.rs index 7fc5cf22609..4640f7c9e0f 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -12,8 +12,11 @@ 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. @@ -518,6 +521,11 @@ where 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 @@ -715,6 +723,46 @@ where ) } +#[cfg(feature = "servo")] +fn notify_paint_worklet(context: &StyleContext, 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(_context: &StyleContext, _data: &ElementData) +where + E: TElement, +{ + // The CSS paint API is Servo-only at the moment +} + fn note_children( context: &mut StyleContext, element: E, diff --git a/components/style_traits/Cargo.toml b/components/style_traits/Cargo.toml index 1a4f5ad858d..a52f0662c0a 100644 --- a/components/style_traits/Cargo.toml +++ b/components/style_traits/Cargo.toml @@ -10,7 +10,7 @@ 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] @@ -23,3 +23,4 @@ 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} diff --git a/components/style_traits/lib.rs b/components/style_traits/lib.rs index 96809f925f8..33c3d15658c 100644 --- a/components/style_traits/lib.rs +++ b/components/style_traits/lib.rs @@ -22,11 +22,13 @@ extern crate euclid; 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. /// @@ -184,3 +186,9 @@ impl ParsingMode { } } +#[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); +}