1use std::cell::Cell;
6use std::collections::hash_map::Entry;
7use std::ptr::null_mut;
8use std::rc::Rc;
9use std::sync::{Arc, Mutex};
10use std::thread;
11use std::time::Duration;
12
13use base::id::PipelineId;
14use crossbeam_channel::{Sender, unbounded};
15use dom_struct::dom_struct;
16use euclid::{Scale, Size2D};
17use js::jsapi::{
18 HandleValueArray, Heap, IsCallable, IsConstructor, JS_ClearPendingException,
19 JS_IsExceptionPending, JSAutoRealm, JSObject, NewArrayObject, Value,
20};
21use js::jsval::{JSVal, ObjectValue, UndefinedValue};
22use js::rust::HandleValue;
23use js::rust::wrappers::{Call, Construct1};
24use net_traits::image_cache::ImageCache;
25use pixels::PixelFormat;
26use script_traits::{DrawAPaintImageResult, PaintWorkletError, Painter};
27use servo_config::pref;
28use servo_url::ServoUrl;
29use style_traits::{CSSPixel, SpeculativePainter};
30use stylo_atoms::Atom;
31use webrender_api::units::DevicePixel;
32
33use super::bindings::trace::HashMapTracedValues;
34use crate::dom::bindings::callback::CallbackContainer;
35use crate::dom::bindings::cell::DomRefCell;
36use crate::dom::bindings::codegen::Bindings::PaintWorkletGlobalScopeBinding;
37use crate::dom::bindings::codegen::Bindings::PaintWorkletGlobalScopeBinding::PaintWorkletGlobalScopeMethods;
38use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction;
39use crate::dom::bindings::conversions::{get_property, get_property_jsval};
40use crate::dom::bindings::error::{Error, Fallible};
41use crate::dom::bindings::inheritance::Castable;
42use crate::dom::bindings::reflector::DomObject;
43use crate::dom::bindings::root::{Dom, DomRoot};
44use crate::dom::bindings::str::DOMString;
45use crate::dom::cssstylevalue::CSSStyleValue;
46use crate::dom::globalscope::GlobalScope;
47use crate::dom::paintrenderingcontext2d::PaintRenderingContext2D;
48use crate::dom::paintsize::PaintSize;
49use crate::dom::stylepropertymapreadonly::StylePropertyMapReadOnly;
50use crate::dom::worklet::WorkletExecutor;
51use crate::dom::workletglobalscope::{WorkletGlobalScope, WorkletGlobalScopeInit, WorkletTask};
52use crate::script_runtime::CanGc;
53
54#[dom_struct]
56pub(crate) struct PaintWorkletGlobalScope {
57 worklet_global: WorkletGlobalScope,
59 #[ignore_malloc_size_of = "Arc"]
61 #[no_trace]
62 image_cache: Arc<dyn ImageCache>,
63 paint_definitions: DomRefCell<HashMapTracedValues<Atom, Box<PaintDefinition>>>,
65 #[ignore_malloc_size_of = "mozjs"]
67 paint_class_instances: DomRefCell<HashMapTracedValues<Atom, Box<Heap<JSVal>>>>,
68 #[no_trace]
70 cached_name: DomRefCell<Atom>,
71 #[no_trace]
73 cached_size: Cell<Size2D<f32, CSSPixel>>,
74 #[no_trace]
76 cached_device_pixel_ratio: Cell<Scale<f32, CSSPixel, DevicePixel>>,
77 #[no_trace]
79 cached_properties: DomRefCell<Vec<(Atom, String)>>,
80 cached_arguments: DomRefCell<Vec<String>>,
82 #[no_trace]
84 cached_result: DomRefCell<DrawAPaintImageResult>,
85}
86
87impl PaintWorkletGlobalScope {
88 #[allow(unsafe_code)]
89 pub(crate) fn new(
90 pipeline_id: PipelineId,
91 base_url: ServoUrl,
92 executor: WorkletExecutor,
93 init: &WorkletGlobalScopeInit,
94 ) -> DomRoot<PaintWorkletGlobalScope> {
95 debug!(
96 "Creating paint worklet global scope for pipeline {}.",
97 pipeline_id
98 );
99 let global = Box::new(PaintWorkletGlobalScope {
100 worklet_global: WorkletGlobalScope::new_inherited(
101 pipeline_id,
102 base_url,
103 executor,
104 init,
105 ),
106 image_cache: init.image_cache.clone(),
107 paint_definitions: Default::default(),
108 paint_class_instances: Default::default(),
109 cached_name: DomRefCell::new(Atom::from("")),
110 cached_size: Cell::new(Size2D::zero()),
111 cached_device_pixel_ratio: Cell::new(Scale::new(1.0)),
112 cached_properties: Default::default(),
113 cached_arguments: Default::default(),
114 cached_result: DomRefCell::new(DrawAPaintImageResult {
115 width: 0,
116 height: 0,
117 format: PixelFormat::BGRA8,
118 image_key: None,
119 missing_image_urls: Vec::new(),
120 }),
121 });
122 PaintWorkletGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(GlobalScope::get_cx(), global)
123 }
124
125 pub(crate) fn image_cache(&self) -> Arc<dyn ImageCache> {
126 self.image_cache.clone()
127 }
128
129 pub(crate) fn perform_a_worklet_task(&self, task: PaintWorkletTask) {
130 match task {
131 PaintWorkletTask::DrawAPaintImage(
132 name,
133 size,
134 device_pixel_ratio,
135 properties,
136 arguments,
137 sender,
138 ) => {
139 let cache_hit = (*self.cached_name.borrow() == name) &&
140 (self.cached_size.get() == size) &&
141 (self.cached_device_pixel_ratio.get() == device_pixel_ratio) &&
142 (*self.cached_properties.borrow() == properties) &&
143 (*self.cached_arguments.borrow() == arguments);
144 let result = if cache_hit {
145 debug!("Cache hit on paint worklet {}!", name);
146 self.cached_result.borrow().clone()
147 } else {
148 debug!("Cache miss on paint worklet {}!", name);
149 let map = StylePropertyMapReadOnly::from_iter(
150 self.upcast(),
151 properties.iter().cloned(),
152 CanGc::note(),
153 );
154 let result =
155 self.draw_a_paint_image(&name, size, device_pixel_ratio, &map, &arguments);
156 if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) {
157 *self.cached_name.borrow_mut() = name;
158 self.cached_size.set(size);
159 self.cached_device_pixel_ratio.set(device_pixel_ratio);
160 *self.cached_properties.borrow_mut() = properties;
161 *self.cached_arguments.borrow_mut() = arguments;
162 *self.cached_result.borrow_mut() = result.clone();
163 }
164 result
165 };
166 let _ = sender.send(result);
167 },
168 PaintWorkletTask::SpeculativelyDrawAPaintImage(name, properties, arguments) => {
169 let should_speculate = (*self.cached_name.borrow() != name) ||
170 (*self.cached_properties.borrow() != properties) ||
171 (*self.cached_arguments.borrow() != arguments);
172 if should_speculate {
173 let size = self.cached_size.get();
174 let device_pixel_ratio = self.cached_device_pixel_ratio.get();
175 let map = StylePropertyMapReadOnly::from_iter(
176 self.upcast(),
177 properties.iter().cloned(),
178 CanGc::note(),
179 );
180 let result =
181 self.draw_a_paint_image(&name, size, device_pixel_ratio, &map, &arguments);
182 if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) {
183 *self.cached_name.borrow_mut() = name;
184 *self.cached_properties.borrow_mut() = properties;
185 *self.cached_arguments.borrow_mut() = arguments;
186 *self.cached_result.borrow_mut() = result;
187 }
188 }
189 },
190 }
191 }
192
193 fn draw_a_paint_image(
195 &self,
196 name: &Atom,
197 size_in_px: Size2D<f32, CSSPixel>,
198 device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
199 properties: &StylePropertyMapReadOnly,
200 arguments: &[String],
201 ) -> DrawAPaintImageResult {
202 let size_in_dpx = size_in_px * device_pixel_ratio;
203 let size_in_dpx = Size2D::new(
204 size_in_dpx.width.abs() as u32,
205 size_in_dpx.height.abs() as u32,
206 );
207
208 self.invoke_a_paint_callback(
212 name,
213 size_in_px,
214 size_in_dpx,
215 device_pixel_ratio,
216 properties,
217 arguments,
218 CanGc::note(),
219 )
220 }
221
222 #[allow(clippy::too_many_arguments)]
224 #[allow(unsafe_code)]
225 fn invoke_a_paint_callback(
226 &self,
227 name: &Atom,
228 size_in_px: Size2D<f32, CSSPixel>,
229 size_in_dpx: Size2D<u32, DevicePixel>,
230 device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
231 properties: &StylePropertyMapReadOnly,
232 arguments: &[String],
233 can_gc: CanGc,
234 ) -> DrawAPaintImageResult {
235 debug!(
236 "Invoking a paint callback {}({},{}) at {:?}.",
237 name, size_in_px.width, size_in_px.height, device_pixel_ratio
238 );
239
240 let cx = WorkletGlobalScope::get_cx();
241 let _ac = JSAutoRealm::new(*cx, self.worklet_global.reflector().get_jsobject().get());
242
243 rooted!(in(*cx) let mut class_constructor = UndefinedValue());
246 rooted!(in(*cx) let mut paint_function = UndefinedValue());
247 let rendering_context = match self.paint_definitions.borrow().get(name) {
248 None => {
249 warn!("Drawing un-registered paint definition {}.", name);
251 return self.invalid_image(size_in_dpx, vec![]);
252 },
253 Some(definition) => {
254 if !definition.constructor_valid_flag.get() {
256 debug!("Drawing invalid paint definition {}.", name);
257 return self.invalid_image(size_in_dpx, vec![]);
258 }
259 class_constructor.set(definition.class_constructor.get());
260 paint_function.set(definition.paint_function.get());
261 DomRoot::from_ref(&*definition.context)
262 },
263 };
264
265 rooted!(in(*cx) let mut paint_instance = UndefinedValue());
271 match self.paint_class_instances.borrow_mut().entry(name.clone()) {
272 Entry::Occupied(entry) => paint_instance.set(entry.get().get()),
273 Entry::Vacant(entry) => {
274 let args = HandleValueArray::empty();
276 rooted!(in(*cx) let mut result = null_mut::<JSObject>());
277 unsafe {
278 Construct1(*cx, class_constructor.handle(), &args, result.handle_mut());
279 }
280 paint_instance.set(ObjectValue(result.get()));
281 if unsafe { JS_IsExceptionPending(*cx) } {
282 debug!("Paint constructor threw an exception {}.", name);
283 unsafe {
284 JS_ClearPendingException(*cx);
285 }
286 self.paint_definitions
287 .borrow_mut()
288 .get_mut(name)
289 .expect("Vanishing paint definition.")
290 .constructor_valid_flag
291 .set(false);
292 return self.invalid_image(size_in_dpx, vec![]);
293 }
294 entry
296 .insert(Box::<Heap<Value>>::default())
297 .set(paint_instance.get());
298 },
299 };
300
301 rendering_context.set_bitmap_dimensions(size_in_px, device_pixel_ratio);
306
307 let paint_size = PaintSize::new(self, size_in_px, can_gc);
309
310 debug!("Invoking paint function {}.", name);
313 rooted_vec!(let mut arguments_values);
314 for argument in arguments {
315 let style_value = CSSStyleValue::new(self.upcast(), argument.clone(), can_gc);
316 arguments_values.push(ObjectValue(style_value.reflector().get_jsobject().get()));
317 }
318 let arguments_value_array = HandleValueArray::from(&arguments_values);
319 rooted!(in(*cx) let argument_object = unsafe { NewArrayObject(*cx, &arguments_value_array) });
320
321 rooted_vec!(let mut callback_args);
322 callback_args.push(ObjectValue(
323 rendering_context.reflector().get_jsobject().get(),
324 ));
325 callback_args.push(ObjectValue(paint_size.reflector().get_jsobject().get()));
326 callback_args.push(ObjectValue(properties.reflector().get_jsobject().get()));
327 callback_args.push(ObjectValue(argument_object.get()));
328 let args = HandleValueArray::from(&callback_args);
329
330 rooted!(in(*cx) let mut result = UndefinedValue());
331 unsafe {
332 Call(
333 *cx,
334 paint_instance.handle(),
335 paint_function.handle(),
336 &args,
337 result.handle_mut(),
338 );
339 }
340 let missing_image_urls = rendering_context.take_missing_image_urls();
341
342 if unsafe { JS_IsExceptionPending(*cx) } {
344 debug!("Paint function threw an exception {}.", name);
345 unsafe {
346 JS_ClearPendingException(*cx);
347 }
348 return self.invalid_image(size_in_dpx, missing_image_urls);
349 }
350
351 rendering_context.update_rendering();
352
353 DrawAPaintImageResult {
354 width: size_in_dpx.width,
355 height: size_in_dpx.height,
356 format: PixelFormat::BGRA8,
357 image_key: Some(rendering_context.image_key()),
358 missing_image_urls,
359 }
360 }
361
362 fn invalid_image(
364 &self,
365 size: Size2D<u32, DevicePixel>,
366 missing_image_urls: Vec<ServoUrl>,
367 ) -> DrawAPaintImageResult {
368 debug!("Returning an invalid image.");
369 DrawAPaintImageResult {
370 width: size.width,
371 height: size.height,
372 format: PixelFormat::BGRA8,
373 image_key: None,
374 missing_image_urls,
375 }
376 }
377
378 fn painter(&self, name: Atom) -> Box<dyn Painter> {
379 struct WorkletPainter {
381 name: Atom,
382 executor: Mutex<WorkletExecutor>,
383 }
384 impl SpeculativePainter for WorkletPainter {
385 fn speculatively_draw_a_paint_image(
386 &self,
387 properties: Vec<(Atom, String)>,
388 arguments: Vec<String>,
389 ) {
390 let name = self.name.clone();
391 let task =
392 PaintWorkletTask::SpeculativelyDrawAPaintImage(name, properties, arguments);
393 self.executor
394 .lock()
395 .expect("Locking a painter.")
396 .schedule_a_worklet_task(WorkletTask::Paint(task));
397 }
398 }
399 impl Painter for WorkletPainter {
400 fn draw_a_paint_image(
401 &self,
402 size: Size2D<f32, CSSPixel>,
403 device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
404 properties: Vec<(Atom, String)>,
405 arguments: Vec<String>,
406 ) -> Result<DrawAPaintImageResult, PaintWorkletError> {
407 let name = self.name.clone();
408 let (sender, receiver) = unbounded();
409 let task = PaintWorkletTask::DrawAPaintImage(
410 name,
411 size,
412 device_pixel_ratio,
413 properties,
414 arguments,
415 sender,
416 );
417 self.executor
418 .lock()
419 .expect("Locking a painter.")
420 .schedule_a_worklet_task(WorkletTask::Paint(task));
421
422 let timeout = pref!(dom_worklet_timeout_ms) as u64;
423
424 receiver
425 .recv_timeout(Duration::from_millis(timeout))
426 .map_err(PaintWorkletError::from)
427 }
428 }
429 Box::new(WorkletPainter {
430 name,
431 executor: Mutex::new(self.worklet_global.executor()),
432 })
433 }
434}
435
436pub(crate) enum PaintWorkletTask {
438 DrawAPaintImage(
439 Atom,
440 Size2D<f32, CSSPixel>,
441 Scale<f32, CSSPixel, DevicePixel>,
442 Vec<(Atom, String)>,
443 Vec<String>,
444 Sender<DrawAPaintImageResult>,
445 ),
446 SpeculativelyDrawAPaintImage(Atom, Vec<(Atom, String)>, Vec<String>),
447}
448
449#[derive(JSTraceable, MallocSizeOf)]
454#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
455struct PaintDefinition {
456 #[ignore_malloc_size_of = "mozjs"]
457 class_constructor: Heap<JSVal>,
458 #[ignore_malloc_size_of = "mozjs"]
459 paint_function: Heap<JSVal>,
460 constructor_valid_flag: Cell<bool>,
461 context_alpha_flag: bool,
462 input_arguments_len: usize,
464 context: Dom<PaintRenderingContext2D>,
468}
469
470impl PaintDefinition {
471 fn new(
472 class_constructor: HandleValue,
473 paint_function: HandleValue,
474 alpha: bool,
475 input_arguments_len: usize,
476 context: &PaintRenderingContext2D,
477 ) -> Box<PaintDefinition> {
478 let result = Box::new(PaintDefinition {
479 class_constructor: Heap::default(),
480 paint_function: Heap::default(),
481 constructor_valid_flag: Cell::new(true),
482 context_alpha_flag: alpha,
483 input_arguments_len,
484 context: Dom::from_ref(context),
485 });
486 result.class_constructor.set(class_constructor.get());
487 result.paint_function.set(paint_function.get());
488 result
489 }
490}
491
492impl PaintWorkletGlobalScopeMethods<crate::DomTypeHolder> for PaintWorkletGlobalScope {
493 #[allow(unsafe_code)]
494 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
495 fn RegisterPaint(&self, name: DOMString, paint_ctor: Rc<VoidFunction>) -> Fallible<()> {
497 let name = Atom::from(name);
498 let cx = WorkletGlobalScope::get_cx();
499 rooted!(in(*cx) let paint_obj = paint_ctor.callback_holder().get());
500 rooted!(in(*cx) let paint_val = ObjectValue(paint_obj.get()));
501
502 debug!("Registering paint image name {}.", name);
503
504 if name.is_empty() {
506 return Err(Error::Type(String::from("Empty paint name.")));
507 }
508
509 if self.paint_definitions.borrow().contains_key(&name) {
511 return Err(Error::InvalidModification);
512 }
513
514 let mut property_names: Vec<String> =
516 unsafe { get_property(*cx, paint_obj.handle(), "inputProperties", ()) }?
517 .unwrap_or_default();
518 let properties = property_names.drain(..).map(Atom::from).collect();
519
520 let input_arguments: Vec<String> =
522 unsafe { get_property(*cx, paint_obj.handle(), "inputArguments", ()) }?
523 .unwrap_or_default();
524
525 let alpha: bool =
529 unsafe { get_property(*cx, paint_obj.handle(), "alpha", ()) }?.unwrap_or(true);
530
531 if unsafe { !IsConstructor(paint_obj.get()) } {
533 return Err(Error::Type(String::from("Not a constructor.")));
534 }
535
536 rooted!(in(*cx) let mut prototype = UndefinedValue());
538 unsafe {
539 get_property_jsval(*cx, paint_obj.handle(), "prototype", prototype.handle_mut())?;
540 }
541 if !prototype.is_object() {
542 return Err(Error::Type(String::from("Prototype is not an object.")));
543 }
544 rooted!(in(*cx) let prototype = prototype.to_object());
545
546 rooted!(in(*cx) let mut paint_function = UndefinedValue());
548 unsafe {
549 get_property_jsval(
550 *cx,
551 prototype.handle(),
552 "paint",
553 paint_function.handle_mut(),
554 )?;
555 }
556 if !paint_function.is_object() || unsafe { !IsCallable(paint_function.to_object()) } {
557 return Err(Error::Type(String::from("Paint function is not callable.")));
558 }
559
560 let Some(context) = PaintRenderingContext2D::new(self, CanGc::note()) else {
562 return Err(Error::Operation);
563 };
564 let definition = PaintDefinition::new(
565 paint_val.handle(),
566 paint_function.handle(),
567 alpha,
568 input_arguments.len(),
569 &context,
570 );
571
572 debug!("Registering definition {}.", name);
574 self.paint_definitions
575 .borrow_mut()
576 .insert(name.clone(), definition);
577
578 let painter = self.painter(name.clone());
583 self.worklet_global
584 .register_paint_worklet(name, properties, painter);
585
586 Ok(())
587 }
588
589 fn Sleep(&self, ms: u64) {
596 thread::sleep(Duration::from_millis(ms));
597 }
598}