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::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::css::cssstylevalue::CSSStyleValue;
46use crate::dom::css::stylepropertymapreadonly::StylePropertyMapReadOnly;
47use crate::dom::globalscope::GlobalScope;
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 ) -> 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 webview_id,
103 pipeline_id,
104 base_url,
105 inherited_secure_context,
106 executor,
107 init,
108 ),
109 image_cache: init.image_cache.clone(),
110 paint_definitions: Default::default(),
111 paint_class_instances: Default::default(),
112 cached_name: DomRefCell::new(Atom::from("")),
113 cached_size: Cell::new(Size2D::zero()),
114 cached_device_pixel_ratio: Cell::new(Scale::new(1.0)),
115 cached_properties: Default::default(),
116 cached_arguments: Default::default(),
117 cached_result: DomRefCell::new(DrawAPaintImageResult {
118 width: 0,
119 height: 0,
120 format: PixelFormat::BGRA8,
121 image_key: None,
122 missing_image_urls: Vec::new(),
123 }),
124 });
125 PaintWorkletGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(GlobalScope::get_cx(), global)
126 }
127
128 pub(crate) fn image_cache(&self) -> Arc<dyn ImageCache> {
129 self.image_cache.clone()
130 }
131
132 pub(crate) fn perform_a_worklet_task(&self, task: PaintWorkletTask) {
133 match task {
134 PaintWorkletTask::DrawAPaintImage(
135 name,
136 size,
137 device_pixel_ratio,
138 properties,
139 arguments,
140 sender,
141 ) => {
142 let cache_hit = (*self.cached_name.borrow() == name) &&
143 (self.cached_size.get() == size) &&
144 (self.cached_device_pixel_ratio.get() == device_pixel_ratio) &&
145 (*self.cached_properties.borrow() == properties) &&
146 (*self.cached_arguments.borrow() == arguments);
147 let result = if cache_hit {
148 debug!("Cache hit on paint worklet {}!", name);
149 self.cached_result.borrow().clone()
150 } else {
151 debug!("Cache miss on paint worklet {}!", name);
152 let map = StylePropertyMapReadOnly::from_iter(
153 self.upcast(),
154 properties.iter().cloned(),
155 CanGc::note(),
156 );
157 let result =
158 self.draw_a_paint_image(&name, size, device_pixel_ratio, &map, &arguments);
159 if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) {
160 *self.cached_name.borrow_mut() = name;
161 self.cached_size.set(size);
162 self.cached_device_pixel_ratio.set(device_pixel_ratio);
163 *self.cached_properties.borrow_mut() = properties;
164 *self.cached_arguments.borrow_mut() = arguments;
165 *self.cached_result.borrow_mut() = result.clone();
166 }
167 result
168 };
169 let _ = sender.send(result);
170 },
171 PaintWorkletTask::SpeculativelyDrawAPaintImage(name, properties, arguments) => {
172 let should_speculate = (*self.cached_name.borrow() != name) ||
173 (*self.cached_properties.borrow() != properties) ||
174 (*self.cached_arguments.borrow() != arguments);
175 if should_speculate {
176 let size = self.cached_size.get();
177 let device_pixel_ratio = self.cached_device_pixel_ratio.get();
178 let map = StylePropertyMapReadOnly::from_iter(
179 self.upcast(),
180 properties.iter().cloned(),
181 CanGc::note(),
182 );
183 let result =
184 self.draw_a_paint_image(&name, size, device_pixel_ratio, &map, &arguments);
185 if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) {
186 *self.cached_name.borrow_mut() = name;
187 *self.cached_properties.borrow_mut() = properties;
188 *self.cached_arguments.borrow_mut() = arguments;
189 *self.cached_result.borrow_mut() = result;
190 }
191 }
192 },
193 }
194 }
195
196 fn draw_a_paint_image(
198 &self,
199 name: &Atom,
200 size_in_px: Size2D<f32, CSSPixel>,
201 device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
202 properties: &StylePropertyMapReadOnly,
203 arguments: &[String],
204 ) -> DrawAPaintImageResult {
205 let size_in_dpx = size_in_px * device_pixel_ratio;
206 let size_in_dpx = Size2D::new(
207 size_in_dpx.width.abs() as u32,
208 size_in_dpx.height.abs() as u32,
209 );
210
211 self.invoke_a_paint_callback(
215 name,
216 size_in_px,
217 size_in_dpx,
218 device_pixel_ratio,
219 properties,
220 arguments,
221 CanGc::note(),
222 )
223 }
224
225 #[expect(clippy::too_many_arguments)]
227 #[expect(unsafe_code)]
228 fn invoke_a_paint_callback(
229 &self,
230 name: &Atom,
231 size_in_px: Size2D<f32, CSSPixel>,
232 size_in_dpx: Size2D<u32, DevicePixel>,
233 device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
234 properties: &StylePropertyMapReadOnly,
235 arguments: &[String],
236 can_gc: CanGc,
237 ) -> DrawAPaintImageResult {
238 debug!(
239 "Invoking a paint callback {}({},{}) at {:?}.",
240 name, size_in_px.width, size_in_px.height, device_pixel_ratio
241 );
242
243 let cx = WorkletGlobalScope::get_cx();
244 let _ac = JSAutoRealm::new(*cx, self.worklet_global.reflector().get_jsobject().get());
245
246 rooted!(in(*cx) let mut class_constructor = UndefinedValue());
249 rooted!(in(*cx) let mut paint_function = UndefinedValue());
250 let rendering_context = match self.paint_definitions.borrow().get(name) {
251 None => {
252 warn!("Drawing un-registered paint definition {}.", name);
254 return self.invalid_image(size_in_dpx, vec![]);
255 },
256 Some(definition) => {
257 if !definition.constructor_valid_flag.get() {
259 debug!("Drawing invalid paint definition {}.", name);
260 return self.invalid_image(size_in_dpx, vec![]);
261 }
262 class_constructor.set(definition.class_constructor.get());
263 paint_function.set(definition.paint_function.get());
264 DomRoot::from_ref(&*definition.context)
265 },
266 };
267
268 rooted!(in(*cx) let mut paint_instance = UndefinedValue());
274 match self.paint_class_instances.borrow_mut().entry(name.clone()) {
275 Entry::Occupied(entry) => paint_instance.set(entry.get().get()),
276 Entry::Vacant(entry) => {
277 let args = HandleValueArray::empty();
279 rooted!(in(*cx) let mut result = null_mut::<JSObject>());
280 unsafe {
281 Construct1(*cx, class_constructor.handle(), &args, result.handle_mut());
282 }
283 paint_instance.set(ObjectValue(result.get()));
284 if unsafe { JS_IsExceptionPending(*cx) } {
285 debug!("Paint constructor threw an exception {}.", name);
286 unsafe {
287 JS_ClearPendingException(*cx);
288 }
289 self.paint_definitions
290 .borrow_mut()
291 .get_mut(name)
292 .expect("Vanishing paint definition.")
293 .constructor_valid_flag
294 .set(false);
295 return self.invalid_image(size_in_dpx, vec![]);
296 }
297 entry
299 .insert(Box::<Heap<Value>>::default())
300 .set(paint_instance.get());
301 },
302 };
303
304 rendering_context.set_bitmap_dimensions(size_in_px, device_pixel_ratio);
309
310 let paint_size = PaintSize::new(self, size_in_px, can_gc);
312
313 debug!("Invoking paint function {}.", name);
316 rooted_vec!(let mut arguments_values);
317 for argument in arguments {
318 let style_value = CSSStyleValue::new(self.upcast(), argument.clone(), can_gc);
319 arguments_values.push(ObjectValue(style_value.reflector().get_jsobject().get()));
320 }
321 let arguments_value_array = HandleValueArray::from(&arguments_values);
322 rooted!(in(*cx) let argument_object = unsafe { NewArrayObject(*cx, &arguments_value_array) });
323
324 rooted_vec!(let mut callback_args);
325 callback_args.push(ObjectValue(
326 rendering_context.reflector().get_jsobject().get(),
327 ));
328 callback_args.push(ObjectValue(paint_size.reflector().get_jsobject().get()));
329 callback_args.push(ObjectValue(properties.reflector().get_jsobject().get()));
330 callback_args.push(ObjectValue(argument_object.get()));
331 let args = HandleValueArray::from(&callback_args);
332
333 rooted!(in(*cx) let mut result = UndefinedValue());
334 unsafe {
335 Call(
336 *cx,
337 paint_instance.handle(),
338 paint_function.handle(),
339 &args,
340 result.handle_mut(),
341 );
342 }
343 let missing_image_urls = rendering_context.take_missing_image_urls();
344
345 if unsafe { JS_IsExceptionPending(*cx) } {
347 debug!("Paint function threw an exception {}.", name);
348 unsafe {
349 JS_ClearPendingException(*cx);
350 }
351 return self.invalid_image(size_in_dpx, missing_image_urls);
352 }
353
354 rendering_context.update_rendering();
355
356 DrawAPaintImageResult {
357 width: size_in_dpx.width,
358 height: size_in_dpx.height,
359 format: PixelFormat::BGRA8,
360 image_key: Some(rendering_context.image_key()),
361 missing_image_urls,
362 }
363 }
364
365 fn invalid_image(
367 &self,
368 size: Size2D<u32, DevicePixel>,
369 missing_image_urls: Vec<ServoUrl>,
370 ) -> DrawAPaintImageResult {
371 debug!("Returning an invalid image.");
372 DrawAPaintImageResult {
373 width: size.width,
374 height: size.height,
375 format: PixelFormat::BGRA8,
376 image_key: None,
377 missing_image_urls,
378 }
379 }
380
381 fn painter(&self, name: Atom) -> Box<dyn Painter> {
382 struct WorkletPainter {
384 name: Atom,
385 executor: Mutex<WorkletExecutor>,
386 }
387 impl SpeculativePainter for WorkletPainter {
388 fn speculatively_draw_a_paint_image(
389 &self,
390 properties: Vec<(Atom, String)>,
391 arguments: Vec<String>,
392 ) {
393 let name = self.name.clone();
394 let task =
395 PaintWorkletTask::SpeculativelyDrawAPaintImage(name, properties, arguments);
396 self.executor
397 .lock()
398 .expect("Locking a painter.")
399 .schedule_a_worklet_task(WorkletTask::Paint(task));
400 }
401 }
402 impl Painter for WorkletPainter {
403 fn draw_a_paint_image(
404 &self,
405 size: Size2D<f32, CSSPixel>,
406 device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
407 properties: Vec<(Atom, String)>,
408 arguments: Vec<String>,
409 ) -> Result<DrawAPaintImageResult, PaintWorkletError> {
410 let name = self.name.clone();
411 let (sender, receiver) = unbounded();
412 let task = PaintWorkletTask::DrawAPaintImage(
413 name,
414 size,
415 device_pixel_ratio,
416 properties,
417 arguments,
418 sender,
419 );
420 self.executor
421 .lock()
422 .expect("Locking a painter.")
423 .schedule_a_worklet_task(WorkletTask::Paint(task));
424
425 let timeout = pref!(dom_worklet_timeout_ms) as u64;
426
427 receiver
428 .recv_timeout(Duration::from_millis(timeout))
429 .map_err(PaintWorkletError::from)
430 }
431 }
432 Box::new(WorkletPainter {
433 name,
434 executor: Mutex::new(self.worklet_global.executor()),
435 })
436 }
437}
438
439pub(crate) enum PaintWorkletTask {
441 DrawAPaintImage(
442 Atom,
443 Size2D<f32, CSSPixel>,
444 Scale<f32, CSSPixel, DevicePixel>,
445 Vec<(Atom, String)>,
446 Vec<String>,
447 Sender<DrawAPaintImageResult>,
448 ),
449 SpeculativelyDrawAPaintImage(Atom, Vec<(Atom, String)>, Vec<String>),
450}
451
452#[derive(JSTraceable, MallocSizeOf)]
457#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
458struct PaintDefinition {
459 #[ignore_malloc_size_of = "mozjs"]
460 class_constructor: Heap<JSVal>,
461 #[ignore_malloc_size_of = "mozjs"]
462 paint_function: Heap<JSVal>,
463 constructor_valid_flag: Cell<bool>,
464 context_alpha_flag: bool,
465 input_arguments_len: usize,
467 context: Dom<PaintRenderingContext2D>,
471}
472
473impl PaintDefinition {
474 fn new(
475 class_constructor: HandleValue,
476 paint_function: HandleValue,
477 alpha: bool,
478 input_arguments_len: usize,
479 context: &PaintRenderingContext2D,
480 ) -> Box<PaintDefinition> {
481 let result = Box::new(PaintDefinition {
482 class_constructor: Heap::default(),
483 paint_function: Heap::default(),
484 constructor_valid_flag: Cell::new(true),
485 context_alpha_flag: alpha,
486 input_arguments_len,
487 context: Dom::from_ref(context),
488 });
489 result.class_constructor.set(class_constructor.get());
490 result.paint_function.set(paint_function.get());
491 result
492 }
493}
494
495impl PaintWorkletGlobalScopeMethods<crate::DomTypeHolder> for PaintWorkletGlobalScope {
496 #[expect(unsafe_code)]
497 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
498 fn RegisterPaint(&self, name: DOMString, paint_ctor: Rc<VoidFunction>) -> Fallible<()> {
500 let name = Atom::from(name);
501 let cx = WorkletGlobalScope::get_cx();
502 rooted!(in(*cx) let paint_obj = paint_ctor.callback_holder().get());
503 rooted!(in(*cx) let paint_val = ObjectValue(paint_obj.get()));
504
505 debug!("Registering paint image name {}.", name);
506
507 if name.is_empty() {
509 return Err(Error::Type(String::from("Empty paint name.")));
510 }
511
512 if self.paint_definitions.borrow().contains_key(&name) {
514 return Err(Error::InvalidModification);
515 }
516
517 let mut property_names: Vec<String> =
519 get_property(cx, paint_obj.handle(), "inputProperties", ())?.unwrap_or_default();
520 let properties = property_names.drain(..).map(Atom::from).collect();
521
522 let input_arguments: Vec<String> =
524 get_property(cx, paint_obj.handle(), "inputArguments", ())?.unwrap_or_default();
525
526 let alpha: bool = 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 get_property_jsval(cx, paint_obj.handle(), "prototype", prototype.handle_mut())?;
539 if !prototype.is_object() {
540 return Err(Error::Type(String::from("Prototype is not an object.")));
541 }
542 rooted!(in(*cx) let prototype = prototype.to_object());
543
544 rooted!(in(*cx) let mut paint_function = UndefinedValue());
546 get_property_jsval(cx, prototype.handle(), "paint", paint_function.handle_mut())?;
547 if !paint_function.is_object() || unsafe { !IsCallable(paint_function.to_object()) } {
548 return Err(Error::Type(String::from("Paint function is not callable.")));
549 }
550
551 let Some(context) = PaintRenderingContext2D::new(self, CanGc::note()) else {
553 return Err(Error::Operation);
554 };
555 let definition = PaintDefinition::new(
556 paint_val.handle(),
557 paint_function.handle(),
558 alpha,
559 input_arguments.len(),
560 &context,
561 );
562
563 debug!("Registering definition {}.", name);
565 self.paint_definitions
566 .borrow_mut()
567 .insert(name.clone(), definition);
568
569 let painter = self.painter(name.clone());
574 self.worklet_global
575 .register_paint_worklet(name, properties, painter);
576
577 Ok(())
578 }
579
580 fn Sleep(&self, ms: u64) {
587 thread::sleep(Duration::from_millis(ms));
588 }
589}