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