diff --git a/Cargo.lock b/Cargo.lock index 50bfbccb10e0..faa3d1d7d300 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2484,6 +2484,7 @@ dependencies = [ name = "script_traits" version = "0.0.1" dependencies = [ + "app_units 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "bluetooth_traits 0.0.1", "canvas_traits 0.0.1", "cookie 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2503,6 +2504,7 @@ dependencies = [ "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)", + "servo_atoms 0.0.1", "servo_url 0.0.1", "style_traits 0.0.1", "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/components/layout/context.rs b/components/layout/context.rs index 3a4fac939cd2..22b407d1fb63 100644 --- a/components/layout/context.rs +++ b/components/layout/context.rs @@ -15,6 +15,7 @@ use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder}; use opaque_node::OpaqueNodeMethods; use parking_lot::RwLock; use script_layout_interface::{PendingImage, PendingImageState}; +use script_traits::PaintWorkletExecutor; use script_traits::UntrustedNodeAddress; use servo_url::ServoUrl; use std::borrow::{Borrow, BorrowMut}; @@ -95,6 +96,9 @@ pub struct LayoutContext<'a> { WebRenderImageInfo, BuildHasherDefault>>>, + /// The executor for worklets + pub paint_worklet_executor: Option>, + /// 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>>, diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index c4a20d1ee11b..6794d144a478 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -64,6 +64,7 @@ use style::values::generics::background::BackgroundSize; use style::values::generics::image::{Circle, Ellipse, EndingShape as GenericEndingShape}; use style::values::generics::image::{GradientItem as GenericGradientItem, GradientKind}; 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::cursor::Cursor; @@ -395,6 +396,28 @@ pub trait FragmentDisplayListBuilding { image_url: &ServoUrl, background_index: usize); + /// Adds the display items necessary to paint a webrender image of this fragment to the + /// appropriate section of the display list. + fn build_display_list_for_webrender_image(&self, + state: &mut DisplayListBuildState, + style: &ServoComputedValues, + display_list_section: DisplayListSection, + absolute_bounds: &Rect, + clip: &ClippingRegion, + webrender_image: WebRenderImageInfo, + index: usize); + + /// Adds the display items necessary to paint the background image created by this fragment's + /// worklet to the appropriate section of the display list. + fn build_display_list_for_background_paint_worklet(&self, + state: &mut DisplayListBuildState, + style: &ServoComputedValues, + display_list_section: DisplayListSection, + absolute_bounds: &Rect, + clip: &ClippingRegion, + paint_worklet: &PaintWorklet, + index: usize); + fn convert_linear_gradient(&self, bounds: &Rect, stops: &[GradientItem], @@ -893,6 +916,15 @@ impl FragmentDisplayListBuilding for Fragment { i); } } + Either::Second(Image::PaintWorklet(ref paint_worklet)) => { + self.build_display_list_for_background_paint_worklet(state, + style, + display_list_section, + &bounds, + &clip, + paint_worklet, + i); + } Either::Second(Image::Rect(_)) => { // TODO: Implement `-moz-image-rect` } @@ -956,144 +988,204 @@ impl FragmentDisplayListBuilding for Fragment { clip: &ClippingRegion, image_url: &ServoUrl, index: usize) { - let background = style.get_background(); let webrender_image = state.layout_context .get_webrender_image_for_url(self.node, image_url.clone(), UsePlaceholder::No); if let Some(webrender_image) = webrender_image { - debug!("(building display list) building background image"); - - // Use `background-size` to get the size. - let mut bounds = *absolute_bounds; - let image_size = self.compute_background_image_size(style, &bounds, - &webrender_image, index); - - // Clip. - // - // TODO: Check the bounds to see if a clip item is actually required. - let mut clip = clip.clone(); - clip.intersect_rect(&bounds); - - // Background image should be positioned on the padding box basis. - let border = style.logical_border_width().to_physical(style.writing_mode); - - // Use 'background-origin' to get the origin value. - let origin = get_cyclic(&background.background_origin.0, index); - let (mut origin_x, mut origin_y) = match *origin { - background_origin::single_value::T::padding_box => { - (Au(0), Au(0)) - } - background_origin::single_value::T::border_box => { - (-border.left, -border.top) - } - background_origin::single_value::T::content_box => { - let border_padding = self.border_padding.to_physical(self.style.writing_mode); - (border_padding.left - border.left, border_padding.top - border.top) - } - }; + self.build_display_list_for_webrender_image(state, + style, + display_list_section, + absolute_bounds, + clip, + webrender_image, + index); + } + } - // Use `background-attachment` to get the initial virtual origin - let attachment = get_cyclic(&background.background_attachment.0, index); - let (virtual_origin_x, virtual_origin_y) = match *attachment { - background_attachment::single_value::T::scroll => { - (absolute_bounds.origin.x, absolute_bounds.origin.y) - } - background_attachment::single_value::T::fixed => { - // If the ‘background-attachment’ value for this image is ‘fixed’, then - // 'background-origin' has no effect. - origin_x = Au(0); - origin_y = Au(0); - (Au(0), Au(0)) - } - }; + fn build_display_list_for_webrender_image(&self, + state: &mut DisplayListBuildState, + style: &ServoComputedValues, + display_list_section: DisplayListSection, + absolute_bounds: &Rect, + clip: &ClippingRegion, + webrender_image: WebRenderImageInfo, + index: usize) { + debug!("(building display list) building background image"); + let background = style.get_background(); - let horiz_position = *get_cyclic(&background.background_position_x.0, index); - let vert_position = *get_cyclic(&background.background_position_y.0, index); - // Use `background-position` to get the offset. - let horizontal_position = horiz_position.to_used_value(bounds.size.width - image_size.width); - let vertical_position = vert_position.to_used_value(bounds.size.height - image_size.height); - - // The anchor position for this background, based on both the background-attachment - // and background-position properties. - let anchor_origin_x = border.left + virtual_origin_x + origin_x + horizontal_position; - let anchor_origin_y = border.top + virtual_origin_y + origin_y + vertical_position; - - let mut tile_spacing = Size2D::zero(); - let mut stretch_size = image_size; - - // Adjust origin and size based on background-repeat - let background_repeat = get_cyclic(&background.background_repeat.0, index); - match background_repeat.0 { - background_repeat::single_value::RepeatKeyword::NoRepeat => { - bounds.origin.x = anchor_origin_x; - bounds.size.width = image_size.width; - } - background_repeat::single_value::RepeatKeyword::Repeat => { - ImageFragmentInfo::tile_image(&mut bounds.origin.x, - &mut bounds.size.width, - anchor_origin_x, - image_size.width); - } - background_repeat::single_value::RepeatKeyword::Space => { - ImageFragmentInfo::tile_image_spaced(&mut bounds.origin.x, - &mut bounds.size.width, - &mut tile_spacing.width, - anchor_origin_x, - image_size.width); + // Use `background-size` to get the size. + let mut bounds = *absolute_bounds; + let image_size = self.compute_background_image_size(style, &bounds, + &webrender_image, index); - } - background_repeat::single_value::RepeatKeyword::Round => { - ImageFragmentInfo::tile_image_round(&mut bounds.origin.x, - &mut bounds.size.width, - anchor_origin_x, - &mut stretch_size.width); - } - }; - match background_repeat.1 { - background_repeat::single_value::RepeatKeyword::NoRepeat => { - bounds.origin.y = anchor_origin_y; - bounds.size.height = image_size.height; - } - background_repeat::single_value::RepeatKeyword::Repeat => { - ImageFragmentInfo::tile_image(&mut bounds.origin.y, - &mut bounds.size.height, - anchor_origin_y, - image_size.height); - } - background_repeat::single_value::RepeatKeyword::Space => { - ImageFragmentInfo::tile_image_spaced(&mut bounds.origin.y, - &mut bounds.size.height, - &mut tile_spacing.height, - anchor_origin_y, - image_size.height); + // Clip. + // + // TODO: Check the bounds to see if a clip item is actually required. + let mut clip = clip.clone(); + clip.intersect_rect(&bounds); - } - background_repeat::single_value::RepeatKeyword::Round => { - ImageFragmentInfo::tile_image_round(&mut bounds.origin.y, - &mut bounds.size.height, - anchor_origin_y, - &mut stretch_size.height); - } - }; + // Background image should be positioned on the padding box basis. + let border = style.logical_border_width().to_physical(style.writing_mode); - // Create the image display item. - let base = state.create_base_display_item(&bounds, - &clip, - self.node, - style.get_cursor(Cursor::Default), - display_list_section); - state.add_display_item(DisplayItem::Image(box ImageDisplayItem { - base: base, - webrender_image: webrender_image, - image_data: None, - stretch_size: stretch_size, - tile_spacing: tile_spacing, - image_rendering: style.get_inheritedbox().image_rendering.clone(), - })); + // Use 'background-origin' to get the origin value. + let origin = get_cyclic(&background.background_origin.0, index); + let (mut origin_x, mut origin_y) = match *origin { + background_origin::single_value::T::padding_box => { + (Au(0), Au(0)) + } + background_origin::single_value::T::border_box => { + (-border.left, -border.top) + } + background_origin::single_value::T::content_box => { + let border_padding = self.border_padding.to_physical(self.style.writing_mode); + (border_padding.left - border.left, border_padding.top - border.top) + } + }; + + // Use `background-attachment` to get the initial virtual origin + let attachment = get_cyclic(&background.background_attachment.0, index); + let (virtual_origin_x, virtual_origin_y) = match *attachment { + background_attachment::single_value::T::scroll => { + (absolute_bounds.origin.x, absolute_bounds.origin.y) + } + background_attachment::single_value::T::fixed => { + // If the ‘background-attachment’ value for this image is ‘fixed’, then + // 'background-origin' has no effect. + origin_x = Au(0); + origin_y = Au(0); + (Au(0), Au(0)) + } + }; + + let horiz_position = *get_cyclic(&background.background_position_x.0, index); + let vert_position = *get_cyclic(&background.background_position_y.0, index); + // Use `background-position` to get the offset. + let horizontal_position = horiz_position.to_used_value(bounds.size.width - image_size.width); + let vertical_position = vert_position.to_used_value(bounds.size.height - image_size.height); + + // The anchor position for this background, based on both the background-attachment + // and background-position properties. + let anchor_origin_x = border.left + virtual_origin_x + origin_x + horizontal_position; + let anchor_origin_y = border.top + virtual_origin_y + origin_y + vertical_position; + + let mut tile_spacing = Size2D::zero(); + let mut stretch_size = image_size; + + // Adjust origin and size based on background-repeat + let background_repeat = get_cyclic(&background.background_repeat.0, index); + match background_repeat.0 { + background_repeat::single_value::RepeatKeyword::NoRepeat => { + bounds.origin.x = anchor_origin_x; + bounds.size.width = image_size.width; + } + background_repeat::single_value::RepeatKeyword::Repeat => { + ImageFragmentInfo::tile_image(&mut bounds.origin.x, + &mut bounds.size.width, + anchor_origin_x, + image_size.width); + } + background_repeat::single_value::RepeatKeyword::Space => { + ImageFragmentInfo::tile_image_spaced(&mut bounds.origin.x, + &mut bounds.size.width, + &mut tile_spacing.width, + anchor_origin_x, + image_size.width); + + } + background_repeat::single_value::RepeatKeyword::Round => { + ImageFragmentInfo::tile_image_round(&mut bounds.origin.x, + &mut bounds.size.width, + anchor_origin_x, + &mut stretch_size.width); + } + }; + match background_repeat.1 { + background_repeat::single_value::RepeatKeyword::NoRepeat => { + bounds.origin.y = anchor_origin_y; + bounds.size.height = image_size.height; + } + background_repeat::single_value::RepeatKeyword::Repeat => { + ImageFragmentInfo::tile_image(&mut bounds.origin.y, + &mut bounds.size.height, + anchor_origin_y, + image_size.height); + } + background_repeat::single_value::RepeatKeyword::Space => { + ImageFragmentInfo::tile_image_spaced(&mut bounds.origin.y, + &mut bounds.size.height, + &mut tile_spacing.height, + anchor_origin_y, + image_size.height); + + } + background_repeat::single_value::RepeatKeyword::Round => { + ImageFragmentInfo::tile_image_round(&mut bounds.origin.y, + &mut bounds.size.height, + anchor_origin_y, + &mut stretch_size.height); + } + }; + + // Create the image display item. + let base = state.create_base_display_item(&bounds, + &clip, + self.node, + style.get_cursor(Cursor::Default), + display_list_section); + + debug!("(building display list) adding background image."); + state.add_display_item(DisplayItem::Image(box ImageDisplayItem { + base: base, + webrender_image: webrender_image, + image_data: None, + stretch_size: stretch_size, + tile_spacing: tile_spacing, + image_rendering: style.get_inheritedbox().image_rendering.clone(), + })); - } + } + + fn build_display_list_for_background_paint_worklet(&self, + state: &mut DisplayListBuildState, + style: &ServoComputedValues, + display_list_section: DisplayListSection, + absolute_bounds: &Rect, + clip: &ClippingRegion, + paint_worklet: &PaintWorklet, + index: usize) + { + // TODO: check that this is the servo equivalent of "concrete object size". + // https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image + // https://drafts.csswg.org/css-images-3/#concrete-object-size + let size = self.content_box().size.to_physical(style.writing_mode); + let name = paint_worklet.name.clone(); + + // If the script thread has not added any paint worklet modules, there is nothing to do! + let executor = match state.layout_context.paint_worklet_executor { + Some(ref executor) => executor, + None => return debug!("Worklet {} called before any paint modules are added.", name), + }; + + // TODO: add a one-place cache to avoid drawing the paint image every time. + debug!("Drawing a paint image {}({},{}).", name, size.width.to_px(), size.height.to_px()); + let mut image = match executor.draw_a_paint_image(name, size) { + Ok(image) => image, + Err(err) => return warn!("Error running paint worklet ({:?}).", err), + }; + + // Make sure the image has a webrender key. + state.layout_context.image_cache.set_webrender_image_key(&mut image); + + debug!("Drew a paint image ({},{}).", image.width, image.height); + self.build_display_list_for_webrender_image(state, + style, + display_list_section, + absolute_bounds, + clip, + WebRenderImageInfo::from_image(&image), + index); } fn convert_linear_gradient(&self, @@ -1402,6 +1494,9 @@ impl FragmentDisplayListBuilding for Fragment { } } } + Either::Second(Image::PaintWorklet(..)) => { + // TODO: Handle border-image with `paint()`. + } Either::Second(Image::Rect(..)) => { // TODO: Handle border-image with `-moz-image-rect`. } diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 9ddff21070e9..4e91da113a60 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -90,6 +90,7 @@ 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::PaintWorkletExecutor; use selectors::Element; use servo_config::opts; use servo_config::prefs::PREFS; @@ -225,6 +226,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. + paint_worklet_executor: Option>, /// Webrender interface. webrender_api: webrender_traits::RenderApi, @@ -477,6 +481,7 @@ impl LayoutThread { constellation_chan: constellation_chan.clone(), time_profiler_chan: time_profiler_chan, mem_profiler_chan: mem_profiler_chan, + paint_worklet_executor: None, image_cache: image_cache.clone(), font_cache_thread: font_cache_thread, first_reflow: Cell::new(true), @@ -574,6 +579,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 }, + paint_worklet_executor: self.paint_worklet_executor.clone(), } } @@ -689,6 +695,11 @@ impl LayoutThread { Msg::SetFinalUrl(final_url) => { self.url = final_url; }, + Msg::SetPaintWorkletExecutor(executor) => { + debug!("Setting the paint worklet executor"); + debug_assert!(self.paint_worklet_executor.is_none()); + self.paint_worklet_executor = Some(executor); + }, Msg::PrepareToExit(response_chan) => { self.prepare_to_exit(response_chan); return false diff --git a/components/net/image_cache.rs b/components/net/image_cache.rs index b8467e5a461d..d0c31eb23b5c 100644 --- a/components/net/image_cache.rs +++ b/components/net/image_cache.rs @@ -57,9 +57,18 @@ fn get_placeholder_image(webrender_api: &webrender_traits::RenderApi, path: &Pat let mut image_data = vec![]; try!(file.read_to_end(&mut image_data)); let mut image = load_from_memory(&image_data).unwrap(); + set_webrender_image_key(webrender_api, &mut image); + Ok(Arc::new(image)) +} + +fn set_webrender_image_key(webrender_api: &webrender_traits::RenderApi, image: &mut Image) { + if image.id.is_some() { return; } let format = convert_format(image.format); let mut bytes = Vec::new(); bytes.extend_from_slice(&*image.bytes); + if format == webrender_traits::ImageFormat::RGBA8 { + premultiply(bytes.as_mut_slice()); + } let descriptor = webrender_traits::ImageDescriptor { width: image.width, height: image.height, @@ -72,7 +81,6 @@ fn get_placeholder_image(webrender_api: &webrender_traits::RenderApi, path: &Pat let image_key = webrender_api.generate_image_key(); webrender_api.add_image(image_key, descriptor, data, None); image.id = Some(image_key); - Ok(Arc::new(image)) } // TODO(gw): This is a port of the old is_image_opaque code from WR. @@ -338,26 +346,7 @@ impl ImageCacheStore { }; match load_result { - LoadResult::Loaded(ref mut image) => { - let format = convert_format(image.format); - let mut bytes = Vec::new(); - bytes.extend_from_slice(&*image.bytes); - if format == webrender_traits::ImageFormat::RGBA8 { - premultiply(bytes.as_mut_slice()); - } - let descriptor = webrender_traits::ImageDescriptor { - width: image.width, - height: image.height, - stride: None, - format: format, - offset: 0, - is_opaque: is_image_opaque(format, &bytes), - }; - let data = webrender_traits::ImageData::new(bytes); - let image_key = self.webrender_api.generate_image_key(); - self.webrender_api.add_image(image_key, descriptor, data, None); - image.id = Some(image_key); - } + LoadResult::Loaded(ref mut image) => set_webrender_image_key(&self.webrender_api, image), LoadResult::PlaceholderLoaded(..) | LoadResult::None => {} } @@ -576,4 +565,9 @@ impl ImageCache for ImageCacheImpl { } } } + + /// Ensure an image has a webrender key. + fn set_webrender_image_key(&self, image: &mut Image) { + set_webrender_image_key(&self.store.lock().unwrap().webrender_api, image); + } } diff --git a/components/net_traits/image_cache.rs b/components/net_traits/image_cache.rs index 99f8d576d71a..cfc41ea2142f 100644 --- a/components/net_traits/image_cache.rs +++ b/components/net_traits/image_cache.rs @@ -118,4 +118,7 @@ pub trait ImageCache: Sync + Send { /// Inform the image cache about a response for a pending request. fn notify_pending_response(&self, id: PendingImageId, action: FetchResponseMsg); + + /// Ensure an image has a webrender key. + fn set_webrender_image_key(&self, image: &mut Image); } diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 8ab41391bb2d..3ea53f2b4ca2 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -391,6 +391,7 @@ pub mod node; pub mod nodeiterator; pub mod nodelist; pub mod pagetransitionevent; +pub mod paintworkletglobalscope; pub mod performance; pub mod performancetiming; pub mod permissions; diff --git a/components/script/dom/paintworkletglobalscope.rs b/components/script/dom/paintworkletglobalscope.rs new file mode 100644 index 000000000000..6a4babe896d5 --- /dev/null +++ b/components/script/dom/paintworkletglobalscope.rs @@ -0,0 +1,97 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use app_units::Au; +use dom::bindings::cell::DOMRefCell; +use dom::bindings::codegen::Bindings::PaintWorkletGlobalScopeBinding; +use dom::bindings::codegen::Bindings::PaintWorkletGlobalScopeBinding::PaintWorkletGlobalScopeMethods; +use dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction; +use dom::bindings::js::Root; +use dom::bindings::str::DOMString; +use dom::workletglobalscope::WorkletGlobalScope; +use dom::workletglobalscope::WorkletGlobalScopeInit; +use dom_struct::dom_struct; +use euclid::Size2D; +use ipc_channel::ipc::IpcSharedMemory; +use js::rust::Runtime; +use msg::constellation_msg::PipelineId; +use net_traits::image::base::Image; +use net_traits::image::base::PixelFormat; +use script_traits::PaintWorkletError; +use servo_atoms::Atom; +use servo_url::ServoUrl; +use std::rc::Rc; +use std::sync::mpsc::Sender; + +#[dom_struct] +/// https://drafts.css-houdini.org/css-paint-api/#paintworkletglobalscope +pub struct PaintWorkletGlobalScope { + /// The worklet global for this object + worklet_global: WorkletGlobalScope, + /// A buffer to draw into + buffer: DOMRefCell>, +} + +impl PaintWorkletGlobalScope { + #[allow(unsafe_code)] + pub fn new(runtime: &Runtime, + pipeline_id: PipelineId, + base_url: ServoUrl, + init: &WorkletGlobalScopeInit) + -> Root { + debug!("Creating paint worklet global scope for pipeline {}.", pipeline_id); + let global = box PaintWorkletGlobalScope { + worklet_global: WorkletGlobalScope::new_inherited(pipeline_id, base_url, init), + buffer: Default::default(), + }; + unsafe { PaintWorkletGlobalScopeBinding::Wrap(runtime.cx(), global) } + } + + pub fn perform_a_worklet_task(&self, task: PaintWorkletTask) { + match task { + PaintWorkletTask::DrawAPaintImage(name, size, sender) => self.draw_a_paint_image(name, size, sender), + } + } + + fn draw_a_paint_image(&self, + name: Atom, + concrete_object_size: Size2D, + sender: Sender>) { + let width = concrete_object_size.width.to_px().abs() as u32; + let height = concrete_object_size.height.to_px().abs() as u32; + let area = (width as usize) * (height as usize); + let old_buffer_size = self.buffer.borrow().len(); + let new_buffer_size = area * 4; + debug!("Drawing a paint image {}({},{}).", name, width, height); + // TODO: call into script to create the image. + // For now, we just build a dummy. + if new_buffer_size > old_buffer_size { + let pixel = [0xFF, 0x00, 0x00, 0xFF]; + self.buffer.borrow_mut().extend(pixel.iter().cycle().take(new_buffer_size - old_buffer_size)); + } else { + self.buffer.borrow_mut().truncate(new_buffer_size); + } + let image = Image { + width: width, + height: height, + format: PixelFormat::RGBA8, + bytes: IpcSharedMemory::from_bytes(&*self.buffer.borrow()), + id: None, + }; + let _ = sender.send(Ok(image)); + } +} + +impl PaintWorkletGlobalScopeMethods for PaintWorkletGlobalScope { + /// https://drafts.css-houdini.org/css-paint-api/#dom-paintworkletglobalscope-registerpaint + fn RegisterPaint(&self, name: DOMString, _paintCtor: Rc) { + debug!("Registering paint image name {}.", name); + // TODO + } +} + +/// Tasks which can be peformed by a paint worklet +pub enum PaintWorkletTask { + DrawAPaintImage(Atom, Size2D, Sender>) +} diff --git a/components/script/dom/webidls/PaintWorkletGlobalScope.webidl b/components/script/dom/webidls/PaintWorkletGlobalScope.webidl new file mode 100644 index 000000000000..1611ad236941 --- /dev/null +++ b/components/script/dom/webidls/PaintWorkletGlobalScope.webidl @@ -0,0 +1,9 @@ +/* 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/. */ + +// https://drafts.css-houdini.org/css-paint-api/#paintworkletglobalscope +[Global=(Worklet,PaintWorklet), Exposed=PaintWorklet] +interface PaintWorkletGlobalScope : WorkletGlobalScope { + void registerPaint(DOMString name, VoidFunction paintCtor); +}; diff --git a/components/script/dom/webidls/Window.webidl b/components/script/dom/webidls/Window.webidl index c4c253997431..50808c830165 100644 --- a/components/script/dom/webidls/Window.webidl +++ b/components/script/dom/webidls/Window.webidl @@ -204,3 +204,7 @@ partial interface Window { //readonly attribute EventSender eventSender; }; +// https://drafts.css-houdini.org/css-paint-api-1/#paint-worklet +partial interface Window { + [SameObject] readonly attribute Worklet paintWorklet; +}; diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index fc793c206c49..e627721bd05e 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -51,6 +51,7 @@ use dom::storage::Storage; use dom::testrunner::TestRunner; use dom::windowproxy::WindowProxy; use dom::worklet::Worklet; +use dom::workletglobalscope::WorkletGlobalScopeType; use dom_struct::dom_struct; use euclid::{Point2D, Rect, Size2D}; use fetch; @@ -279,6 +280,8 @@ pub struct Window { /// Worklets test_worklet: MutNullableJS, + /// https://drafts.css-houdini.org/css-paint-api-1/#paint-worklet + paint_worklet: MutNullableJS, } impl Window { @@ -373,6 +376,14 @@ impl Window { self.webvr_thread.clone() } + fn new_paint_worklet(&self) -> Root { + debug!("Creating new paint worklet."); + let worklet = Worklet::new(self, WorkletGlobalScopeType::Paint); + let executor = Arc::new(worklet.executor()); + let _ = self.layout_chan.send(Msg::SetPaintWorkletExecutor(executor)); + worklet + } + pub fn permission_state_invocation_results(&self) -> &DOMRefCell> { &self.permission_state_invocation_results } @@ -1011,6 +1022,11 @@ impl WindowMethods for Window { fetch::Fetch(&self.upcast(), input, init) } + // https://drafts.css-houdini.org/css-paint-api-1/#paint-worklet + fn PaintWorklet(&self) -> Root { + self.paint_worklet.or_init(|| self.new_paint_worklet()) + } + fn TestRunner(&self) -> Root { self.test_runner.or_init(|| TestRunner::new(self.upcast())) } @@ -1856,6 +1872,7 @@ impl Window { pending_layout_images: DOMRefCell::new(HashMap::new()), unminified_js_dir: DOMRefCell::new(None), test_worklet: Default::default(), + paint_worklet: Default::default(), }; unsafe { diff --git a/components/script/dom/worklet.rs b/components/script/dom/worklet.rs index fa5b3950b518..da74215f7529 100644 --- a/components/script/dom/worklet.rs +++ b/components/script/dom/worklet.rs @@ -10,6 +10,7 @@ //! thread pool implementation, which only performs GC or code loading on //! a backup thread, not on the primary worklet thread. +use app_units::Au; use dom::bindings::codegen::Bindings::RequestBinding::RequestCredentials; use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; use dom::bindings::codegen::Bindings::WorkletBinding::WorkletMethods; @@ -27,6 +28,7 @@ use dom::bindings::str::USVString; use dom::bindings::trace::JSTraceable; use dom::bindings::trace::RootedTraceableBox; use dom::globalscope::GlobalScope; +use dom::paintworkletglobalscope::PaintWorkletTask; use dom::promise::Promise; use dom::testworkletglobalscope::TestWorkletTask; use dom::window::Window; @@ -35,6 +37,7 @@ use dom::workletglobalscope::WorkletGlobalScopeInit; use dom::workletglobalscope::WorkletGlobalScopeType; use dom::workletglobalscope::WorkletTask; use dom_struct::dom_struct; +use euclid::Size2D; use js::jsapi::JSGCParamKey; use js::jsapi::JSTracer; use js::jsapi::JS_GC; @@ -42,6 +45,7 @@ use js::jsapi::JS_GetGCParameter; use js::rust::Runtime; use msg::constellation_msg::PipelineId; use net_traits::IpcSend; +use net_traits::image::base::Image; use net_traits::load_whole_resource; use net_traits::request::Destination; use net_traits::request::RequestInit; @@ -54,6 +58,9 @@ use script_runtime::new_rt_and_cx; use script_thread::MainThreadScriptMsg; use script_thread::Runnable; use script_thread::ScriptThread; +use script_traits::PaintWorkletError; +use script_traits::PaintWorkletExecutor; +use servo_atoms::Atom; use servo_rand; use servo_url::ImmutableOrigin; use servo_url::ServoUrl; @@ -62,12 +69,14 @@ use std::collections::HashMap; use std::collections::hash_map; use std::rc::Rc; use std::sync::Arc; +use std::sync::Mutex; use std::sync::atomic::AtomicIsize; use std::sync::atomic::Ordering; use std::sync::mpsc; use std::sync::mpsc::Receiver; use std::sync::mpsc::Sender; use std::thread; +use std::time::Duration; use style::thread_state; use swapper::Swapper; use swapper::swapper; @@ -76,6 +85,7 @@ use uuid::Uuid; // Magic numbers const WORKLET_THREAD_POOL_SIZE: u32 = 3; const MIN_GC_THRESHOLD: u32 = 1_000_000; +const PAINT_TIMEOUT_MILLISECONDS: u64 = 10; #[dom_struct] /// https://drafts.css-houdini.org/worklets/#worklet @@ -109,6 +119,13 @@ impl Worklet { pub fn worklet_global_scope_type(&self) -> WorkletGlobalScopeType { self.global_type } + + pub fn executor(&self) -> WorkletExecutor { + WorkletExecutor { + worklet_id: self.worklet_id, + primary_sender: Mutex::new(ScriptThread::worklet_thread_pool().primary_sender.clone()), + } + } } impl WorkletMethods for Worklet { @@ -561,7 +578,8 @@ impl WorkletThread { // TODO: Caching. // TODO: Avoid re-parsing the origin as a URL. let resource_fetcher = self.global_init.resource_threads.sender(); - let origin_url = ServoUrl::parse(&*origin.unicode_serialization()).expect("Failed to parse origin as URL."); + let origin_url = ServoUrl::parse(&*origin.unicode_serialization()) + .unwrap_or_else(|_| ServoUrl::parse("about:blank").unwrap()); let request = RequestInit { url: script_url, type_: RequestType::Script, @@ -635,3 +653,35 @@ impl WorkletThread { self.script_sender.send(msg).expect("Worklet thread outlived script thread."); } } + +/// An executor of worklet tasks +pub struct WorkletExecutor { + worklet_id: WorkletId, + // Rather annoyingly, we have to use a mutex here because + // layout threads share their context rather than cloning it. + primary_sender: Mutex>, +} + +impl WorkletExecutor { + /// Schedule a worklet task to be peformed by the worklet thread pool. + fn schedule_a_worklet_task(&self, task: WorkletTask) { + let _ = self.primary_sender.lock() + .expect("Locking the worklet channel.") + .send(WorkletData::Task(self.worklet_id, task)); + } +} + +impl PaintWorkletExecutor for WorkletExecutor { + /// https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image + fn draw_a_paint_image(&self, + name: Atom, + concrete_object_size: Size2D) + -> Result + { + let (sender, receiver) = mpsc::channel(); + let task = WorkletTask::Paint(PaintWorkletTask::DrawAPaintImage(name, concrete_object_size, sender)); + let timeout = Duration::from_millis(PAINT_TIMEOUT_MILLISECONDS); + self.schedule_a_worklet_task(task); + receiver.recv_timeout(timeout)? + } +} diff --git a/components/script/dom/workletglobalscope.rs b/components/script/dom/workletglobalscope.rs index a2e2463ca27c..78804d66ae04 100644 --- a/components/script/dom/workletglobalscope.rs +++ b/components/script/dom/workletglobalscope.rs @@ -6,6 +6,8 @@ use devtools_traits::ScriptToDevtoolsControlMsg; use dom::bindings::inheritance::Castable; use dom::bindings::js::Root; use dom::globalscope::GlobalScope; +use dom::paintworkletglobalscope::PaintWorkletGlobalScope; +use dom::paintworkletglobalscope::PaintWorkletTask; use dom::testworkletglobalscope::TestWorkletGlobalScope; use dom::testworkletglobalscope::TestWorkletTask; use dom_struct::dom_struct; @@ -92,6 +94,10 @@ impl WorkletGlobalScope { Some(global) => global.perform_a_worklet_task(task), None => warn!("This is not a test worklet."), }, + WorkletTask::Paint(task) => match self.downcast::() { + Some(global) => global.perform_a_worklet_task(task), + None => warn!("This is not a paint worklet."), + }, } } } @@ -116,8 +122,10 @@ pub struct WorkletGlobalScopeInit { /// https://drafts.css-houdini.org/worklets/#worklet-global-scope-type #[derive(Clone, Copy, Debug, HeapSizeOf, JSTraceable)] pub enum WorkletGlobalScopeType { - /// https://drafts.css-houdini.org/worklets/#examples + /// A servo-specific testing worklet Test, + /// A paint worklet + Paint, } impl WorkletGlobalScopeType { @@ -132,6 +140,8 @@ impl WorkletGlobalScopeType { match *self { WorkletGlobalScopeType::Test => Root::upcast(TestWorkletGlobalScope::new(runtime, pipeline_id, base_url, init)), + WorkletGlobalScopeType::Paint => + Root::upcast(PaintWorkletGlobalScope::new(runtime, pipeline_id, base_url, init)), } } } @@ -139,5 +149,5 @@ impl WorkletGlobalScopeType { /// A task which can be performed in the context of a worklet global. pub enum WorkletTask { Test(TestWorkletTask), + Paint(PaintWorkletTask), } - diff --git a/components/script_layout_interface/message.rs b/components/script_layout_interface/message.rs index 038967fdd04a..104de7da3d99 100644 --- a/components/script_layout_interface/message.rs +++ b/components/script_layout_interface/message.rs @@ -14,6 +14,7 @@ use profile_traits::mem::ReportsChan; use rpc::LayoutRPC; use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg}; use script_traits::{ScrollState, UntrustedNodeAddress, WindowSizeData}; +use script_traits::PaintWorkletExecutor; use servo_url::ServoUrl; use std::sync::Arc; use std::sync::mpsc::{Receiver, Sender}; @@ -84,6 +85,9 @@ pub enum Msg { /// 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. + SetPaintWorkletExecutor(Arc), } diff --git a/components/script_traits/Cargo.toml b/components/script_traits/Cargo.toml index 2e957b3882f8..ac85cd782ec6 100644 --- a/components/script_traits/Cargo.toml +++ b/components/script_traits/Cargo.toml @@ -10,6 +10,7 @@ name = "script_traits" path = "lib.rs" [dependencies] +app_units = "0.4" bluetooth_traits = {path = "../bluetooth_traits"} canvas_traits = {path = "../canvas_traits"} cookie = "0.6" @@ -29,6 +30,7 @@ profile_traits = {path = "../profile_traits"} rustc-serialize = "0.3.4" serde = "0.9" serde_derive = "0.9" +servo_atoms = {path = "../atoms"} servo_url = {path = "../url"} style_traits = {path = "../style_traits", features = ["servo"]} time = "0.1.12" diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index ba92857efbc6..c036eee28d92 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -9,6 +9,7 @@ #![deny(missing_docs)] #![deny(unsafe_code)] +extern crate app_units; extern crate bluetooth_traits; extern crate canvas_traits; extern crate cookie as cookie_rs; @@ -30,6 +31,7 @@ extern crate rustc_serialize; extern crate serde; #[macro_use] extern crate serde_derive; +extern crate servo_atoms; extern crate servo_url; extern crate style_traits; extern crate time; @@ -39,6 +41,7 @@ extern crate webvr_traits; mod script_msg; pub mod webdriver_msg; +use app_units::Au; use bluetooth_traits::BluetoothRequest; use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId}; use euclid::Size2D; @@ -63,12 +66,13 @@ use net_traits::storage_thread::StorageType; use profile_traits::mem; use profile_traits::time as profile_time; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use servo_atoms::Atom; use servo_url::ImmutableOrigin; use servo_url::ServoUrl; use std::collections::HashMap; use std::fmt; use std::sync::Arc; -use std::sync::mpsc::{Receiver, Sender}; +use std::sync::mpsc::{Receiver, Sender, RecvTimeoutError}; use style_traits::CSSPixel; use webdriver_msg::{LoadStatus, WebDriverScriptCommand}; use webrender_traits::ClipId; @@ -813,3 +817,28 @@ pub struct WorkerScriptLoadOrigin { /// the pipeline id of the entity requesting the load pub pipeline_id: Option, } + +/// Errors from executing a paint worklet +#[derive(Debug, Deserialize, Serialize, Clone)] +pub enum PaintWorkletError { + /// Execution timed out. + Timeout, + /// No such worklet. + WorkletNotFound, +} + +impl From for PaintWorkletError { + fn from(_: RecvTimeoutError) -> PaintWorkletError { + PaintWorkletError::Timeout + } +} + +/// Execute paint code in the worklet thread pool.< +pub trait PaintWorkletExecutor: Sync + Send { + /// https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image + fn draw_a_paint_image(&self, + name: Atom, + concrete_object_size: Size2D) + -> Result; +} + diff --git a/components/style/values/generics/image.rs b/components/style/values/generics/image.rs index 5965db357dc7..7730cbea9f92 100644 --- a/components/style/values/generics/image.rs +++ b/components/style/values/generics/image.rs @@ -27,6 +27,10 @@ pub enum Image { Rect(ImageRect), /// A `-moz-element(# )` Element(Atom), + /// A paint worklet image. + /// https://drafts.css-houdini.org/css-paint-api/ + #[cfg(feature = "servo")] + PaintWorklet(PaintWorklet), } /// A CSS gradient. @@ -128,6 +132,23 @@ pub struct ColorStop { pub position: Option, } +/// Specified values for a paint worklet. +/// https://drafts.css-houdini.org/css-paint-api/ +#[derive(Clone, Debug, PartialEq, ToComputedValue)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub struct PaintWorklet { + /// The name the worklet was registered with. + pub name: Atom, +} + +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)?; + dest.write_str(")") + } +} + /// Values for `moz-image-rect`. /// /// `-moz-image-rect(, top, right, bottom, left);` @@ -150,6 +171,8 @@ impl fmt::Debug for Image Image::Url(ref url) => url.to_css(f), Image::Gradient(ref grad) => grad.fmt(f), Image::Rect(ref rect) => rect.fmt(f), + #[cfg(feature = "servo")] + Image::PaintWorklet(ref paint_worklet) => paint_worklet.fmt(f), Image::Element(ref selector) => { f.write_str("-moz-element(#")?; serialize_identifier(&selector.to_string(), f)?; @@ -167,6 +190,8 @@ impl ToCss for Image Image::Url(ref url) => url.to_css(dest), Image::Gradient(ref gradient) => gradient.to_css(dest), Image::Rect(ref rect) => rect.to_css(dest), + #[cfg(feature = "servo")] + Image::PaintWorklet(ref paint_worklet) => paint_worklet.to_css(dest), Image::Element(ref selector) => { dest.write_str("-moz-element(#")?; serialize_identifier(&selector.to_string(), dest)?; diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index d14e9b492b25..4564bc0993f0 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -22,6 +22,7 @@ use values::generics::image::{EndingShape as GenericEndingShape, Gradient as Gen use values::generics::image::{GradientItem as GenericGradientItem, GradientKind as GenericGradientKind}; use values::generics::image::{Image as GenericImage, ImageRect as GenericImageRect}; use values::generics::image::{LineDirection as GenericsLineDirection, ShapeExtent}; +use values::generics::image::PaintWorklet; use values::generics::position::Position as GenericPosition; use values::specified::{Angle, CSSColor, Color, Length, LengthOrPercentage}; use values::specified::{Number, NumberOrPercentage, Percentage}; @@ -98,6 +99,12 @@ impl Parse for Image { if let Ok(gradient) = input.try(|i| Gradient::parse(context, i)) { return Ok(GenericImage::Gradient(gradient)); } + #[cfg(feature = "servo")] + { + if let Ok(paint_worklet) = input.try(|i| PaintWorklet::parse(context, i)) { + return Ok(GenericImage::PaintWorklet(paint_worklet)); + } + } #[cfg(feature = "gecko")] { if let Ok(mut image_rect) = input.try(|input| ImageRect::parse(context, input)) { @@ -673,6 +680,18 @@ impl Parse for ColorStop { } } +impl Parse for PaintWorklet { + fn parse(_context: &ParserContext, input: &mut Parser) -> Result { + input.expect_function_matching("paint")?; + input.parse_nested_block(|i| { + let name = i.expect_ident()?; + Ok(PaintWorklet { + name: Atom::from(name), + }) + }) + } +} + impl Parse for ImageRect { fn parse(context: &ParserContext, input: &mut Parser) -> Result { input.try(|i| i.expect_function_matching("-moz-image-rect"))?; diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 66f4ca615c80..05fe2bf665be 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -6736,6 +6736,18 @@ ], {} ] + ], + "mozilla/worklets/test_paint_worklet.html": [ + [ + "/_mozilla/mozilla/worklets/test_paint_worklet.html", + [ + [ + "/_mozilla/mozilla/worklets/test_paint_worklet_ref.html", + "==" + ] + ], + {} + ] ] }, "reftest_node": { @@ -11208,6 +11220,16 @@ {} ] ], + "mozilla/worklets/test_paint_worklet.js": [ + [ + {} + ] + ], + "mozilla/worklets/test_paint_worklet_ref.html": [ + [ + {} + ] + ], "mozilla/worklets/test_worklet.js": [ [ {} @@ -31775,6 +31797,18 @@ "f3a9b8c78346507bc0b3190c8000ccf80cc133f6", "support" ], + "mozilla/worklets/test_paint_worklet.html": [ + "67fccbde17c28e13b5f4dc54d70b1279d6e9d602", + "reftest" + ], + "mozilla/worklets/test_paint_worklet.js": [ + "e714db50da9e5cb18c652629fcc1b5ccc453564d", + "support" + ], + "mozilla/worklets/test_paint_worklet_ref.html": [ + "e9cfa945824a8ecf07c41a269f82a2c2ca002406", + "support" + ], "mozilla/worklets/test_worklet.html": [ "fe9c93a5307c616f878b6623155e1b04c86dd994", "testharness" diff --git a/tests/wpt/mozilla/meta/mozilla/worklets/test_paint_worklet.html.ini b/tests/wpt/mozilla/meta/mozilla/worklets/test_paint_worklet.html.ini new file mode 100644 index 000000000000..2f081d45de60 --- /dev/null +++ b/tests/wpt/mozilla/meta/mozilla/worklets/test_paint_worklet.html.ini @@ -0,0 +1,4 @@ +[test_paint_worklet.html] + type: reftest + expected: FAIL + diff --git a/tests/wpt/mozilla/tests/mozilla/worklets/test_paint_worklet.html b/tests/wpt/mozilla/tests/mozilla/worklets/test_paint_worklet.html new file mode 100644 index 000000000000..1dfe906a0e2a --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/worklets/test_paint_worklet.html @@ -0,0 +1,18 @@ + + + + + A basic paint worklet test + + + +
+ + + diff --git a/tests/wpt/mozilla/tests/mozilla/worklets/test_paint_worklet.js b/tests/wpt/mozilla/tests/mozilla/worklets/test_paint_worklet.js new file mode 100644 index 000000000000..3ccc61d61b37 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/worklets/test_paint_worklet.js @@ -0,0 +1,6 @@ +registerPaint("test", class { + paint(ctx, size) { + ctx.fillStyle = 'green'; + ctx.fillRect(0, 0, size.width, size.height); + } +}); diff --git a/tests/wpt/mozilla/tests/mozilla/worklets/test_paint_worklet_ref.html b/tests/wpt/mozilla/tests/mozilla/worklets/test_paint_worklet_ref.html new file mode 100644 index 000000000000..2d3f57bacd04 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/worklets/test_paint_worklet_ref.html @@ -0,0 +1,6 @@ + + + +
+ +