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