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::Window;
26use crate::dom::bindings::inheritance::Castable;
27use crate::dom::bindings::root::DomRoot;
28use crate::dom::bindings::trace::CustomTraceable;
29use crate::dom::bindings::utils::define_all_exposed_interfaces;
30use crate::dom::globalscope::GlobalScope;
31use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope;
32#[cfg(feature = "testbinding")]
33use crate::dom::testworkletglobalscope::TestWorkletGlobalScope;
34#[cfg(feature = "webgpu")]
35use crate::dom::webgpu::identityhub::IdentityHub;
36use crate::dom::worklet::WorkletExecutor;
37use crate::messaging::MainThreadScriptMsg;
38use crate::realms::enter_auto_realm;
39use crate::runtime::microtask::MicrotaskQueue;
40use crate::tasks::task::TaskCanceller;
41use crate::tasks::task_manager::TaskManager;
42
43#[dom_struct]
44/// <https://drafts.css-houdini.org/worklets/#workletglobalscope>
45pub(crate) struct WorkletGlobalScope {
46    /// The global for this worklet.
47    globalscope: GlobalScope,
48    /// The base URL for this worklet.
49    #[no_trace]
50    base_url: ServoUrl,
51    /// Sender back to the script thread
52    to_script_thread_sender: Sender<MainThreadScriptMsg>,
53    /// Worklet task executor
54    executor: WorkletExecutor,
55
56    #[no_trace]
57    /// The pipeline that created this worklet.
58    pipeline_id: PipelineId,
59
60    #[no_trace]
61    origin: MutableOrigin,
62
63    /// The [`TaskManager`] for this [`WorkletGlobalScope`].
64    #[conditional_malloc_size_of]
65    task_manager: Rc<TaskManager>,
66
67    /// The "microtask queue" for this WorkletGlobalScope's event loop.
68    /// <https://html.spec.whatwg.org/multipage/#microtask-queue>
69    #[conditional_malloc_size_of]
70    microtask_queue: Rc<MicrotaskQueue>,
71
72    #[conditional_malloc_size_of]
73    closing: Arc<AtomicBool>,
74}
75
76impl WorkletGlobalScope {
77    /// Create a new heap-allocated `WorkletGlobalScope`.
78    #[allow(clippy::too_many_arguments)]
79    pub(crate) fn new(
80        scope_type: WorkletGlobalScopeType,
81        pipeline_id: PipelineId,
82        base_url: ServoUrl,
83        inherited_secure_context: Option<bool>,
84        executor: WorkletExecutor,
85        init: &WorkletGlobalScopeInit,
86        cx: &mut JSContext,
87        closing: Arc<AtomicBool>,
88        microtask_queue: Rc<MicrotaskQueue>,
89    ) -> DomRoot<WorkletGlobalScope> {
90        let scope: DomRoot<WorkletGlobalScope> = match scope_type {
91            #[cfg(feature = "testbinding")]
92            WorkletGlobalScopeType::Test => DomRoot::upcast(TestWorkletGlobalScope::new(
93                pipeline_id,
94                base_url,
95                inherited_secure_context,
96                executor,
97                init,
98                cx,
99                closing,
100                microtask_queue,
101            )),
102            WorkletGlobalScopeType::Paint => DomRoot::upcast(PaintWorkletGlobalScope::new(
103                cx,
104                pipeline_id,
105                base_url,
106                inherited_secure_context,
107                executor,
108                init,
109                closing,
110                microtask_queue,
111            )),
112        };
113
114        let mut realm = enter_auto_realm(cx, &*scope);
115        let mut realm = realm.current_realm();
116        define_all_exposed_interfaces(&mut realm, scope.upcast());
117
118        scope
119    }
120
121    /// Create a new stack-allocated `WorkletGlobalScope`.
122    pub(crate) fn new_inherited(
123        pipeline_id: PipelineId,
124        base_url: ServoUrl,
125        inherited_secure_context: Option<bool>,
126        executor: WorkletExecutor,
127        init: &WorkletGlobalScopeInit,
128        closing: Arc<AtomicBool>,
129        microtask_queue: Rc<MicrotaskQueue>,
130    ) -> Self {
131        let script_event_loop_sender = executor.event_loop_sender();
132
133        Self {
134            globalscope: GlobalScope::new_inherited(
135                init.devtools_chan.clone(),
136                init.mem_profiler_chan.clone(),
137                init.time_profiler_chan.clone(),
138                init.script_to_constellation_sender.clone(),
139                init.to_embedder_sender.clone(),
140                init.resource_threads.clone(),
141                init.storage_threads.clone(),
142                base_url.clone(),
143                None,
144                #[cfg(feature = "webgpu")]
145                init.gpu_id_hub.clone(),
146                inherited_secure_context,
147                false,
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    pub(crate) fn task_manager(&self) -> Rc<TaskManager> {
202        self.task_manager.clone()
203    }
204
205    pub(crate) fn perform_a_microtask_checkpoint(&self, cx: &mut JSContext) {
206        if !self.closing.load(Ordering::SeqCst) {
207            self.microtask_queue
208                .checkpoint(cx, vec![DomRoot::from_ref(&self.globalscope)]);
209        }
210    }
211}
212
213impl From<&Window> for WorkletGlobalScopeInit {
214    fn from(window: &Window) -> Self {
215        let global_scope = window.as_global_scope();
216
217        WorkletGlobalScopeInit {
218            to_script_thread_sender: window.main_thread_script_chan().clone(),
219            resource_threads: global_scope.resource_threads().clone(),
220            storage_threads: global_scope.storage_threads().clone(),
221            mem_profiler_chan: global_scope.mem_profiler_chan().clone(),
222            time_profiler_chan: global_scope.time_profiler_chan().clone(),
223            devtools_chan: global_scope.devtools_chan().cloned(),
224            script_to_constellation_sender: global_scope.script_to_constellation_chan().sender,
225            to_embedder_sender: global_scope.script_to_embedder_chan().clone(),
226            image_cache: global_scope.image_cache(),
227            #[cfg(feature = "webgpu")]
228            gpu_id_hub: global_scope.wgpu_id_hub(),
229        }
230    }
231}
232
233/// Resources required by workletglobalscopes
234#[derive(Clone)]
235pub(crate) struct WorkletGlobalScopeInit {
236    /// Channel to the main script thread
237    pub(crate) to_script_thread_sender: Sender<MainThreadScriptMsg>,
238    /// Channel to a resource thread
239    pub(crate) resource_threads: ResourceThreads,
240    /// Channels to the [`StorageThreads`].
241    pub(crate) storage_threads: StorageThreads,
242    /// Channel to the memory profiler
243    pub(crate) mem_profiler_chan: mem::ProfilerChan,
244    /// Channel to the time profiler
245    pub(crate) time_profiler_chan: time::ProfilerChan,
246    /// Channel to devtools
247    pub(crate) devtools_chan: Option<GenericCallback<ScriptToDevtoolsControlMsg>>,
248    /// Messages to send to the Embedder
249    pub(crate) to_embedder_sender: ScriptToEmbedderChan,
250    /// The image cache
251    pub(crate) image_cache: Arc<dyn ImageCache>,
252    /// Identity manager for WebGPU resources
253    #[cfg(feature = "webgpu")]
254    pub(crate) gpu_id_hub: Arc<IdentityHub>,
255    pub(crate) script_to_constellation_sender: ScriptToConstellationSender,
256}
257
258/// <https://drafts.css-houdini.org/worklets/#worklet-global-scope-type>
259#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)]
260pub(crate) enum WorkletGlobalScopeType {
261    /// A servo-specific testing worklet
262    #[cfg(feature = "testbinding")]
263    Test,
264    /// A paint worklet
265    Paint,
266}