mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
When a paint worklet thread takes too long, we would like to move on, since we have a ~16ms budget for rendering @60fps. At the moment, there is no provision in the paintworklet spec to signal such timeouts to the developer. ajeffrey opened an [issue][1] for this, but it got punted to v2 of the spec. Hence we are silently timing out unresponsive paint scripts. The timeout value is chosen to be 10ms by default, and can be overridden by setting the `dom.worklet.timeout_ms` pref. In the absence of such a timeout, the reftest in this commit would fail by timing out the testrunner itself, since the paint script never returns. From my discussions with ajeffrey, this should do until we spec out a way to signal timeouts to the script developer. Since we did not have a better way to trigger a timeout than a busy waiting loop (which would hog one core of the test machine until the timeout was reached), we decided to implement a test only blocking sleep, available to the PaintWorkletGlobalScope. Since `dom.worklet.enabled` enables worklets in general, we also decided to have another pref `dom.worklet.blockingsleep.enabled`, which, in addition to `dom.worklet.enabled`, would be required for the blocking sleep to be available. This fixes #17370. [1]: https://github.com/w3c/css-houdini-drafts/issues/507
522 lines
23 KiB
Rust
522 lines
23 KiB
Rust
/* 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 dom::bindings::callback::CallbackContainer;
|
|
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::conversions::get_property;
|
|
use dom::bindings::conversions::get_property_jsval;
|
|
use dom::bindings::error::Error;
|
|
use dom::bindings::error::Fallible;
|
|
use dom::bindings::inheritance::Castable;
|
|
use dom::bindings::reflector::DomObject;
|
|
use dom::bindings::root::{Dom, DomRoot};
|
|
use dom::bindings::str::DOMString;
|
|
use dom::cssstylevalue::CSSStyleValue;
|
|
use dom::paintrenderingcontext2d::PaintRenderingContext2D;
|
|
use dom::paintsize::PaintSize;
|
|
use dom::stylepropertymapreadonly::StylePropertyMapReadOnly;
|
|
use dom::worklet::WorkletExecutor;
|
|
use dom::workletglobalscope::WorkletGlobalScope;
|
|
use dom::workletglobalscope::WorkletGlobalScopeInit;
|
|
use dom::workletglobalscope::WorkletTask;
|
|
use dom_struct::dom_struct;
|
|
use euclid::TypedScale;
|
|
use euclid::TypedSize2D;
|
|
use ipc_channel::ipc;
|
|
use js::jsapi::Call;
|
|
use js::jsapi::Construct1;
|
|
use js::jsapi::HandleValue;
|
|
use js::jsapi::HandleValueArray;
|
|
use js::jsapi::Heap;
|
|
use js::jsapi::IsCallable;
|
|
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;
|
|
use js::rust::Runtime;
|
|
use msg::constellation_msg::PipelineId;
|
|
use net_traits::image::base::PixelFormat;
|
|
use net_traits::image_cache::ImageCache;
|
|
use script_traits::{DrawAPaintImageResult, PaintWorkletError};
|
|
use script_traits::Painter;
|
|
use servo_atoms::Atom;
|
|
use servo_config::prefs::PREFS;
|
|
use servo_url::ServoUrl;
|
|
use std::cell::Cell;
|
|
use std::collections::HashMap;
|
|
use std::collections::hash_map::Entry;
|
|
use std::ptr::null_mut;
|
|
use std::rc::Rc;
|
|
use std::sync::Arc;
|
|
use std::sync::Mutex;
|
|
use std::sync::mpsc;
|
|
use std::sync::mpsc::Sender;
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
use style_traits::CSSPixel;
|
|
use style_traits::DevicePixel;
|
|
use style_traits::SpeculativePainter;
|
|
|
|
/// <https://drafts.css-houdini.org/css-paint-api/#paintworkletglobalscope>
|
|
#[dom_struct]
|
|
pub struct PaintWorkletGlobalScope {
|
|
/// The worklet global for this object
|
|
worklet_global: WorkletGlobalScope,
|
|
/// The image cache
|
|
#[ignore_malloc_size_of = "Arc"]
|
|
image_cache: Arc<ImageCache>,
|
|
/// <https://drafts.css-houdini.org/css-paint-api/#paint-definitions>
|
|
paint_definitions: DomRefCell<HashMap<Atom, Box<PaintDefinition>>>,
|
|
/// <https://drafts.css-houdini.org/css-paint-api/#paint-class-instances>
|
|
paint_class_instances: DomRefCell<HashMap<Atom, Box<Heap<JSVal>>>>,
|
|
/// The most recent name the worklet was called with
|
|
cached_name: DomRefCell<Atom>,
|
|
/// The most recent size the worklet was drawn at
|
|
cached_size: Cell<TypedSize2D<f32, CSSPixel>>,
|
|
/// The most recent device pixel ratio the worklet was drawn at
|
|
cached_device_pixel_ratio: Cell<TypedScale<f32, CSSPixel, DevicePixel>>,
|
|
/// The most recent properties the worklet was drawn at
|
|
cached_properties: DomRefCell<Vec<(Atom, String)>>,
|
|
/// The most recent arguments the worklet was drawn at
|
|
cached_arguments: DomRefCell<Vec<String>>,
|
|
/// The most recent result
|
|
cached_result: DomRefCell<DrawAPaintImageResult>,
|
|
}
|
|
|
|
impl PaintWorkletGlobalScope {
|
|
#[allow(unsafe_code)]
|
|
pub fn new(runtime: &Runtime,
|
|
pipeline_id: PipelineId,
|
|
base_url: ServoUrl,
|
|
executor: WorkletExecutor,
|
|
init: &WorkletGlobalScopeInit)
|
|
-> DomRoot<PaintWorkletGlobalScope> {
|
|
debug!("Creating paint worklet global scope for pipeline {}.", pipeline_id);
|
|
let global = Box::new(PaintWorkletGlobalScope {
|
|
worklet_global: WorkletGlobalScope::new_inherited(pipeline_id, base_url, executor, init),
|
|
image_cache: init.image_cache.clone(),
|
|
paint_definitions: Default::default(),
|
|
paint_class_instances: Default::default(),
|
|
cached_name: DomRefCell::new(Atom::from("")),
|
|
cached_size: Cell::new(TypedSize2D::zero()),
|
|
cached_device_pixel_ratio: Cell::new(TypedScale::new(1.0)),
|
|
cached_properties: Default::default(),
|
|
cached_arguments: Default::default(),
|
|
cached_result: DomRefCell::new(DrawAPaintImageResult {
|
|
width: 0,
|
|
height: 0,
|
|
format: PixelFormat::BGRA8,
|
|
image_key: None,
|
|
missing_image_urls: Vec::new(),
|
|
}),
|
|
});
|
|
unsafe { PaintWorkletGlobalScopeBinding::Wrap(runtime.cx(), global) }
|
|
}
|
|
|
|
pub fn image_cache(&self) -> Arc<ImageCache> {
|
|
self.image_cache.clone()
|
|
}
|
|
|
|
pub fn perform_a_worklet_task(&self, task: PaintWorkletTask) {
|
|
match task {
|
|
PaintWorkletTask::DrawAPaintImage(name, size, device_pixel_ratio, properties, arguments, sender) => {
|
|
let cache_hit = (&*self.cached_name.borrow() == &name) &&
|
|
(self.cached_size.get() == size) &&
|
|
(self.cached_device_pixel_ratio.get() == device_pixel_ratio) &&
|
|
(&*self.cached_properties.borrow() == &properties) &&
|
|
(&*self.cached_arguments.borrow() == &arguments);
|
|
let result = if cache_hit {
|
|
debug!("Cache hit on paint worklet {}!", name);
|
|
self.cached_result.borrow().clone()
|
|
} else {
|
|
debug!("Cache miss on paint worklet {}!", name);
|
|
let map = StylePropertyMapReadOnly::from_iter(self.upcast(), properties.iter().cloned());
|
|
let result = self.draw_a_paint_image(&name, size, device_pixel_ratio, &*map, &*arguments);
|
|
if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) {
|
|
*self.cached_name.borrow_mut() = name;
|
|
self.cached_size.set(size);
|
|
self.cached_device_pixel_ratio.set(device_pixel_ratio);
|
|
*self.cached_properties.borrow_mut() = properties;
|
|
*self.cached_arguments.borrow_mut() = arguments;
|
|
*self.cached_result.borrow_mut() = result.clone();
|
|
}
|
|
result
|
|
};
|
|
let _ = sender.send(result);
|
|
}
|
|
PaintWorkletTask::SpeculativelyDrawAPaintImage(name, properties, arguments) => {
|
|
let should_speculate = (&*self.cached_name.borrow() != &name) ||
|
|
(&*self.cached_properties.borrow() != &properties) ||
|
|
(&*self.cached_arguments.borrow() != &arguments);
|
|
if should_speculate {
|
|
let size = self.cached_size.get();
|
|
let device_pixel_ratio = self.cached_device_pixel_ratio.get();
|
|
let map = StylePropertyMapReadOnly::from_iter(self.upcast(), properties.iter().cloned());
|
|
let result = self.draw_a_paint_image(&name, size, device_pixel_ratio, &*map, &*arguments);
|
|
if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) {
|
|
*self.cached_name.borrow_mut() = name;
|
|
*self.cached_properties.borrow_mut() = properties;
|
|
*self.cached_arguments.borrow_mut() = arguments;
|
|
*self.cached_result.borrow_mut() = result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image>
|
|
fn draw_a_paint_image(&self,
|
|
name: &Atom,
|
|
size_in_px: TypedSize2D<f32, CSSPixel>,
|
|
device_pixel_ratio: TypedScale<f32, CSSPixel, DevicePixel>,
|
|
properties: &StylePropertyMapReadOnly,
|
|
arguments: &[String])
|
|
-> DrawAPaintImageResult
|
|
{
|
|
let size_in_dpx = size_in_px * device_pixel_ratio;
|
|
let size_in_dpx = TypedSize2D::new(size_in_dpx.width.abs() as u32, size_in_dpx.height.abs() as u32);
|
|
|
|
// TODO: Steps 1-5.
|
|
|
|
// TODO: document paint definitions.
|
|
self.invoke_a_paint_callback(name, size_in_px, size_in_dpx, device_pixel_ratio, properties, arguments)
|
|
}
|
|
|
|
/// <https://drafts.css-houdini.org/css-paint-api/#invoke-a-paint-callback>
|
|
#[allow(unsafe_code)]
|
|
fn invoke_a_paint_callback(&self,
|
|
name: &Atom,
|
|
size_in_px: TypedSize2D<f32, CSSPixel>,
|
|
size_in_dpx: TypedSize2D<u32, DevicePixel>,
|
|
device_pixel_ratio: TypedScale<f32, CSSPixel, DevicePixel>,
|
|
properties: &StylePropertyMapReadOnly,
|
|
arguments: &[String])
|
|
-> DrawAPaintImageResult
|
|
{
|
|
debug!("Invoking a paint callback {}({},{}) at {}.",
|
|
name, size_in_px.width, size_in_px.height, device_pixel_ratio);
|
|
|
|
let cx = self.worklet_global.get_cx();
|
|
let _ac = JSAutoCompartment::new(cx, self.worklet_global.reflector().get_jsobject().get());
|
|
|
|
// TODO: Steps 1-2.1.
|
|
// Step 2.2-5.1.
|
|
rooted!(in(cx) let mut class_constructor = UndefinedValue());
|
|
rooted!(in(cx) let mut paint_function = UndefinedValue());
|
|
let rendering_context = match self.paint_definitions.borrow().get(name) {
|
|
None => {
|
|
// Step 2.2.
|
|
warn!("Drawing un-registered paint definition {}.", name);
|
|
return self.invalid_image(size_in_dpx, vec![]);
|
|
}
|
|
Some(definition) => {
|
|
// Step 5.1
|
|
if !definition.constructor_valid_flag.get() {
|
|
debug!("Drawing invalid paint definition {}.", name);
|
|
return self.invalid_image(size_in_dpx, vec![]);
|
|
}
|
|
class_constructor.set(definition.class_constructor.get());
|
|
paint_function.set(definition.paint_function.get());
|
|
DomRoot::from_ref(&*definition.context)
|
|
}
|
|
};
|
|
|
|
// Steps 5.2-5.4
|
|
// TODO: the spec requires calling the constructor now, but we might want to
|
|
// prepopulate the paint instance in `RegisterPaint`, to avoid calling it in
|
|
// the primary worklet thread.
|
|
// https://github.com/servo/servo/issues/17377
|
|
rooted!(in(cx) let mut paint_instance = UndefinedValue());
|
|
match self.paint_class_instances.borrow_mut().entry(name.clone()) {
|
|
Entry::Occupied(entry) => paint_instance.set(entry.get().get()),
|
|
Entry::Vacant(entry) => {
|
|
// Step 5.2-5.3
|
|
let args = HandleValueArray::new();
|
|
rooted!(in(cx) let mut result = null_mut());
|
|
unsafe { Construct1(cx, class_constructor.handle(), &args, result.handle_mut()); }
|
|
paint_instance.set(ObjectValue(result.get()));
|
|
if unsafe { JS_IsExceptionPending(cx) } {
|
|
debug!("Paint constructor threw an exception {}.", name);
|
|
unsafe { JS_ClearPendingException(cx); }
|
|
self.paint_definitions.borrow_mut().get_mut(name)
|
|
.expect("Vanishing paint definition.")
|
|
.constructor_valid_flag.set(false);
|
|
return self.invalid_image(size_in_dpx, vec![]);
|
|
}
|
|
// Step 5.4
|
|
entry.insert(Box::new(Heap::default())).set(paint_instance.get());
|
|
}
|
|
};
|
|
|
|
// TODO: Steps 6-7
|
|
// Step 8
|
|
// TODO: the spec requires creating a new paint rendering context each time,
|
|
// this code recycles the same one.
|
|
rendering_context.set_bitmap_dimensions(size_in_px, device_pixel_ratio);
|
|
|
|
// Step 9
|
|
let paint_size = PaintSize::new(self, size_in_px);
|
|
|
|
// TODO: Step 10
|
|
// Steps 11-12
|
|
debug!("Invoking paint function {}.", name);
|
|
rooted_vec!(let arguments_values <- arguments.iter().cloned()
|
|
.map(|argument| CSSStyleValue::new(self.upcast(), argument)));
|
|
let arguments_value_vec: Vec<JSVal> = arguments_values.iter()
|
|
.map(|argument| ObjectValue(argument.reflector().get_jsobject().get()))
|
|
.collect();
|
|
let arguments_value_array = unsafe { HandleValueArray::from_rooted_slice(&*arguments_value_vec) };
|
|
rooted!(in(cx) let argument_object = unsafe { JS_NewArrayObject(cx, &arguments_value_array) });
|
|
|
|
let args_slice = [
|
|
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) };
|
|
|
|
rooted!(in(cx) let mut result = UndefinedValue());
|
|
unsafe { Call(cx, paint_instance.handle(), paint_function.handle(), &args, result.handle_mut()); }
|
|
let missing_image_urls = rendering_context.take_missing_image_urls();
|
|
|
|
// Step 13.
|
|
if unsafe { JS_IsExceptionPending(cx) } {
|
|
debug!("Paint function threw an exception {}.", name);
|
|
unsafe { JS_ClearPendingException(cx); }
|
|
return self.invalid_image(size_in_dpx, missing_image_urls);
|
|
}
|
|
|
|
let (sender, receiver) = ipc::channel().expect("IPC channel creation.");
|
|
rendering_context.send_data(sender);
|
|
let image_key = match receiver.recv() {
|
|
Ok(data) => Some(data.image_key),
|
|
_ => None,
|
|
};
|
|
|
|
DrawAPaintImageResult {
|
|
width: size_in_dpx.width,
|
|
height: size_in_dpx.height,
|
|
format: PixelFormat::BGRA8,
|
|
image_key: image_key,
|
|
missing_image_urls: missing_image_urls,
|
|
}
|
|
}
|
|
|
|
// https://drafts.csswg.org/css-images-4/#invalid-image
|
|
fn invalid_image(&self, size: TypedSize2D<u32, DevicePixel>, missing_image_urls: Vec<ServoUrl>)
|
|
-> DrawAPaintImageResult {
|
|
debug!("Returning an invalid image.");
|
|
DrawAPaintImageResult {
|
|
width: size.width as u32,
|
|
height: size.height as u32,
|
|
format: PixelFormat::BGRA8,
|
|
image_key: None,
|
|
missing_image_urls: missing_image_urls,
|
|
}
|
|
}
|
|
|
|
fn painter(&self, name: Atom) -> Box<Painter> {
|
|
// Rather annoyingly we have to use a mutex here to make the painter Sync.
|
|
struct WorkletPainter {
|
|
name: Atom,
|
|
executor: Mutex<WorkletExecutor>,
|
|
}
|
|
impl SpeculativePainter for WorkletPainter {
|
|
fn speculatively_draw_a_paint_image(&self,
|
|
properties: Vec<(Atom, String)>,
|
|
arguments: Vec<String>) {
|
|
let name = self.name.clone();
|
|
let task = PaintWorkletTask::SpeculativelyDrawAPaintImage(name, properties, arguments);
|
|
self.executor.lock().expect("Locking a painter.")
|
|
.schedule_a_worklet_task(WorkletTask::Paint(task));
|
|
}
|
|
}
|
|
impl Painter for WorkletPainter {
|
|
fn draw_a_paint_image(&self,
|
|
size: TypedSize2D<f32, CSSPixel>,
|
|
device_pixel_ratio: TypedScale<f32, CSSPixel, DevicePixel>,
|
|
properties: Vec<(Atom, String)>,
|
|
arguments: Vec<String>)
|
|
-> Result<DrawAPaintImageResult, PaintWorkletError> {
|
|
let name = self.name.clone();
|
|
let (sender, receiver) = mpsc::channel();
|
|
let task = PaintWorkletTask::DrawAPaintImage(name,
|
|
size,
|
|
device_pixel_ratio,
|
|
properties,
|
|
arguments,
|
|
sender);
|
|
self.executor.lock().expect("Locking a painter.")
|
|
.schedule_a_worklet_task(WorkletTask::Paint(task));
|
|
|
|
let timeout = PREFS.get("dom.worklet.timeout_ms")
|
|
.as_u64()
|
|
.unwrap_or(10u64);
|
|
|
|
let timeout_duration = Duration::from_millis(timeout);
|
|
receiver.recv_timeout(timeout_duration)
|
|
.map_err(|e| PaintWorkletError::from(e))
|
|
}
|
|
}
|
|
Box::new(WorkletPainter {
|
|
name: name,
|
|
executor: Mutex::new(self.worklet_global.executor()),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Tasks which can be peformed by a paint worklet
|
|
pub enum PaintWorkletTask {
|
|
DrawAPaintImage(Atom,
|
|
TypedSize2D<f32, CSSPixel>,
|
|
TypedScale<f32, CSSPixel, DevicePixel>,
|
|
Vec<(Atom, String)>,
|
|
Vec<String>,
|
|
Sender<DrawAPaintImageResult>),
|
|
SpeculativelyDrawAPaintImage(Atom,
|
|
Vec<(Atom, String)>,
|
|
Vec<String>),
|
|
}
|
|
|
|
/// A paint definition
|
|
/// <https://drafts.css-houdini.org/css-paint-api/#paint-definition>
|
|
/// This type is dangerous, because it contains uboxed `Heap<JSVal>` values,
|
|
/// which can't be moved.
|
|
#[derive(JSTraceable, MallocSizeOf)]
|
|
#[must_root]
|
|
struct PaintDefinition {
|
|
class_constructor: Heap<JSVal>,
|
|
paint_function: Heap<JSVal>,
|
|
constructor_valid_flag: Cell<bool>,
|
|
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.
|
|
context: Dom<PaintRenderingContext2D>,
|
|
}
|
|
|
|
impl PaintDefinition {
|
|
fn new(class_constructor: HandleValue,
|
|
paint_function: HandleValue,
|
|
alpha: bool,
|
|
input_arguments_len: usize,
|
|
context: &PaintRenderingContext2D)
|
|
-> Box<PaintDefinition>
|
|
{
|
|
let result = Box::new(PaintDefinition {
|
|
class_constructor: Heap::default(),
|
|
paint_function: Heap::default(),
|
|
constructor_valid_flag: Cell::new(true),
|
|
context_alpha_flag: alpha,
|
|
input_arguments_len: input_arguments_len,
|
|
context: Dom::from_ref(context),
|
|
});
|
|
result.class_constructor.set(class_constructor.get());
|
|
result.paint_function.set(paint_function.get());
|
|
result
|
|
}
|
|
}
|
|
|
|
impl PaintWorkletGlobalScopeMethods for PaintWorkletGlobalScope {
|
|
#[allow(unsafe_code)]
|
|
#[allow(unrooted_must_root)]
|
|
/// <https://drafts.css-houdini.org/css-paint-api/#dom-paintworkletglobalscope-registerpaint>
|
|
fn RegisterPaint(&self, name: DOMString, paint_ctor: Rc<VoidFunction>) -> Fallible<()> {
|
|
let name = Atom::from(name);
|
|
let cx = self.worklet_global.get_cx();
|
|
rooted!(in(cx) let paint_obj = paint_ctor.callback_holder().get());
|
|
rooted!(in(cx) let paint_val = ObjectValue(paint_obj.get()));
|
|
|
|
debug!("Registering paint image name {}.", name);
|
|
|
|
// Step 1.
|
|
if name.is_empty() {
|
|
return Err(Error::Type(String::from("Empty paint name."))) ;
|
|
}
|
|
|
|
// Step 2-3.
|
|
if self.paint_definitions.borrow().contains_key(&name) {
|
|
return Err(Error::InvalidModification);
|
|
}
|
|
|
|
// Step 4-6.
|
|
let mut property_names: Vec<String> =
|
|
unsafe { get_property(cx, paint_obj.handle(), "inputProperties", ()) }?
|
|
.unwrap_or_default();
|
|
let properties = property_names.drain(..).map(Atom::from).collect();
|
|
|
|
// Step 7-9.
|
|
let input_arguments: Vec<String> =
|
|
unsafe { get_property(cx, paint_obj.handle(), "inputArguments", ()) }?
|
|
.unwrap_or_default();
|
|
|
|
// TODO: Steps 10-11.
|
|
|
|
// Steps 12-13.
|
|
let alpha: bool =
|
|
unsafe { get_property(cx, paint_obj.handle(), "alpha", ()) }?
|
|
.unwrap_or(true);
|
|
|
|
// Step 14
|
|
if unsafe { !IsConstructor(paint_obj.get()) } {
|
|
return Err(Error::Type(String::from("Not a constructor.")));
|
|
}
|
|
|
|
// Steps 15-16
|
|
rooted!(in(cx) let mut prototype = UndefinedValue());
|
|
unsafe { get_property_jsval(cx, paint_obj.handle(), "prototype", prototype.handle_mut())?; }
|
|
if !prototype.is_object() {
|
|
return Err(Error::Type(String::from("Prototype is not an object.")));
|
|
}
|
|
rooted!(in(cx) let prototype = prototype.to_object());
|
|
|
|
// Steps 17-18
|
|
rooted!(in(cx) let mut paint_function = UndefinedValue());
|
|
unsafe { get_property_jsval(cx, prototype.handle(), "paint", paint_function.handle_mut())?; }
|
|
if !paint_function.is_object() || unsafe { !IsCallable(paint_function.to_object()) } {
|
|
return Err(Error::Type(String::from("Paint function is not callable.")));
|
|
}
|
|
|
|
// Step 19.
|
|
let context = PaintRenderingContext2D::new(self);
|
|
let definition = PaintDefinition::new(paint_val.handle(),
|
|
paint_function.handle(),
|
|
alpha,
|
|
input_arguments.len(),
|
|
&*context);
|
|
|
|
// Step 20.
|
|
debug!("Registering definition {}.", name);
|
|
self.paint_definitions.borrow_mut().insert(name.clone(), definition);
|
|
|
|
// TODO: Step 21.
|
|
|
|
// Inform layout that there is a registered paint worklet.
|
|
// TODO: layout will end up getting this message multiple times.
|
|
let painter = self.painter(name.clone());
|
|
self.worklet_global.register_paint_worklet(name, properties, painter);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// This is a blocking sleep function available in the paint worklet
|
|
/// global scope behind the dom.worklet.enabled +
|
|
/// dom.worklet.blockingsleep.enabled prefs. It is to be used only for
|
|
/// testing, e.g., timeouts, where otherwise one would need busy waiting
|
|
/// to make sure a certain timeout is triggered.
|
|
/// check-tidy: no specs after this line
|
|
fn Sleep(&self, ms: u64) {
|
|
thread::sleep(Duration::from_millis(ms));
|
|
}
|
|
}
|