aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock2
-rw-r--r--components/layout/context.rs4
-rw-r--r--components/layout/display_list_builder.rs347
-rw-r--r--components/layout_thread/lib.rs11
-rw-r--r--components/net/image_cache.rs36
-rw-r--r--components/net_traits/image_cache.rs3
-rw-r--r--components/script/dom/mod.rs1
-rw-r--r--components/script/dom/paintworkletglobalscope.rs97
-rw-r--r--components/script/dom/webidls/PaintWorkletGlobalScope.webidl9
-rw-r--r--components/script/dom/webidls/Window.webidl4
-rw-r--r--components/script/dom/window.rs17
-rw-r--r--components/script/dom/worklet.rs52
-rw-r--r--components/script/dom/workletglobalscope.rs14
-rw-r--r--components/script_layout_interface/message.rs4
-rw-r--r--components/script_traits/Cargo.toml2
-rw-r--r--components/script_traits/lib.rs31
-rw-r--r--components/style/values/generics/image.rs25
-rw-r--r--components/style/values/specified/image.rs19
-rw-r--r--tests/wpt/mozilla/meta/MANIFEST.json34
-rw-r--r--tests/wpt/mozilla/meta/mozilla/worklets/test_paint_worklet.html.ini4
-rw-r--r--tests/wpt/mozilla/tests/mozilla/worklets/test_paint_worklet.html18
-rw-r--r--tests/wpt/mozilla/tests/mozilla/worklets/test_paint_worklet.js6
-rw-r--r--tests/wpt/mozilla/tests/mozilla/worklets/test_paint_worklet_ref.html6
23 files changed, 595 insertions, 151 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 50bfbccb10e..faa3d1d7d30 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 3a4fac939cd..22b407d1fb6 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<FnvHasher>>>>,
+ /// The executor for worklets
+ pub paint_worklet_executor: Option<Arc<PaintWorkletExecutor>>,
+
/// 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>>>,
diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs
index c4a20d1ee11..6794d144a47 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<Au>,
+ 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<Au>,
+ clip: &ClippingRegion,
+ paint_worklet: &PaintWorklet,
+ index: usize);
+
fn convert_linear_gradient(&self,
bounds: &Rect<Au>,
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<Au>,
+ 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<Au>,
+ 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 9ddff21070e..4e91da113a6 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<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder),
WebRenderImageInfo>>>,
+ /// The executor for paint worklets.
+ /// Will be None if the script thread hasn't added any paint worklet modules.
+ paint_worklet_executor: Option<Arc<PaintWorkletExecutor>>,
/// 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 b8467e5a461..d0c31eb23b5 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 99f8d576d71..cfc41ea2142 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 8ab41391bb2..3ea53f2b4ca 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 00000000000..6a4babe896d
--- /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<Vec<u8>>,
+}
+
+impl PaintWorkletGlobalScope {
+ #[allow(unsafe_code)]
+ pub fn new(runtime: &Runtime,
+ pipeline_id: PipelineId,
+ base_url: ServoUrl,
+ 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, 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<Au>,
+ sender: Sender<Result<Image, PaintWorkletError>>) {
+ 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<VoidFunction>) {
+ debug!("Registering paint image name {}.", name);
+ // TODO
+ }
+}
+
+/// Tasks which can be peformed by a paint worklet
+pub enum PaintWorkletTask {
+ DrawAPaintImage(Atom, Size2D<Au>, Sender<Result<Image, PaintWorkletError>>)
+}
diff --git a/components/script/dom/webidls/PaintWorkletGlobalScope.webidl b/components/script/dom/webidls/PaintWorkletGlobalScope.webidl
new file mode 100644
index 00000000000..1611ad23694
--- /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 c4c25399743..50808c83016 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 fc793c206c4..e627721bd05 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<Worklet>,
+ /// https://drafts.css-houdini.org/css-paint-api-1/#paint-worklet
+ paint_worklet: MutNullableJS<Worklet>,
}
impl Window {
@@ -373,6 +376,14 @@ impl Window {
self.webvr_thread.clone()
}
+ fn new_paint_worklet(&self) -> Root<Worklet> {
+ 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<HashMap<String, PermissionState>> {
&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<Worklet> {
+ self.paint_worklet.or_init(|| self.new_paint_worklet())
+ }
+
fn TestRunner(&self) -> Root<TestRunner> {
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 fa5b3950b51..da74215f752 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<Sender<WorkletData>>,
+}
+
+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<Au>)
+ -> Result<Image, PaintWorkletError>
+ {
+ 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 a2e2463ca27..78804d66ae0 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::<PaintWorkletGlobalScope>() {
+ 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 038967fdd04..104de7da3d9 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<PaintWorkletExecutor>),
}
diff --git a/components/script_traits/Cargo.toml b/components/script_traits/Cargo.toml
index 2e957b3882f..ac85cd782ec 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 ba92857efbc..c036eee28d9 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<PipelineId>,
}
+
+/// Errors from executing a paint worklet
+#[derive(Debug, Deserialize, Serialize, Clone)]
+pub enum PaintWorkletError {
+ /// Execution timed out.
+ Timeout,
+ /// No such worklet.
+ WorkletNotFound,
+}
+
+impl From<RecvTimeoutError> 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<Au>)
+ -> Result<Image, PaintWorkletError>;
+}
+
diff --git a/components/style/values/generics/image.rs b/components/style/values/generics/image.rs
index 5965db357dc..7730cbea9f9 100644
--- a/components/style/values/generics/image.rs
+++ b/components/style/values/generics/image.rs
@@ -27,6 +27,10 @@ pub enum Image<Gradient, ImageRect> {
Rect(ImageRect),
/// A `-moz-element(# <element-id>)`
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<Color, LengthOrPercentage> {
pub position: Option<LengthOrPercentage>,
}
+/// 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<W>(&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(<uri>, top, right, bottom, left);`
@@ -150,6 +171,8 @@ impl<G, R> fmt::Debug for Image<G, R>
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<G, R> ToCss for Image<G, R>
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 d14e9b492b2..4564bc0993f 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<Self, ()> {
+ 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<Self, ()> {
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 66f4ca615c8..05fe2bf665b 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 00000000000..2f081d45de6
--- /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 00000000000..1dfe906a0e2
--- /dev/null
+++ b/tests/wpt/mozilla/tests/mozilla/worklets/test_paint_worklet.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html class="reftest-wait">
+ <head>
+ <meta charset=utf-8>
+ <title>A basic paint worklet test</title>
+ <link rel=match href=/_mozilla/mozilla/worklets/test_paint_worklet_ref.html>
+ </head>
+ <body>
+ <div style="height: 100px; width: 100px; background: paint(test);"></div>
+ </body>
+ <script>
+ // This reftest will TIMEOUT if loading the paint worklet fails,
+ // It will PASS if the worklet draws a green rectangle.
+ window.paintWorklet
+ .addModule("test_paint_worklet.js")
+ .then(function() { document.documentElement.classList.remove("reftest-wait"); });
+ </script>
+</html>
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 00000000000..3ccc61d61b3
--- /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 00000000000..2d3f57bacd0
--- /dev/null
+++ b/tests/wpt/mozilla/tests/mozilla/worklets/test_paint_worklet_ref.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<html>
+ <body>
+ <div style="height: 100px; width: 100px; background: green;"></div>
+ </body>
+</html>