Skip to main content

script/dom/worklet/
paintworkletglobalscope.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::ptr::{NonNull, null_mut};
7use std::sync::atomic::AtomicBool;
8use std::sync::{Arc, Mutex};
9use std::thread;
10use std::time::Duration;
11
12use crossbeam_channel::unbounded;
13use dom_struct::dom_struct;
14use euclid::{Scale, Size2D};
15use js::context::JSContext;
16use js::jsapi::{HandleValueArray, Heap, IsCallable, IsConstructor, JSObject, Value};
17use js::jsval::{JSVal, ObjectValue, UndefinedValue};
18use js::realm::AutoRealm;
19use js::rust::wrappers2::{
20    Call, Construct1, JS_ClearPendingException, JS_IsExceptionPending, NewArrayObject,
21};
22use js::rust::{HandleValue, MutableHandle};
23use net_traits::image_cache::ImageCache;
24use pixels::PixelFormat;
25use script_bindings::callback::{HasCallbackHolder, RootedCallback};
26use script_bindings::cell::DomRefCell;
27use script_bindings::interfaces::HasOrigin;
28use script_bindings::reflector::DomObject;
29use script_traits::{DrawAPaintImageResult, PaintWorkletError, Painter};
30use servo_base::id::PipelineId;
31use servo_config::pref;
32use servo_url::{MutableOrigin, ServoUrl};
33use style_traits::{CSSPixel, SpeculativePainter};
34use stylo_atoms::Atom;
35use webrender_api::units::DevicePixel;
36
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};
52
53/// <https://drafts.css-houdini.org/css-paint-api/#paintworkletglobalscope>
54#[dom_struct]
55pub(crate) struct PaintWorkletGlobalScope {
56    /// The worklet global for this object
57    worklet_global: WorkletGlobalScope,
58    /// The image cache
59    #[ignore_malloc_size_of = "ImageCache"]
60    #[no_trace]
61    image_cache: Arc<dyn ImageCache>,
62    /// <https://drafts.css-houdini.org/css-paint-api/#paint-definitions>
63    paint_definitions: DomRefCell<HashMapTracedValues<Atom, Box<PaintDefinition>>>,
64    /// <https://drafts.css-houdini.org/css-paint-api/#paint-class-instances>
65    #[ignore_malloc_size_of = "mozjs"]
66    paint_class_instances: DomRefCell<HashMapTracedValues<Atom, Box<Heap<JSVal>>>>,
67    /// The most recent name the worklet was called with
68    #[no_trace]
69    cached_name: DomRefCell<Atom>,
70    /// The most recent size the worklet was drawn at
71    #[no_trace]
72    cached_size: Cell<Size2D<f32, CSSPixel>>,
73    /// The most recent device pixel ratio the worklet was drawn at
74    #[no_trace]
75    cached_device_pixel_ratio: Cell<Scale<f32, CSSPixel, DevicePixel>>,
76    /// The most recent properties the worklet was drawn at
77    #[no_trace]
78    cached_properties: DomRefCell<Vec<(Atom, String)>>,
79    /// The most recent arguments the worklet was drawn at
80    cached_arguments: DomRefCell<Vec<String>>,
81    /// The most recent result
82    #[no_trace]
83    cached_result: DomRefCell<DrawAPaintImageResult>,
84}
85
86impl PaintWorkletGlobalScope {
87    #[allow(clippy::too_many_arguments)]
88    pub(crate) fn new(
89        cx: &mut JSContext,
90        pipeline_id: PipelineId,
91        base_url: ServoUrl,
92        inherited_secure_context: Option<bool>,
93        executor: WorkletExecutor,
94        init: &WorkletGlobalScopeInit,
95        closing: Arc<AtomicBool>,
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                pipeline_id,
104                base_url,
105                inherited_secure_context,
106                executor,
107                init,
108                closing,
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.origin(), global)
127    }
128
129    pub(crate) fn image_cache(&self) -> Arc<dyn ImageCache> {
130        self.image_cache.clone()
131    }
132
133    fn has_cached_paint_image(
134        &self,
135        name: &Atom,
136        size: Size2D<f32, CSSPixel>,
137        device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
138        properties: &[(Atom, String)],
139        arguments: &[String],
140    ) -> bool {
141        (&*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    }
147
148    fn set_cached_paint_image(
149        &self,
150        name: Atom,
151        size: Size2D<f32, CSSPixel>,
152        device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
153        properties: Vec<(Atom, String)>,
154        arguments: Vec<String>,
155        result: DrawAPaintImageResult,
156    ) {
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;
163    }
164
165    /// <https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image>
166    fn draw_a_paint_image(
167        &self,
168        cx: &mut JSContext,
169        name: &Atom,
170        size_in_px: Size2D<f32, CSSPixel>,
171        device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
172        properties: &StylePropertyMapReadOnly,
173        arguments: &[String],
174    ) -> DrawAPaintImageResult {
175        let size_in_dpx = size_in_px * device_pixel_ratio;
176        let size_in_dpx = Size2D::new(
177            size_in_dpx.width.abs() as u32,
178            size_in_dpx.height.abs() as u32,
179        );
180
181        // TODO: Steps 1-5.
182
183        // TODO: document paint definitions.
184        self.invoke_a_paint_callback(
185            cx,
186            name,
187            size_in_px,
188            size_in_dpx,
189            device_pixel_ratio,
190            properties,
191            arguments,
192        )
193    }
194
195    #[expect(unsafe_code)]
196    /// Gets or creates the `self.paint_class_instances` given by `name` and inserts
197    /// the value into the provided `paint_instance` handle. Returns `Err` when this
198    /// fails.
199    fn get_or_create_paint_instance(
200        &self,
201        cx: &mut JSContext,
202        mut paint_instance: MutableHandle<Value>,
203        name: Atom,
204        class_constructor: HandleValue,
205        size_in_dpx: Size2D<u32, DevicePixel>,
206    ) -> Result<(), DrawAPaintImageResult> {
207        if let Some(entry) = self.paint_class_instances.borrow().get(&name) {
208            paint_instance.set(entry.get());
209            return Ok(());
210        }
211
212        // Step of <https://drafts.css-houdini.org/css-paint-api/#invoke-a-paint-callback>
213        // 5.2 Let paintCtor be the class constructor on definition.
214        // 5.3 Let paintInstance be the result of Construct(paintCtor).
215        //     If construct throws an exception, set the definition’s constructor valid flag to false,
216        //     let the image output be an invalid image and abort all these steps.
217        let args = HandleValueArray::empty();
218        rooted!(&in(cx) let mut result = null_mut::<JSObject>());
219        unsafe {
220            Construct1(cx, class_constructor, &args, result.handle_mut());
221        }
222        paint_instance.set(ObjectValue(result.get()));
223        if unsafe { JS_IsExceptionPending(cx) } {
224            debug!("Paint constructor threw an exception {}.", name);
225            unsafe {
226                JS_ClearPendingException(cx);
227            }
228            self.paint_definitions
229                .safe_borrow_mut(cx)
230                .get_mut(&name)
231                .expect("Vanishing paint definition.")
232                .constructor_valid_flag
233                .set(false);
234            // 1.2 Let the image output be an invalid image and abort all these steps.
235            return Err(self.invalid_image(size_in_dpx, vec![]));
236        }
237        // Step 5.4
238        self.paint_class_instances
239            .safe_borrow_mut(cx)
240            .entry(name)
241            .or_default()
242            .set(paint_instance.get());
243        Ok(())
244    }
245
246    /// <https://drafts.css-houdini.org/css-paint-api/#invoke-a-paint-callback>
247    #[expect(clippy::too_many_arguments)]
248    #[expect(unsafe_code)]
249    fn invoke_a_paint_callback(
250        &self,
251        cx: &mut JSContext,
252        name: &Atom,
253        size_in_px: Size2D<f32, CSSPixel>,
254        size_in_dpx: Size2D<u32, DevicePixel>,
255        device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
256        properties: &StylePropertyMapReadOnly,
257        arguments: &[String],
258    ) -> DrawAPaintImageResult {
259        debug!(
260            "Invoking a paint callback {}({},{}) at {:?}.",
261            name, size_in_px.width, size_in_px.height, device_pixel_ratio
262        );
263
264        let mut realm = AutoRealm::new(
265            cx,
266            NonNull::new(self.worklet_global.reflector().get_jsobject().get()).unwrap(),
267        );
268        let cx = &mut *realm;
269
270        // TODO: Steps 1-2.1.
271        // Step 2.2-5.1.
272        rooted!(&in(cx) let mut class_constructor = UndefinedValue());
273        rooted!(&in(cx) let mut paint_function = UndefinedValue());
274        let rendering_context = match self.paint_definitions.borrow().get(name) {
275            None => {
276                // Step 2.2.
277                warn!("Drawing un-registered paint definition {}.", name);
278                return self.invalid_image(size_in_dpx, vec![]);
279            },
280            Some(definition) => {
281                // Step 5.1
282                if !definition.constructor_valid_flag.get() {
283                    debug!("Drawing invalid paint definition {}.", name);
284                    return self.invalid_image(size_in_dpx, vec![]);
285                }
286                class_constructor.set(definition.class_constructor.get());
287                paint_function.set(definition.paint_function.get());
288                DomRoot::from_ref(&*definition.context)
289            },
290        };
291
292        // Steps 5.2-5.4
293        // TODO: the spec requires calling the constructor now, but we might want to
294        // prepopulate the paint instance in `RegisterPaint`, to avoid calling it in
295        // the primary worklet thread.
296        // https://github.com/servo/servo/issues/17377
297        rooted!(&in(cx) let mut paint_instance = UndefinedValue());
298        if let Err(early_return) = self.get_or_create_paint_instance(
299            cx,
300            paint_instance.handle_mut(),
301            name.clone(),
302            class_constructor.handle(),
303            size_in_dpx,
304        ) {
305            return early_return;
306        }
307
308        // TODO: Steps 6-7
309        // Step 8
310        // TODO: the spec requires creating a new paint rendering context each time,
311        // this code recycles the same one.
312        rendering_context.set_bitmap_dimensions(size_in_px, device_pixel_ratio);
313
314        // Step 9
315        let paint_size = PaintSize::new(cx, self, size_in_px);
316
317        // TODO: Step 10
318        // Steps 11-12
319        debug!("Invoking paint function {}.", name);
320        rooted_vec!(let mut arguments_values);
321        for argument in arguments {
322            let style_value = CSSStyleValue::new(cx, self.upcast(), argument.clone());
323            arguments_values.push(ObjectValue(style_value.reflector().get_jsobject().get()));
324        }
325        let arguments_value_array = HandleValueArray::from(&arguments_values);
326        rooted!(&in(cx) let argument_object = unsafe { NewArrayObject(cx, &arguments_value_array) });
327
328        rooted_vec!(let mut callback_args);
329        callback_args.push(ObjectValue(
330            rendering_context.reflector().get_jsobject().get(),
331        ));
332        callback_args.push(ObjectValue(paint_size.reflector().get_jsobject().get()));
333        callback_args.push(ObjectValue(properties.reflector().get_jsobject().get()));
334        callback_args.push(ObjectValue(argument_object.get()));
335        let args = HandleValueArray::from(&callback_args);
336
337        rooted!(&in(cx) let mut result = UndefinedValue());
338        unsafe {
339            Call(
340                cx,
341                paint_instance.handle(),
342                paint_function.handle(),
343                &args,
344                result.handle_mut(),
345            );
346        }
347        let missing_image_urls = rendering_context.take_missing_image_urls();
348
349        // Step 13.
350        if unsafe { JS_IsExceptionPending(cx) } {
351            debug!("Paint function threw an exception {}.", name);
352            unsafe {
353                JS_ClearPendingException(cx);
354            }
355            return self.invalid_image(size_in_dpx, missing_image_urls);
356        }
357
358        rendering_context.update_rendering();
359
360        DrawAPaintImageResult {
361            width: size_in_dpx.width,
362            height: size_in_dpx.height,
363            format: PixelFormat::BGRA8,
364            image_key: Some(rendering_context.image_key()),
365            missing_image_urls,
366        }
367    }
368
369    /// <https://drafts.csswg.org/css-images-4/#invalid-image>
370    fn invalid_image(
371        &self,
372        size: Size2D<u32, DevicePixel>,
373        missing_image_urls: Vec<ServoUrl>,
374    ) -> DrawAPaintImageResult {
375        debug!("Returning an invalid image.");
376        DrawAPaintImageResult {
377            width: size.width,
378            height: size.height,
379            format: PixelFormat::BGRA8,
380            image_key: None,
381            missing_image_urls,
382        }
383    }
384
385    fn painter(&self, name: Atom) -> Box<dyn Painter> {
386        Box::new(WorkletPainter {
387            name,
388            executor: Mutex::new(self.worklet_global.executor()),
389        })
390    }
391}
392
393// Rather annoyingly we have to use a mutex here to make the painter Sync.
394struct WorkletPainter {
395    name: Atom,
396    executor: Mutex<WorkletExecutor>,
397}
398
399impl SpeculativePainter for WorkletPainter {
400    fn speculatively_draw_a_paint_image(
401        &self,
402        properties: Vec<(Atom, String)>,
403        arguments: Vec<String>,
404    ) {
405        let name = self.name.clone();
406
407        let speculatively_draw_a_paint_image_task =
408            move |cx: &mut JSContext, global_scope: &WorkletGlobalScope| {
409                let paint_worklet_global_scope = global_scope
410                    .downcast::<PaintWorkletGlobalScope>()
411                    .expect("PaintWorklet's task should be run only on PaintWorkletGlobalScope.");
412
413                let should_speculate = (*paint_worklet_global_scope.cached_name.borrow() != name) ||
414                    (*paint_worklet_global_scope.cached_properties.borrow() != properties) ||
415                    (*paint_worklet_global_scope.cached_arguments.borrow() != arguments);
416                if should_speculate {
417                    let size = paint_worklet_global_scope.cached_size.get();
418                    let device_pixel_ratio =
419                        paint_worklet_global_scope.cached_device_pixel_ratio.get();
420                    let map = StylePropertyMapReadOnly::from_iter(
421                        cx,
422                        paint_worklet_global_scope.upcast(),
423                        properties.iter().cloned(),
424                    );
425                    let result = paint_worklet_global_scope.draw_a_paint_image(
426                        cx,
427                        &name,
428                        size,
429                        device_pixel_ratio,
430                        &map,
431                        &arguments,
432                    );
433                    if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) {
434                        paint_worklet_global_scope.set_cached_paint_image(
435                            name,
436                            size,
437                            device_pixel_ratio,
438                            properties,
439                            arguments,
440                            result,
441                        );
442                    }
443                }
444            };
445
446        self.executor
447            .lock()
448            .expect("Locking a painter.")
449            .schedule_a_worklet_task(Box::new(speculatively_draw_a_paint_image_task));
450    }
451}
452
453impl Painter for WorkletPainter {
454    fn draw_a_paint_image(
455        &self,
456        size: Size2D<f32, CSSPixel>,
457        device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
458        properties: Vec<(Atom, String)>,
459        arguments: Vec<String>,
460    ) -> Result<DrawAPaintImageResult, PaintWorkletError> {
461        let name = self.name.clone();
462        let (sender, receiver) = unbounded();
463
464        let draw_a_paint_image_task =
465            move |cx: &mut JSContext, global_scope: &WorkletGlobalScope| {
466                let paint_worklet_global_scope = global_scope
467                    .downcast::<PaintWorkletGlobalScope>()
468                    .expect("PaintWorklet's task should be run only on PaintWorkletGlobalScope.");
469
470                let cache_hit = paint_worklet_global_scope.has_cached_paint_image(
471                    &name,
472                    size,
473                    device_pixel_ratio,
474                    &properties,
475                    &arguments,
476                );
477                let result = if cache_hit {
478                    debug!("Cache hit on paint worklet {}!", name);
479                    paint_worklet_global_scope.cached_result.borrow().clone()
480                } else {
481                    debug!("Cache miss on paint worklet {}!", name);
482                    let map = StylePropertyMapReadOnly::from_iter(
483                        cx,
484                        paint_worklet_global_scope.upcast(),
485                        properties.iter().cloned(),
486                    );
487                    let result = paint_worklet_global_scope.draw_a_paint_image(
488                        cx,
489                        &name,
490                        size,
491                        device_pixel_ratio,
492                        &map,
493                        &arguments,
494                    );
495                    if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) {
496                        paint_worklet_global_scope.set_cached_paint_image(
497                            name,
498                            size,
499                            device_pixel_ratio,
500                            properties,
501                            arguments,
502                            result.clone(),
503                        );
504                    }
505                    result
506                };
507                let _ = sender.send(result);
508            };
509
510        self.executor
511            .lock()
512            .expect("Locking a painter.")
513            .schedule_a_worklet_task(Box::new(draw_a_paint_image_task));
514
515        let timeout = pref!(dom_worklet_timeout_ms) as u64;
516
517        receiver
518            .recv_timeout(Duration::from_millis(timeout))
519            .map_err(PaintWorkletError::from)
520    }
521}
522
523/// A paint definition
524/// <https://drafts.css-houdini.org/css-paint-api/#paint-definition>
525/// This type is dangerous, because it contains uboxed `Heap<JSVal>` values,
526/// which can't be moved.
527#[derive(JSTraceable, MallocSizeOf)]
528#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
529struct PaintDefinition {
530    #[ignore_malloc_size_of = "mozjs"]
531    class_constructor: Heap<JSVal>,
532    #[ignore_malloc_size_of = "mozjs"]
533    paint_function: Heap<JSVal>,
534    constructor_valid_flag: Cell<bool>,
535    context_alpha_flag: bool,
536    // TODO: this should be a list of CSS syntaxes.
537    input_arguments_len: usize,
538    // TODO: the spec calls for fresh rendering contexts each time a paint image is drawn,
539    // but to avoid having the primary worklet thread create a new renering context,
540    // we recycle them.
541    context: Dom<PaintRenderingContext2D>,
542}
543
544impl PaintDefinition {
545    fn new(
546        class_constructor: HandleValue,
547        paint_function: HandleValue,
548        alpha: bool,
549        input_arguments_len: usize,
550        context: &PaintRenderingContext2D,
551    ) -> Box<PaintDefinition> {
552        let result = Box::new(PaintDefinition {
553            class_constructor: Heap::default(),
554            paint_function: Heap::default(),
555            constructor_valid_flag: Cell::new(true),
556            context_alpha_flag: alpha,
557            input_arguments_len,
558            context: Dom::from_ref(context),
559        });
560        result.class_constructor.set(class_constructor.get());
561        result.paint_function.set(paint_function.get());
562        result
563    }
564}
565
566impl PaintWorkletGlobalScopeMethods<crate::DomTypeHolder> for PaintWorkletGlobalScope {
567    #[expect(unsafe_code)]
568    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
569    /// <https://drafts.css-houdini.org/css-paint-api/#dom-paintworkletglobalscope-registerpaint>
570    fn RegisterPaint(
571        &self,
572        cx: &mut JSContext,
573        name: DOMString,
574        paint_ctor: RootedCallback<VoidFunction>,
575    ) -> Fallible<()> {
576        let name = Atom::from(name);
577        rooted!(&in(cx) let paint_obj = paint_ctor.callback_holder().get());
578        rooted!(&in(cx) let paint_val = ObjectValue(paint_obj.get()));
579
580        debug!("Registering paint image name {}.", name);
581
582        // Step 1.
583        if name.is_empty() {
584            return Err(Error::Type(c"Empty paint name.".to_owned()));
585        }
586
587        // Step 2-3.
588        if self.paint_definitions.borrow().contains_key(&name) {
589            return Err(Error::InvalidModification(None));
590        }
591
592        // Step 4-6.
593        let property_names: Vec<String> =
594            get_property(cx, paint_obj.handle(), c"inputProperties", ())?.unwrap_or_default();
595        let properties = property_names.into_iter().map(Atom::from).collect();
596
597        // Step 7-9.
598        let input_arguments: Vec<String> =
599            get_property(cx, paint_obj.handle(), c"inputArguments", ())?.unwrap_or_default();
600
601        // TODO: Steps 10-11.
602
603        // Steps 12-13.
604        let alpha: bool = get_property(cx, paint_obj.handle(), c"alpha", ())?.unwrap_or(true);
605
606        // Step 14
607        if unsafe { !IsConstructor(paint_obj.get()) } {
608            return Err(Error::Type(c"Not a constructor.".to_owned()));
609        }
610
611        // Steps 15-16
612        rooted!(&in(cx) let mut prototype = UndefinedValue());
613        get_property_jsval(cx, paint_obj.handle(), c"prototype", prototype.handle_mut())?;
614        if !prototype.is_object() {
615            return Err(Error::Type(c"Prototype is not an object.".to_owned()));
616        }
617        rooted!(&in(cx) let prototype = prototype.to_object());
618
619        // Steps 17-18
620        rooted!(&in(cx) let mut paint_function = UndefinedValue());
621        get_property_jsval(
622            cx,
623            prototype.handle(),
624            c"paint",
625            paint_function.handle_mut(),
626        )?;
627        if !paint_function.is_object() || unsafe { !IsCallable(paint_function.to_object()) } {
628            return Err(Error::Type(c"Paint function is not callable.".to_owned()));
629        }
630
631        // Step 19.
632        let Some(context) = PaintRenderingContext2D::new(cx, self) else {
633            return Err(Error::Operation(None));
634        };
635        let definition = PaintDefinition::new(
636            paint_val.handle(),
637            paint_function.handle(),
638            alpha,
639            input_arguments.len(),
640            &context,
641        );
642
643        // Step 20.
644        debug!("Registering definition {}.", name);
645        self.paint_definitions
646            .borrow_mut()
647            .insert(name.clone(), definition);
648
649        // TODO: Step 21.
650
651        // Inform layout that there is a registered paint worklet.
652        // TODO: layout will end up getting this message multiple times.
653        let painter = self.painter(name.clone());
654        self.worklet_global
655            .register_paint_worklet(name, properties, painter);
656
657        Ok(())
658    }
659
660    /// This is a blocking sleep function available in the paint worklet
661    /// global scope behind the dom.worklet.enabled +
662    /// dom.worklet.blockingsleep.enabled prefs. It is to be used only for
663    /// testing, e.g., timeouts, where otherwise one would need busy waiting
664    /// to make sure a certain timeout is triggered.
665    /// check-tidy: no specs after this line
666    fn Sleep(&self, ms: u64) {
667        thread::sleep(Duration::from_millis(ms));
668    }
669}
670
671impl HasOrigin for PaintWorkletGlobalScope {
672    fn origin(&self) -> MutableOrigin {
673        self.upcast::<WorkletGlobalScope>().origin()
674    }
675}