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