Skip to main content

script/dom/worklet/
workletglobalscope.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::rc::Rc;
6use std::sync::Arc;
7use std::sync::atomic::{AtomicBool, Ordering};
8
9use crossbeam_channel::Sender;
10use devtools_traits::ScriptToDevtoolsControlMsg;
11use dom_struct::dom_struct;
12use embedder_traits::ScriptToEmbedderChan;
13use js::context::JSContext;
14use net_traits::ResourceThreads;
15use net_traits::image_cache::ImageCache;
16use profile_traits::{mem, time};
17use script_traits::Painter;
18use servo_base::generic_channel::GenericCallback;
19use servo_base::id::PipelineId;
20use servo_constellation_traits::ScriptToConstellationSender;
21use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
22use storage_traits::StorageThreads;
23use stylo_atoms::Atom;
24
25use crate::dom::bindings::inheritance::Castable;
26use crate::dom::bindings::root::DomRoot;
27use crate::dom::bindings::trace::CustomTraceable;
28use crate::dom::bindings::utils::define_all_exposed_interfaces;
29use crate::dom::globalscope::GlobalScope;
30use crate::dom::paintworkletglobalscope::{PaintWorkletGlobalScope, PaintWorkletTask};
31#[cfg(feature = "testbinding")]
32use crate::dom::testworkletglobalscope::{TestWorkletGlobalScope, TestWorkletTask};
33#[cfg(feature = "webgpu")]
34use crate::dom::webgpu::identityhub::IdentityHub;
35use crate::dom::worklet::WorkletExecutor;
36use crate::messaging::MainThreadScriptMsg;
37use crate::microtask::MicrotaskQueue;
38use crate::realms::enter_auto_realm;
39use crate::tasks::task::TaskCanceller;
40use crate::tasks::task_manager::TaskManager;
41
42#[dom_struct]
43/// <https://drafts.css-houdini.org/worklets/#workletglobalscope>
44pub(crate) struct WorkletGlobalScope {
45    /// The global for this worklet.
46    globalscope: GlobalScope,
47    /// The base URL for this worklet.
48    #[no_trace]
49    base_url: ServoUrl,
50    /// Sender back to the script thread
51    to_script_thread_sender: Sender<MainThreadScriptMsg>,
52    /// Worklet task executor
53    executor: WorkletExecutor,
54
55    #[no_trace]
56    /// The pipeline that created this worklet.
57    pipeline_id: PipelineId,
58
59    #[no_trace]
60    origin: MutableOrigin,
61
62    /// The [`TaskManager`] for this [`WorkletGlobalScope`].
63    #[conditional_malloc_size_of]
64    task_manager: Rc<TaskManager>,
65
66    /// The "microtask queue" for this WorkletGlobalScope's event loop.
67    /// <https://html.spec.whatwg.org/multipage/#microtask-queue>
68    #[conditional_malloc_size_of]
69    microtask_queue: Rc<MicrotaskQueue>,
70
71    #[conditional_malloc_size_of]
72    closing: Arc<AtomicBool>,
73}
74
75impl WorkletGlobalScope {
76    /// Create a new heap-allocated `WorkletGlobalScope`.
77    #[allow(clippy::too_many_arguments)]
78    pub(crate) fn new(
79        scope_type: WorkletGlobalScopeType,
80        pipeline_id: PipelineId,
81        base_url: ServoUrl,
82        inherited_secure_context: Option<bool>,
83        executor: WorkletExecutor,
84        init: &WorkletGlobalScopeInit,
85        cx: &mut JSContext,
86        closing: Arc<AtomicBool>,
87        microtask_queue: Rc<MicrotaskQueue>,
88    ) -> DomRoot<WorkletGlobalScope> {
89        let scope: DomRoot<WorkletGlobalScope> = match scope_type {
90            #[cfg(feature = "testbinding")]
91            WorkletGlobalScopeType::Test => DomRoot::upcast(TestWorkletGlobalScope::new(
92                pipeline_id,
93                base_url,
94                inherited_secure_context,
95                executor,
96                init,
97                cx,
98                closing,
99                microtask_queue,
100            )),
101            WorkletGlobalScopeType::Paint => DomRoot::upcast(PaintWorkletGlobalScope::new(
102                cx,
103                pipeline_id,
104                base_url,
105                inherited_secure_context,
106                executor,
107                init,
108                closing,
109                microtask_queue,
110            )),
111        };
112
113        let mut realm = enter_auto_realm(cx, &*scope);
114        let mut realm = realm.current_realm();
115        define_all_exposed_interfaces(&mut realm, scope.upcast());
116
117        scope
118    }
119
120    /// Create a new stack-allocated `WorkletGlobalScope`.
121    pub(crate) fn new_inherited(
122        pipeline_id: PipelineId,
123        base_url: ServoUrl,
124        inherited_secure_context: Option<bool>,
125        executor: WorkletExecutor,
126        init: &WorkletGlobalScopeInit,
127        closing: Arc<AtomicBool>,
128        microtask_queue: Rc<MicrotaskQueue>,
129    ) -> Self {
130        let script_event_loop_sender = executor.event_loop_sender();
131
132        Self {
133            globalscope: GlobalScope::new_inherited(
134                init.devtools_chan.clone(),
135                init.mem_profiler_chan.clone(),
136                init.time_profiler_chan.clone(),
137                init.script_to_constellation_sender.clone(),
138                init.to_embedder_sender.clone(),
139                init.resource_threads.clone(),
140                init.storage_threads.clone(),
141                base_url.clone(),
142                None,
143                #[cfg(feature = "webgpu")]
144                init.gpu_id_hub.clone(),
145                inherited_secure_context,
146                false,
147                None, // font_context
148            ),
149            base_url,
150            to_script_thread_sender: init.to_script_thread_sender.clone(),
151            executor,
152            pipeline_id,
153            task_manager: Rc::new(TaskManager::new(
154                Some(script_event_loop_sender),
155                pipeline_id,
156                Some(TaskCanceller {
157                    cancelled: closing.clone(),
158                }),
159            )),
160            origin: MutableOrigin::new(ImmutableOrigin::new_opaque()),
161            microtask_queue,
162            closing,
163        }
164    }
165
166    pub(crate) fn origin(&self) -> MutableOrigin {
167        self.origin.clone()
168    }
169
170    pub(crate) fn pipeline_id(&self) -> PipelineId {
171        self.pipeline_id
172    }
173
174    /// Register a paint worklet to the script thread.
175    pub(crate) fn register_paint_worklet(
176        &self,
177        name: Atom,
178        properties: Vec<Atom>,
179        painter: Box<dyn Painter>,
180    ) {
181        self.to_script_thread_sender
182            .send(MainThreadScriptMsg::RegisterPaintWorklet {
183                pipeline_id: self.globalscope.pipeline_id(),
184                name,
185                properties,
186                painter,
187            })
188            .expect("Worklet thread outlived script thread.");
189    }
190
191    /// The base URL of this global.
192    pub(crate) fn base_url(&self) -> ServoUrl {
193        self.base_url.clone()
194    }
195
196    /// The worklet executor.
197    pub(crate) fn executor(&self) -> WorkletExecutor {
198        self.executor.clone()
199    }
200
201    /// Perform a worklet task
202    pub(crate) fn perform_a_worklet_task(&self, cx: &mut JSContext, task: WorkletTask) {
203        match task {
204            #[cfg(feature = "testbinding")]
205            WorkletTask::Test(task) => match self.downcast::<TestWorkletGlobalScope>() {
206                Some(global) => global.perform_a_worklet_task(task),
207                None => warn!("This is not a test worklet."),
208            },
209            WorkletTask::Paint(task) => match self.downcast::<PaintWorkletGlobalScope>() {
210                Some(global) => global.perform_a_worklet_task(cx, task),
211                None => warn!("This is not a paint worklet."),
212            },
213        }
214    }
215
216    pub(crate) fn task_manager(&self) -> Rc<TaskManager> {
217        self.task_manager.clone()
218    }
219
220    pub(crate) fn perform_a_microtask_checkpoint(&self, cx: &mut JSContext) {
221        if !self.closing.load(Ordering::SeqCst) {
222            self.microtask_queue
223                .checkpoint(cx, vec![DomRoot::from_ref(&self.globalscope)]);
224        }
225    }
226}
227
228/// Resources required by workletglobalscopes
229#[derive(Clone)]
230pub(crate) struct WorkletGlobalScopeInit {
231    /// Channel to the main script thread
232    pub(crate) to_script_thread_sender: Sender<MainThreadScriptMsg>,
233    /// Channel to a resource thread
234    pub(crate) resource_threads: ResourceThreads,
235    /// Channels to the [`StorageThreads`].
236    pub(crate) storage_threads: StorageThreads,
237    /// Channel to the memory profiler
238    pub(crate) mem_profiler_chan: mem::ProfilerChan,
239    /// Channel to the time profiler
240    pub(crate) time_profiler_chan: time::ProfilerChan,
241    /// Channel to devtools
242    pub(crate) devtools_chan: Option<GenericCallback<ScriptToDevtoolsControlMsg>>,
243    /// Messages to send to the Embedder
244    pub(crate) to_embedder_sender: ScriptToEmbedderChan,
245    /// The image cache
246    pub(crate) image_cache: Arc<dyn ImageCache>,
247    /// Identity manager for WebGPU resources
248    #[cfg(feature = "webgpu")]
249    pub(crate) gpu_id_hub: Arc<IdentityHub>,
250    pub(crate) script_to_constellation_sender: ScriptToConstellationSender,
251}
252
253/// <https://drafts.css-houdini.org/worklets/#worklet-global-scope-type>
254#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)]
255pub(crate) enum WorkletGlobalScopeType {
256    /// A servo-specific testing worklet
257    #[cfg(feature = "testbinding")]
258    Test,
259    /// A paint worklet
260    Paint,
261}
262
263/// A task which can be performed in the context of a worklet global.
264pub(crate) enum WorkletTask {
265    #[cfg(feature = "testbinding")]
266    Test(TestWorkletTask),
267    Paint(PaintWorkletTask),
268}