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