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, WebViewId};
14use crossbeam_channel::{Sender, unbounded};
15use dom_struct::dom_struct;
16use euclid::{Scale, Size2D};
17use js::context::JSContext;
18use js::jsapi::{
19 HandleValueArray, Heap, IsCallable, IsConstructor, JS_ClearPendingException,
20 JS_IsExceptionPending, JSAutoRealm, JSObject, NewArrayObject, Value,
21};
22use js::jsval::{JSVal, ObjectValue, UndefinedValue};
23use js::rust::HandleValue;
24use js::rust::wrappers::{Call, Construct1};
25use net_traits::image_cache::ImageCache;
26use pixels::PixelFormat;
27use script_traits::{DrawAPaintImageResult, PaintWorkletError, Painter};
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, 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::note(),
157 );
158 let result =
159 self.draw_a_paint_image(&name, size, device_pixel_ratio, &map, &arguments);
160 if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) {
161 *self.cached_name.borrow_mut() = name;
162 self.cached_size.set(size);
163 self.cached_device_pixel_ratio.set(device_pixel_ratio);
164 *self.cached_properties.borrow_mut() = properties;
165 *self.cached_arguments.borrow_mut() = arguments;
166 *self.cached_result.borrow_mut() = result.clone();
167 }
168 result
169 };
170 let _ = sender.send(result);
171 },
172 PaintWorkletTask::SpeculativelyDrawAPaintImage(name, properties, arguments) => {
173 let should_speculate = (*self.cached_name.borrow() != name) ||
174 (*self.cached_properties.borrow() != properties) ||
175 (*self.cached_arguments.borrow() != arguments);
176 if should_speculate {
177 let size = self.cached_size.get();
178 let device_pixel_ratio = self.cached_device_pixel_ratio.get();
179 let map = StylePropertyMapReadOnly::from_iter(
180 self.upcast(),
181 properties.iter().cloned(),
182 CanGc::note(),
183 );
184 let result =
185 self.draw_a_paint_image(&name, size, device_pixel_ratio, &map, &arguments);
186 if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) {
187 *self.cached_name.borrow_mut() = name;
188 *self.cached_properties.borrow_mut() = properties;
189 *self.cached_arguments.borrow_mut() = arguments;
190 *self.cached_result.borrow_mut() = result;
191 }
192 }
193 },
194 }
195 }
196
197 fn draw_a_paint_image(
199 &self,
200 name: &Atom,
201 size_in_px: Size2D<f32, CSSPixel>,
202 device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
203 properties: &StylePropertyMapReadOnly,
204 arguments: &[String],
205 ) -> DrawAPaintImageResult {
206 let size_in_dpx = size_in_px * device_pixel_ratio;
207 let size_in_dpx = Size2D::new(
208 size_in_dpx.width.abs() as u32,
209 size_in_dpx.height.abs() as u32,
210 );
211
212 self.invoke_a_paint_callback(
216 name,
217 size_in_px,
218 size_in_dpx,
219 device_pixel_ratio,
220 properties,
221 arguments,
222 CanGc::note(),
223 )
224 }
225
226 #[expect(clippy::too_many_arguments)]
228 #[expect(unsafe_code)]
229 fn invoke_a_paint_callback(
230 &self,
231 name: &Atom,
232 size_in_px: Size2D<f32, CSSPixel>,
233 size_in_dpx: Size2D<u32, DevicePixel>,
234 device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
235 properties: &StylePropertyMapReadOnly,
236 arguments: &[String],
237 can_gc: CanGc,
238 ) -> DrawAPaintImageResult {
239 debug!(
240 "Invoking a paint callback {}({},{}) at {:?}.",
241 name, size_in_px.width, size_in_px.height, device_pixel_ratio
242 );
243
244 let cx = WorkletGlobalScope::get_cx();
245 let _ac = JSAutoRealm::new(*cx, self.worklet_global.reflector().get_jsobject().get());
246
247 rooted!(in(*cx) let mut class_constructor = UndefinedValue());
250 rooted!(in(*cx) let mut paint_function = UndefinedValue());
251 let rendering_context = match self.paint_definitions.borrow().get(name) {
252 None => {
253 warn!("Drawing un-registered paint definition {}.", name);
255 return self.invalid_image(size_in_dpx, vec![]);
256 },
257 Some(definition) => {
258 if !definition.constructor_valid_flag.get() {
260 debug!("Drawing invalid paint definition {}.", name);
261 return self.invalid_image(size_in_dpx, vec![]);
262 }
263 class_constructor.set(definition.class_constructor.get());
264 paint_function.set(definition.paint_function.get());
265 DomRoot::from_ref(&*definition.context)
266 },
267 };
268
269 rooted!(in(*cx) let mut paint_instance = UndefinedValue());
275 match self.paint_class_instances.borrow_mut().entry(name.clone()) {
276 Entry::Occupied(entry) => paint_instance.set(entry.get().get()),
277 Entry::Vacant(entry) => {
278 let args = HandleValueArray::empty();
280 rooted!(in(*cx) let mut result = null_mut::<JSObject>());
281 unsafe {
282 Construct1(*cx, class_constructor.handle(), &args, result.handle_mut());
283 }
284 paint_instance.set(ObjectValue(result.get()));
285 if unsafe { JS_IsExceptionPending(*cx) } {
286 debug!("Paint constructor threw an exception {}.", name);
287 unsafe {
288 JS_ClearPendingException(*cx);
289 }
290 self.paint_definitions
291 .borrow_mut()
292 .get_mut(name)
293 .expect("Vanishing paint definition.")
294 .constructor_valid_flag
295 .set(false);
296 return self.invalid_image(size_in_dpx, vec![]);
297 }
298 entry
300 .insert(Box::<Heap<Value>>::default())
301 .set(paint_instance.get());
302 },
303 };
304
305 rendering_context.set_bitmap_dimensions(size_in_px, device_pixel_ratio);
310
311 let paint_size = PaintSize::new(self, size_in_px, can_gc);
313
314 debug!("Invoking paint function {}.", name);
317 rooted_vec!(let mut arguments_values);
318 for argument in arguments {
319 let style_value = CSSStyleValue::new(self.upcast(), argument.clone(), can_gc);
320 arguments_values.push(ObjectValue(style_value.reflector().get_jsobject().get()));
321 }
322 let arguments_value_array = HandleValueArray::from(&arguments_values);
323 rooted!(in(*cx) let argument_object = unsafe { NewArrayObject(*cx, &arguments_value_array) });
324
325 rooted_vec!(let mut callback_args);
326 callback_args.push(ObjectValue(
327 rendering_context.reflector().get_jsobject().get(),
328 ));
329 callback_args.push(ObjectValue(paint_size.reflector().get_jsobject().get()));
330 callback_args.push(ObjectValue(properties.reflector().get_jsobject().get()));
331 callback_args.push(ObjectValue(argument_object.get()));
332 let args = HandleValueArray::from(&callback_args);
333
334 rooted!(in(*cx) let mut result = UndefinedValue());
335 unsafe {
336 Call(
337 *cx,
338 paint_instance.handle(),
339 paint_function.handle(),
340 &args,
341 result.handle_mut(),
342 );
343 }
344 let missing_image_urls = rendering_context.take_missing_image_urls();
345
346 if unsafe { JS_IsExceptionPending(*cx) } {
348 debug!("Paint function threw an exception {}.", name);
349 unsafe {
350 JS_ClearPendingException(*cx);
351 }
352 return self.invalid_image(size_in_dpx, missing_image_urls);
353 }
354
355 rendering_context.update_rendering();
356
357 DrawAPaintImageResult {
358 width: size_in_dpx.width,
359 height: size_in_dpx.height,
360 format: PixelFormat::BGRA8,
361 image_key: Some(rendering_context.image_key()),
362 missing_image_urls,
363 }
364 }
365
366 fn invalid_image(
368 &self,
369 size: Size2D<u32, DevicePixel>,
370 missing_image_urls: Vec<ServoUrl>,
371 ) -> DrawAPaintImageResult {
372 debug!("Returning an invalid image.");
373 DrawAPaintImageResult {
374 width: size.width,
375 height: size.height,
376 format: PixelFormat::BGRA8,
377 image_key: None,
378 missing_image_urls,
379 }
380 }
381
382 fn painter(&self, name: Atom) -> Box<dyn Painter> {
383 struct WorkletPainter {
385 name: Atom,
386 executor: Mutex<WorkletExecutor>,
387 }
388 impl SpeculativePainter for WorkletPainter {
389 fn speculatively_draw_a_paint_image(
390 &self,
391 properties: Vec<(Atom, String)>,
392 arguments: Vec<String>,
393 ) {
394 let name = self.name.clone();
395 let task =
396 PaintWorkletTask::SpeculativelyDrawAPaintImage(name, properties, arguments);
397 self.executor
398 .lock()
399 .expect("Locking a painter.")
400 .schedule_a_worklet_task(WorkletTask::Paint(task));
401 }
402 }
403 impl Painter for WorkletPainter {
404 fn draw_a_paint_image(
405 &self,
406 size: Size2D<f32, CSSPixel>,
407 device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
408 properties: Vec<(Atom, String)>,
409 arguments: Vec<String>,
410 ) -> Result<DrawAPaintImageResult, PaintWorkletError> {
411 let name = self.name.clone();
412 let (sender, receiver) = unbounded();
413 let task = PaintWorkletTask::DrawAPaintImage(
414 name,
415 size,
416 device_pixel_ratio,
417 properties,
418 arguments,
419 sender,
420 );
421 self.executor
422 .lock()
423 .expect("Locking a painter.")
424 .schedule_a_worklet_task(WorkletTask::Paint(task));
425
426 let timeout = pref!(dom_worklet_timeout_ms) as u64;
427
428 receiver
429 .recv_timeout(Duration::from_millis(timeout))
430 .map_err(PaintWorkletError::from)
431 }
432 }
433 Box::new(WorkletPainter {
434 name,
435 executor: Mutex::new(self.worklet_global.executor()),
436 })
437 }
438}
439
440pub(crate) enum PaintWorkletTask {
442 DrawAPaintImage(
443 Atom,
444 Size2D<f32, CSSPixel>,
445 Scale<f32, CSSPixel, DevicePixel>,
446 Vec<(Atom, String)>,
447 Vec<String>,
448 Sender<DrawAPaintImageResult>,
449 ),
450 SpeculativelyDrawAPaintImage(Atom, Vec<(Atom, String)>, Vec<String>),
451}
452
453#[derive(JSTraceable, MallocSizeOf)]
458#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
459struct PaintDefinition {
460 #[ignore_malloc_size_of = "mozjs"]
461 class_constructor: Heap<JSVal>,
462 #[ignore_malloc_size_of = "mozjs"]
463 paint_function: Heap<JSVal>,
464 constructor_valid_flag: Cell<bool>,
465 context_alpha_flag: bool,
466 input_arguments_len: usize,
468 context: Dom<PaintRenderingContext2D>,
472}
473
474impl PaintDefinition {
475 fn new(
476 class_constructor: HandleValue,
477 paint_function: HandleValue,
478 alpha: bool,
479 input_arguments_len: usize,
480 context: &PaintRenderingContext2D,
481 ) -> Box<PaintDefinition> {
482 let result = Box::new(PaintDefinition {
483 class_constructor: Heap::default(),
484 paint_function: Heap::default(),
485 constructor_valid_flag: Cell::new(true),
486 context_alpha_flag: alpha,
487 input_arguments_len,
488 context: Dom::from_ref(context),
489 });
490 result.class_constructor.set(class_constructor.get());
491 result.paint_function.set(paint_function.get());
492 result
493 }
494}
495
496impl PaintWorkletGlobalScopeMethods<crate::DomTypeHolder> for PaintWorkletGlobalScope {
497 #[expect(unsafe_code)]
498 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
499 fn RegisterPaint(&self, name: DOMString, paint_ctor: Rc<VoidFunction>) -> Fallible<()> {
501 let name = Atom::from(name);
502 let cx = WorkletGlobalScope::get_cx();
503 rooted!(in(*cx) let paint_obj = paint_ctor.callback_holder().get());
504 rooted!(in(*cx) let paint_val = ObjectValue(paint_obj.get()));
505
506 debug!("Registering paint image name {}.", name);
507
508 if name.is_empty() {
510 return Err(Error::Type(c"Empty paint name.".to_owned()));
511 }
512
513 if self.paint_definitions.borrow().contains_key(&name) {
515 return Err(Error::InvalidModification(None));
516 }
517
518 let mut property_names: Vec<String> =
520 get_property(cx, paint_obj.handle(), c"inputProperties", ())?.unwrap_or_default();
521 let properties = property_names.drain(..).map(Atom::from).collect();
522
523 let input_arguments: Vec<String> =
525 get_property(cx, paint_obj.handle(), c"inputArguments", ())?.unwrap_or_default();
526
527 let alpha: bool = get_property(cx, paint_obj.handle(), c"alpha", ())?.unwrap_or(true);
531
532 if unsafe { !IsConstructor(paint_obj.get()) } {
534 return Err(Error::Type(c"Not a constructor.".to_owned()));
535 }
536
537 rooted!(in(*cx) let mut prototype = UndefinedValue());
539 get_property_jsval(cx, paint_obj.handle(), c"prototype", prototype.handle_mut())?;
540 if !prototype.is_object() {
541 return Err(Error::Type(c"Prototype is not an object.".to_owned()));
542 }
543 rooted!(in(*cx) let prototype = prototype.to_object());
544
545 rooted!(in(*cx) let mut paint_function = UndefinedValue());
547 get_property_jsval(
548 cx,
549 prototype.handle(),
550 c"paint",
551 paint_function.handle_mut(),
552 )?;
553 if !paint_function.is_object() || unsafe { !IsCallable(paint_function.to_object()) } {
554 return Err(Error::Type(c"Paint function is not callable.".to_owned()));
555 }
556
557 let Some(context) = PaintRenderingContext2D::new(self, CanGc::note()) else {
559 return Err(Error::Operation(None));
560 };
561 let definition = PaintDefinition::new(
562 paint_val.handle(),
563 paint_function.handle(),
564 alpha,
565 input_arguments.len(),
566 &context,
567 );
568
569 debug!("Registering definition {}.", name);
571 self.paint_definitions
572 .borrow_mut()
573 .insert(name.clone(), definition);
574
575 let painter = self.painter(name.clone());
580 self.worklet_global
581 .register_paint_worklet(name, properties, painter);
582
583 Ok(())
584 }
585
586 fn Sleep(&self, ms: u64) {
593 thread::sleep(Duration::from_millis(ms));
594 }
595}