From d101f9c9453d3ed33a95a2f74f76af3aba9329cc Mon Sep 17 00:00:00 2001 From: Alan Jeffrey Date: Tue, 27 Jun 2017 10:34:34 -0500 Subject: [PATCH] Implemented paint worklet arguments. --- components/canvas/canvas_paint_thread.rs | 7 +++ components/layout/display_list_builder.rs | 9 +++- .../script/dom/paintworkletglobalscope.rs | 52 ++++++++++++++----- components/script_traits/lib.rs | 3 +- components/style/values/generics/image.rs | 12 ++++- components/style/values/specified/image.rs | 14 +++-- .../css-paint-api/paint-arguments.html.ini | 4 -- .../paint-function-arguments.html.ini | 4 -- 8 files changed, 78 insertions(+), 27 deletions(-) delete mode 100644 tests/wpt/mozilla/meta/mozilla/css-paint-api/paint-arguments.html.ini delete mode 100644 tests/wpt/mozilla/meta/mozilla/css-paint-api/paint-function-arguments.html.ini diff --git a/components/canvas/canvas_paint_thread.rs b/components/canvas/canvas_paint_thread.rs index 9df8ab5962dc..b3433e62b506 100644 --- a/components/canvas/canvas_paint_thread.rs +++ b/components/canvas/canvas_paint_thread.rs @@ -548,10 +548,15 @@ impl<'a> CanvasPaintThread<'a> { } fn recreate(&mut self, size: Size2D) { + // TODO: clear the thread state. https://github.com/servo/servo/issues/17533 self.drawtarget = CanvasPaintThread::create(size); self.state = CanvasPaintState::new(self.state.draw_options.antialias); self.saved_states.clear(); // Webrender doesn't let images change size, so we clear the webrender image key. + // TODO: there is an annying race condition here: the display list builder + // might still be using the old image key. Really, we should be scheduling the image + // for later deletion, not deleting it immediately. + // https://github.com/servo/servo/issues/17534 if let Some(image_key) = self.image_key.take() { // If this executes, then we are in a new epoch since we last recreated the canvas, // so `old_image_key` must be `None`. @@ -582,6 +587,7 @@ impl<'a> CanvasPaintThread<'a> { match self.image_key { Some(image_key) => { + debug!("Updating image {:?}.", image_key); self.webrender_api.update_image(image_key, descriptor, data, @@ -589,6 +595,7 @@ impl<'a> CanvasPaintThread<'a> { } None => { self.image_key = Some(self.webrender_api.generate_image_key()); + debug!("New image {:?}.", self.image_key); self.webrender_api.add_image(self.image_key.unwrap(), descriptor, data, diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index 55dc56a06f0c..d99b485bd327 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -68,6 +68,7 @@ use style::values::generics::image::{Image, ShapeExtent}; use style::values::generics::image::PaintWorklet; use style::values::specified::position::{X, Y}; use style_traits::CSSPixel; +use style_traits::ToCss; use style_traits::cursor::Cursor; use table_cell::CollapsedBordersForCell; use webrender_api::{ClipId, ColorF, ComplexClipRegion, GradientStop, LocalClip, RepeatMode}; @@ -1173,9 +1174,15 @@ impl FragmentDisplayListBuilding for Fragment { let device_pixel_ratio = state.layout_context.style_context.device_pixel_ratio(); let size_in_au = unbordered_box.size.to_physical(style.writing_mode); let size_in_px = TypedSize2D::new(size_in_au.width.to_f32_px(), size_in_au.height.to_f32_px()); + + // 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 @@ -1191,7 +1198,7 @@ impl FragmentDisplayListBuilding for Fragment { // 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); + 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/script/dom/paintworkletglobalscope.rs b/components/script/dom/paintworkletglobalscope.rs index 2b66452e9f29..a3557d87d612 100644 --- a/components/script/dom/paintworkletglobalscope.rs +++ b/components/script/dom/paintworkletglobalscope.rs @@ -17,6 +17,7 @@ use dom::bindings::js::JS; use dom::bindings::js::Root; use dom::bindings::reflector::DomObject; use dom::bindings::str::DOMString; +use dom::cssstylevalue::CSSStyleValue; use dom::paintrenderingcontext2d::PaintRenderingContext2D; use dom::paintsize::PaintSize; use dom::stylepropertymapreadonly::StylePropertyMapReadOnly; @@ -38,6 +39,7 @@ use js::jsapi::IsConstructor; use js::jsapi::JSAutoCompartment; use js::jsapi::JS_ClearPendingException; use js::jsapi::JS_IsExceptionPending; +use js::jsapi::JS_NewArrayObject; use js::jsval::JSVal; use js::jsval::ObjectValue; use js::jsval::UndefinedValue; @@ -100,9 +102,9 @@ impl PaintWorkletGlobalScope { pub fn perform_a_worklet_task(&self, task: PaintWorkletTask) { match task { - PaintWorkletTask::DrawAPaintImage(name, size_in_px, device_pixel_ratio, properties, sender) => { + 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); + let result = self.draw_a_paint_image(name, size_in_px, device_pixel_ratio, &*properties, arguments); let _ = sender.send(result); } } @@ -113,11 +115,17 @@ impl PaintWorkletGlobalScope { name: Atom, size_in_px: TypedSize2D, device_pixel_ratio: ScaleFactor, - properties: &StylePropertyMapReadOnly) + properties: &StylePropertyMapReadOnly, + arguments: Vec) -> 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, device_pixel_ratio, properties) + 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 @@ -125,12 +133,12 @@ impl PaintWorkletGlobalScope { fn invoke_a_paint_callback(&self, name: Atom, size_in_px: TypedSize2D, + size_in_dpx: TypedSize2D, device_pixel_ratio: ScaleFactor, - properties: &StylePropertyMapReadOnly) + properties: &StylePropertyMapReadOnly, + mut arguments: Vec) -> DrawAPaintImageResult { - let size_in_dpx = size_in_px * device_pixel_ratio; - let size_in_dpx = TypedSize2D::new(size_in_dpx.width.abs() as u32, size_in_dpx.height.abs() as u32); debug!("Invoking a paint callback {}({},{}) at {}.", name, size_in_px.width, size_in_px.height, device_pixel_ratio); @@ -198,10 +206,19 @@ impl PaintWorkletGlobalScope { // TODO: Step 10 // Steps 11-12 debug!("Invoking paint function {}.", name); + rooted_vec!(let arguments_values <- arguments.drain(..) + .map(|argument| CSSStyleValue::new(self.upcast(), argument))); + let arguments_value_vec: Vec = 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 = [ ObjectValue(rendering_context.reflector().get_jsobject().get()), ObjectValue(paint_size.reflector().get_jsobject().get()), ObjectValue(properties.reflector().get_jsobject().get()), + ObjectValue(argument_object.get()), ]; let args = unsafe { HandleValueArray::from_rooted_slice(&args_slice) }; @@ -252,12 +269,17 @@ impl PaintWorkletGlobalScope { fn draw_a_paint_image(&self, size: TypedSize2D, device_pixel_ratio: ScaleFactor, - properties: Vec<(Atom, String)>) - -> DrawAPaintImageResult - { + properties: Vec<(Atom, String)>, + arguments: Vec) + -> DrawAPaintImageResult { let name = self.0.clone(); let (sender, receiver) = mpsc::channel(); - let task = PaintWorkletTask::DrawAPaintImage(name, size, device_pixel_ratio, properties, sender); + let task = PaintWorkletTask::DrawAPaintImage(name, + size, + device_pixel_ratio, + properties, + arguments, + sender); self.1.lock().expect("Locking a painter.") .schedule_a_worklet_task(WorkletTask::Paint(task)); receiver.recv().expect("Worklet thread died?") @@ -296,7 +318,7 @@ impl PaintWorkletGlobalScopeMethods for PaintWorkletGlobalScope { let properties = property_names.drain(..).map(Atom::from).collect(); // Step 7-9. - let _argument_names: Vec = + let input_arguments: Vec = unsafe { get_property(cx, paint_obj.handle(), "inputArguments", ()) }? .unwrap_or_default(); @@ -332,6 +354,7 @@ impl PaintWorkletGlobalScopeMethods for PaintWorkletGlobalScope { let definition = PaintDefinition::new(paint_val.handle(), paint_function.handle(), alpha, + input_arguments.len(), &*context); // Step 20. @@ -356,6 +379,7 @@ pub enum PaintWorkletTask { TypedSize2D, ScaleFactor, Vec<(Atom, String)>, + Vec, Sender) } @@ -370,6 +394,8 @@ struct PaintDefinition { paint_function: Heap, constructor_valid_flag: Cell, context_alpha_flag: bool, + // TODO: this should be a list of CSS syntaxes. + input_arguments_len: usize, // TODO: the spec calls for fresh rendering contexts each time a paint image is drawn, // but to avoid having the primary worklet thread create a new renering context, // we recycle them. @@ -380,6 +406,7 @@ impl PaintDefinition { fn new(class_constructor: HandleValue, paint_function: HandleValue, alpha: bool, + input_arguments_len: usize, context: &PaintRenderingContext2D) -> Box { @@ -388,6 +415,7 @@ impl PaintDefinition { paint_function: Heap::default(), constructor_valid_flag: Cell::new(true), context_alpha_flag: alpha, + input_arguments_len: input_arguments_len, context: JS::from_ref(context), }); result.class_constructor.set(class_constructor.get()); diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 948e6fe39352..829d14d3b4ba 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -822,7 +822,8 @@ pub trait Painter: Sync + Send { fn draw_a_paint_image(&self, size: TypedSize2D, zoom: ScaleFactor, - properties: Vec<(Atom, String)>) + properties: Vec<(Atom, String)>, + arguments: Vec) -> DrawAPaintImageResult; } diff --git a/components/style/values/generics/image.rs b/components/style/values/generics/image.rs index 874912aeaf8e..5da5a68563cc 100644 --- a/components/style/values/generics/image.rs +++ b/components/style/values/generics/image.rs @@ -8,6 +8,7 @@ use Atom; use cssparser::serialize_identifier; +use custom_properties::SpecifiedValue; use std::fmt; use style_traits::{HasViewportPercentage, ToCss}; use values::computed::ComputedValueAsSpecified; @@ -136,17 +137,26 @@ pub struct ColorStop { /// Specified values for a paint worklet. /// https://drafts.css-houdini.org/css-paint-api/ -#[derive(Clone, Debug, PartialEq, ToComputedValue)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct PaintWorklet { /// The name the worklet was registered with. pub name: Atom, + /// The arguments for the worklet. + /// TODO: store a parsed representation of the arguments. + pub arguments: Vec, } +impl ComputedValueAsSpecified for PaintWorklet {} + impl ToCss for PaintWorklet { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { dest.write_str("paint(")?; serialize_identifier(&*self.name.to_string(), dest)?; + for argument in &self.arguments { + dest.write_str(", ")?; + argument.to_css(dest)?; + } dest.write_str(")") } } diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index 840f4ed1cf38..37ed00ccf443 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -9,6 +9,7 @@ use Atom; use cssparser::{Parser, Token, BasicParseError}; +use custom_properties::SpecifiedValue; use parser::{Parse, ParserContext}; use selectors::parser::SelectorParseError; #[cfg(feature = "servo")] @@ -874,12 +875,17 @@ impl Parse for ColorStop { } impl Parse for PaintWorklet { - fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { + fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { input.expect_function_matching("paint")?; - input.parse_nested_block(|i| { - let name = i.expect_ident()?; + input.parse_nested_block(|input| { + let name = Atom::from(&**input.expect_ident()?); + let arguments = input.try(|input| { + input.expect_comma()?; + input.parse_comma_separated(|input| Ok(*SpecifiedValue::parse(context, input)?)) + }).unwrap_or(vec![]); Ok(PaintWorklet { - name: Atom::from(name.as_ref()), + name: name, + arguments: arguments, }) }) } diff --git a/tests/wpt/mozilla/meta/mozilla/css-paint-api/paint-arguments.html.ini b/tests/wpt/mozilla/meta/mozilla/css-paint-api/paint-arguments.html.ini deleted file mode 100644 index 64a88871ab35..000000000000 --- a/tests/wpt/mozilla/meta/mozilla/css-paint-api/paint-arguments.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[paint-arguments.html] - type: reftest - expected: FAIL - bug: https://github.com/servo/servo/issues/17435 diff --git a/tests/wpt/mozilla/meta/mozilla/css-paint-api/paint-function-arguments.html.ini b/tests/wpt/mozilla/meta/mozilla/css-paint-api/paint-function-arguments.html.ini deleted file mode 100644 index 80fbfd4780cb..000000000000 --- a/tests/wpt/mozilla/meta/mozilla/css-paint-api/paint-function-arguments.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[paint-function-arguments.html] - type: reftest - expected: FAIL - bug: https://github.com/servo/servo/issues/17435