Skip to main content

script/dom/workers/
dedicatedworkerglobalscope.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::collections::VecDeque;
7use std::rc::Rc;
8use std::sync::Arc;
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::thread::{self, JoinHandle};
11
12use crossbeam_channel::{Receiver, Sender, unbounded};
13use devtools_traits::DevtoolScriptControlMsg;
14use dom_struct::dom_struct;
15use fonts::FontContext;
16use js::context::JSContext;
17use js::jsapi::{Heap, JSObject};
18use js::jsval::UndefinedValue;
19use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue};
20use net_traits::blob_url_store::UrlWithBlobClaim;
21use net_traits::image_cache::ImageCache;
22use net_traits::policy_container::PolicyContainer;
23use net_traits::request::{
24    CredentialsMode, Destination, InsecureRequestsPolicy, Origin, ParserMetadata,
25    PreloadedResources, Referrer, RequestBuilder, RequestClient, RequestMode,
26};
27use script_bindings::cell::DomRefCell;
28use script_bindings::interfaces::HasOrigin;
29use servo_base::generic_channel::{self, GenericReceiver, GenericSender, RoutedReceiver};
30use servo_base::id::{BrowsingContextId, PipelineId, ScriptEventLoopId, WebViewId};
31use servo_constellation_traits::{
32    ScriptToConstellationMessage, WorkerAnimationFrameTick, WorkerGlobalScopeInit,
33    WorkerScriptLoadOrigin,
34};
35use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
36use style::thread_state::{self, ThreadState};
37
38use crate::conversions::Convert;
39use crate::dom::abstractworker::{MessageData, SimpleWorkerErrorHandler, WorkerScriptMsg};
40use crate::dom::abstractworkerglobalscope::{WorkerEventLoopMethods, run_worker_event_loop};
41use crate::dom::bindings::callback::ExceptionHandling;
42use crate::dom::bindings::codegen::Bindings::AnimationFrameProviderBinding::FrameRequestCallback;
43use crate::dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding;
44use crate::dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding::DedicatedWorkerGlobalScopeMethods;
45use crate::dom::bindings::codegen::Bindings::MessagePortBinding::StructuredSerializeOptions;
46use crate::dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceMethods;
47use crate::dom::bindings::codegen::Bindings::WorkerBinding::{WorkerOptions, WorkerType};
48use crate::dom::bindings::error::{Error, ErrorInfo, ErrorResult, Fallible};
49use crate::dom::bindings::inheritance::Castable;
50use crate::dom::bindings::num::Finite;
51use crate::dom::bindings::refcounted::Trusted;
52use crate::dom::bindings::reflector::DomGlobal;
53use crate::dom::bindings::root::DomRoot;
54use crate::dom::bindings::str::DOMString;
55use crate::dom::bindings::structuredclone;
56use crate::dom::bindings::trace::{CustomTraceable, RootedTraceableBox};
57use crate::dom::csp::Violation;
58use crate::dom::errorevent::ErrorEvent;
59use crate::dom::event::{Event, EventBubbles, EventCancelable};
60use crate::dom::eventtarget::EventTarget;
61use crate::dom::globalscope::GlobalScope;
62use crate::dom::html::htmlscriptelement::Script;
63use crate::dom::messageevent::MessageEvent;
64use crate::dom::types::DebuggerGlobalScope;
65#[cfg(feature = "webgpu")]
66use crate::dom::webgpu::identityhub::IdentityHub;
67use crate::dom::worker::{TrustedWorkerAddress, Worker};
68use crate::dom::workerglobalscope::{ScriptFetchContext, WorkerGlobalScope};
69use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender};
70use crate::modules::script_module::fetch_a_module_script_graph;
71use crate::realms::enter_auto_realm;
72use crate::script_runtime::ScriptThreadEventCategory::WorkerEvent;
73use crate::script_runtime::{IntroductionType, Runtime, ThreadSafeJSContext};
74use crate::tasks::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
75use crate::tasks::task_source::TaskSourceName;
76
77/// Set the `worker` field of a related DedicatedWorkerGlobalScope object to a particular
78/// value for the duration of this object's lifetime. This ensures that the related Worker
79/// object only lives as long as necessary (ie. while events are being executed), while
80/// providing a reference that can be cloned freely.
81pub(crate) struct AutoWorkerReset<'a> {
82    workerscope: &'a DedicatedWorkerGlobalScope,
83    old_worker: Option<TrustedWorkerAddress>,
84}
85
86impl<'a> AutoWorkerReset<'a> {
87    pub(crate) fn new(
88        workerscope: &'a DedicatedWorkerGlobalScope,
89        worker: TrustedWorkerAddress,
90    ) -> AutoWorkerReset<'a> {
91        let old_worker = workerscope.replace_worker(Some(worker));
92        AutoWorkerReset {
93            workerscope,
94            old_worker,
95        }
96    }
97}
98
99impl Drop for AutoWorkerReset<'_> {
100    fn drop(&mut self) {
101        self.workerscope
102            .replace_worker(std::mem::take(&mut self.old_worker));
103    }
104}
105
106/// Messages sent from the owning global.
107pub(crate) enum DedicatedWorkerControlMsg {
108    /// Shutdown the worker.
109    Exit,
110    AnimationFrameProviderUnsupported,
111}
112
113pub(crate) enum DedicatedWorkerScriptMsg {
114    /// Standard message from a worker.
115    CommonWorker(TrustedWorkerAddress, WorkerScriptMsg),
116    /// Wake-up call from the task queue.
117    WakeUp,
118}
119
120pub(crate) enum MixedMessage {
121    Worker(DedicatedWorkerScriptMsg),
122    Devtools(DevtoolScriptControlMsg),
123    Control(DedicatedWorkerControlMsg),
124    AnimationFrameTick(WorkerAnimationFrameTick),
125    Timer,
126}
127
128impl QueuedTaskConversion for DedicatedWorkerScriptMsg {
129    fn task_source_name(&self) -> Option<&TaskSourceName> {
130        let common_worker_msg = match self {
131            DedicatedWorkerScriptMsg::CommonWorker(_, common_worker_msg) => common_worker_msg,
132            _ => return None,
133        };
134        let script_msg = match common_worker_msg {
135            WorkerScriptMsg::Common(script_msg) => script_msg,
136            _ => return None,
137        };
138        match script_msg {
139            CommonScriptMsg::Task(_category, _boxed, _pipeline_id, source_name) => {
140                Some(source_name)
141            },
142            _ => None,
143        }
144    }
145
146    fn pipeline_id(&self) -> Option<PipelineId> {
147        // Workers always return None, since the pipeline_id is only used to check for document activity,
148        // and this check does not apply to worker event-loops.
149        None
150    }
151
152    fn into_queued_task(self) -> Option<QueuedTask> {
153        let (worker, common_worker_msg) = match self {
154            DedicatedWorkerScriptMsg::CommonWorker(worker, common_worker_msg) => {
155                (worker, common_worker_msg)
156            },
157            _ => return None,
158        };
159        let script_msg = match common_worker_msg {
160            WorkerScriptMsg::Common(script_msg) => script_msg,
161            _ => return None,
162        };
163        let (event_category, task, pipeline_id, task_source) = match script_msg {
164            CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => {
165                (category, boxed, pipeline_id, task_source)
166            },
167            _ => return None,
168        };
169        Some(QueuedTask {
170            worker: Some(worker),
171            event_category,
172            task,
173            pipeline_id,
174            task_source,
175        })
176    }
177
178    fn from_queued_task(queued_task: QueuedTask) -> Self {
179        let script_msg = CommonScriptMsg::Task(
180            queued_task.event_category,
181            queued_task.task,
182            queued_task.pipeline_id,
183            queued_task.task_source,
184        );
185        DedicatedWorkerScriptMsg::CommonWorker(
186            queued_task.worker.unwrap(),
187            WorkerScriptMsg::Common(script_msg),
188        )
189    }
190
191    fn inactive_msg() -> Self {
192        // Inactive is only relevant in the context of a browsing-context event-loop.
193        panic!("Workers should never receive messages marked as inactive");
194    }
195
196    fn wake_up_msg() -> Self {
197        DedicatedWorkerScriptMsg::WakeUp
198    }
199
200    fn is_wake_up(&self) -> bool {
201        matches!(self, DedicatedWorkerScriptMsg::WakeUp)
202    }
203}
204
205unsafe_no_jsmanaged_fields!(TaskQueue<DedicatedWorkerScriptMsg>);
206
207// https://html.spec.whatwg.org/multipage/#dedicatedworkerglobalscope
208#[dom_struct]
209pub(crate) struct DedicatedWorkerGlobalScope {
210    workerglobalscope: WorkerGlobalScope,
211    /// The [`WebViewId`] of the `WebView` that this worker is associated with.
212    #[no_trace]
213    webview_id: WebViewId,
214    #[ignore_malloc_size_of = "Defined in std"]
215    task_queue: TaskQueue<DedicatedWorkerScriptMsg>,
216    own_sender: Sender<DedicatedWorkerScriptMsg>,
217    worker: DomRefCell<Option<TrustedWorkerAddress>>,
218    /// Sender to the parent thread.
219    parent_event_loop_sender: ScriptEventLoopSender,
220    #[ignore_malloc_size_of = "ImageCache"]
221    #[no_trace]
222    image_cache: Arc<dyn ImageCache>,
223    #[no_trace]
224    browsing_context: Option<BrowsingContextId>,
225    #[conditional_malloc_size_of]
226    animation_frame_provider_supported: Arc<AtomicBool>,
227    animation_frame_provider_registered: Cell<bool>,
228    /// <https://html.spec.whatwg.org/multipage/#animation-frame-callback-identifier>
229    animation_frame_ident: Cell<u32>,
230    /// Pending animation frame callbacks for a later worker rendering update.
231    #[ignore_malloc_size_of = "closures are hard"]
232    animation_frame_list: DomRefCell<VecDeque<(u32, Rc<FrameRequestCallback>)>>,
233    /// Callbacks snapshotted for the current worker rendering update.
234    #[ignore_malloc_size_of = "closures are hard"]
235    current_animation_frame_list: DomRefCell<VecDeque<(u32, Rc<FrameRequestCallback>)>>,
236    /// Whether we're in the process of running animation callbacks.
237    running_animation_callbacks: Cell<bool>,
238    /// Whether Constellation currently treats this worker as having callbacks.
239    animation_frame_callbacks_active: Cell<bool>,
240    /// A sender for animation frame ticks.
241    #[no_trace]
242    #[ignore_malloc_size_of = "channels are hard"]
243    animation_frame_tick_sender: Option<GenericSender<WorkerAnimationFrameTick>>,
244    /// A receiver for animation frame ticks.
245    #[no_trace]
246    #[ignore_malloc_size_of = "channels are hard"]
247    animation_frame_tick_receiver: Option<RoutedReceiver<WorkerAnimationFrameTick>>,
248    /// A receiver of control messages.
249    #[no_trace]
250    control_receiver: Receiver<DedicatedWorkerControlMsg>,
251    #[no_trace]
252    queued_worker_tasks: DomRefCell<Vec<MessageData>>,
253}
254
255impl WorkerEventLoopMethods for DedicatedWorkerGlobalScope {
256    type WorkerMsg = DedicatedWorkerScriptMsg;
257    type ControlMsg = DedicatedWorkerControlMsg;
258    type Event = MixedMessage;
259
260    fn task_queue(&self) -> &TaskQueue<DedicatedWorkerScriptMsg> {
261        &self.task_queue
262    }
263
264    fn handle_event(&self, event: MixedMessage, cx: &mut JSContext) -> bool {
265        self.handle_mixed_message(event, cx)
266    }
267
268    fn handle_worker_post_event(
269        &self,
270        worker: &TrustedWorkerAddress,
271    ) -> Option<AutoWorkerReset<'_>> {
272        let ar = AutoWorkerReset::new(self, worker.clone());
273        Some(ar)
274    }
275
276    fn from_control_msg(msg: DedicatedWorkerControlMsg) -> MixedMessage {
277        MixedMessage::Control(msg)
278    }
279
280    fn from_worker_msg(msg: DedicatedWorkerScriptMsg) -> MixedMessage {
281        MixedMessage::Worker(msg)
282    }
283
284    fn from_devtools_msg(msg: DevtoolScriptControlMsg) -> MixedMessage {
285        MixedMessage::Devtools(msg)
286    }
287
288    fn from_timer_msg() -> MixedMessage {
289        MixedMessage::Timer
290    }
291
292    fn from_animation_frame_tick_msg(msg: WorkerAnimationFrameTick) -> Option<MixedMessage> {
293        Some(MixedMessage::AnimationFrameTick(msg))
294    }
295
296    fn animation_frame_tick_receiver(&self) -> Option<&RoutedReceiver<WorkerAnimationFrameTick>> {
297        self.animation_frame_tick_receiver.as_ref()
298    }
299
300    fn control_receiver(&self) -> &Receiver<DedicatedWorkerControlMsg> {
301        &self.control_receiver
302    }
303}
304
305impl DedicatedWorkerGlobalScope {
306    #[allow(clippy::too_many_arguments)]
307    fn new_inherited(
308        init: WorkerGlobalScopeInit,
309        webview_id: WebViewId,
310        worker_name: DOMString,
311        worker_type: WorkerType,
312        worker_url: ServoUrl,
313        from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
314        runtime: Runtime,
315        parent_event_loop_sender: ScriptEventLoopSender,
316        own_sender: Sender<DedicatedWorkerScriptMsg>,
317        receiver: Receiver<DedicatedWorkerScriptMsg>,
318        closing: Arc<AtomicBool>,
319        animation_frame_provider_supported: Arc<AtomicBool>,
320        image_cache: Arc<dyn ImageCache>,
321        browsing_context: Option<BrowsingContextId>,
322        animation_frame_tick_sender: Option<GenericSender<WorkerAnimationFrameTick>>,
323        animation_frame_tick_receiver: Option<RoutedReceiver<WorkerAnimationFrameTick>>,
324        #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
325        control_receiver: Receiver<DedicatedWorkerControlMsg>,
326        insecure_requests_policy: InsecureRequestsPolicy,
327        font_context: Arc<FontContext>,
328    ) -> DedicatedWorkerGlobalScope {
329        DedicatedWorkerGlobalScope {
330            workerglobalscope: WorkerGlobalScope::new_inherited(
331                init,
332                worker_name,
333                worker_type,
334                worker_url,
335                runtime,
336                from_devtools_receiver,
337                closing,
338                #[cfg(feature = "webgpu")]
339                gpu_id_hub,
340                insecure_requests_policy,
341                font_context,
342                None,
343            ),
344            webview_id,
345            task_queue: TaskQueue::new(receiver, own_sender.clone()),
346            own_sender,
347            parent_event_loop_sender,
348            worker: DomRefCell::new(None),
349            image_cache,
350            browsing_context,
351            animation_frame_provider_supported,
352            animation_frame_provider_registered: Cell::new(false),
353            animation_frame_ident: Cell::new(0),
354            animation_frame_list: DomRefCell::new(VecDeque::new()),
355            current_animation_frame_list: DomRefCell::new(VecDeque::new()),
356            running_animation_callbacks: Cell::new(false),
357            animation_frame_callbacks_active: Cell::new(false),
358            animation_frame_tick_sender,
359            animation_frame_tick_receiver,
360            control_receiver,
361            queued_worker_tasks: Default::default(),
362        }
363    }
364
365    #[expect(clippy::too_many_arguments)]
366    pub(crate) fn new(
367        init: WorkerGlobalScopeInit,
368        webview_id: WebViewId,
369        worker_name: DOMString,
370        worker_type: WorkerType,
371        worker_url: ServoUrl,
372        from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
373        runtime: Runtime,
374        parent_event_loop_sender: ScriptEventLoopSender,
375        own_sender: Sender<DedicatedWorkerScriptMsg>,
376        receiver: Receiver<DedicatedWorkerScriptMsg>,
377        closing: Arc<AtomicBool>,
378        animation_frame_provider_supported: Arc<AtomicBool>,
379        image_cache: Arc<dyn ImageCache>,
380        browsing_context: Option<BrowsingContextId>,
381        animation_frame_tick_sender: Option<GenericSender<WorkerAnimationFrameTick>>,
382        animation_frame_tick_receiver: Option<RoutedReceiver<WorkerAnimationFrameTick>>,
383        #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
384        control_receiver: Receiver<DedicatedWorkerControlMsg>,
385        insecure_requests_policy: InsecureRequestsPolicy,
386        font_context: Arc<FontContext>,
387        debugger_global: &DebuggerGlobalScope,
388        cx: &mut js::context::JSContext,
389    ) -> DomRoot<DedicatedWorkerGlobalScope> {
390        let scope = Box::new(DedicatedWorkerGlobalScope::new_inherited(
391            init,
392            webview_id,
393            worker_name,
394            worker_type,
395            worker_url,
396            from_devtools_receiver,
397            runtime,
398            parent_event_loop_sender,
399            own_sender,
400            receiver,
401            closing,
402            animation_frame_provider_supported,
403            image_cache,
404            browsing_context,
405            animation_frame_tick_sender,
406            animation_frame_tick_receiver,
407            #[cfg(feature = "webgpu")]
408            gpu_id_hub,
409            control_receiver,
410            insecure_requests_policy,
411            font_context,
412        ));
413        let scope = DedicatedWorkerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(
414            cx,
415            &scope.origin(),
416            scope,
417        );
418        scope
419            .upcast::<WorkerGlobalScope>()
420            .init_debugger_global(debugger_global, cx);
421
422        scope
423    }
424
425    /// <https://html.spec.whatwg.org/multipage/#run-a-worker>
426    #[expect(unsafe_code)]
427    #[allow(clippy::too_many_arguments)]
428    pub(crate) fn run_worker_scope(
429        mut init: WorkerGlobalScopeInit,
430        webview_id: WebViewId,
431        worker_url: UrlWithBlobClaim,
432        from_devtools_receiver: GenericReceiver<DevtoolScriptControlMsg>,
433        worker: TrustedWorkerAddress,
434        parent_event_loop_sender: ScriptEventLoopSender,
435        own_sender: Sender<DedicatedWorkerScriptMsg>,
436        receiver: Receiver<DedicatedWorkerScriptMsg>,
437        worker_load_origin: WorkerScriptLoadOrigin,
438        worker_options: &WorkerOptions,
439        closing: Arc<AtomicBool>,
440        animation_frame_provider_supported: Arc<AtomicBool>,
441        image_cache: Arc<dyn ImageCache>,
442        browsing_context: Option<BrowsingContextId>,
443        #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
444        control_receiver: Receiver<DedicatedWorkerControlMsg>,
445        context_sender: Sender<ThreadSafeJSContext>,
446        insecure_requests_policy: InsecureRequestsPolicy,
447        policy_container: PolicyContainer,
448        font_context: Arc<FontContext>,
449    ) -> JoinHandle<()> {
450        let event_loop_id = ScriptEventLoopId::installed()
451            .expect("Should always be in a ScriptThread or in a dedicated worker");
452        let current_global = GlobalScope::current().expect("No current global object");
453        let origin = current_global.origin().immutable().clone();
454        let referrer = current_global.get_referrer();
455        let parent = current_global.runtime_handle();
456        let current_global_ancestor_trustworthy = current_global.has_trustworthy_ancestor_origin();
457        let is_secure_context = current_global.is_secure_context();
458        let is_nested_browsing_context = current_global.is_nested_browsing_context();
459
460        let worker_type = worker_options.type_;
461        let worker_name = worker_options.name.to_string();
462        let credentials = worker_options.credentials.convert();
463
464        thread::Builder::new()
465            .name(format!("WW:{}", worker_url.debug_compact()))
466            .spawn(move || {
467                thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
468                ScriptEventLoopId::install(event_loop_id);
469
470                let WorkerScriptLoadOrigin {
471                    referrer_url,
472                    pipeline_id,
473                    ..
474                } = worker_load_origin;
475
476                let referrer = referrer_url.map(Referrer::ReferrerUrl).unwrap_or(referrer);
477
478                let request_client = RequestClient {
479                    preloaded_resources: PreloadedResources::default(),
480                    policy_container,
481                    origin: Origin::Origin(origin.clone()),
482                    is_nested_browsing_context,
483                    insecure_requests_policy,
484                    has_trustworthy_ancestor_origin: current_global_ancestor_trustworthy,
485                };
486
487                let event_loop_sender = ScriptEventLoopSender::DedicatedWorker {
488                    sender: own_sender.clone(),
489                    main_thread_worker: worker.clone(),
490                };
491
492                let runtime = unsafe {
493                    Runtime::new_with_parent(Some(parent), Some(event_loop_sender.clone()))
494                };
495                // SAFETY: We are in a new thread, so this first cx.
496                // It is OK to have it separated of runtime here,
497                // because it will never outlive it (runtime destruction happens at the end of this function)
498                let mut cx = unsafe { runtime.cx() };
499                let cx = &mut cx;
500                let debugger_global = DebuggerGlobalScope::new(
501                    pipeline_id,
502                    init.to_devtools_sender.clone(),
503                    init.from_devtools_sender
504                        .clone()
505                        .expect("Guaranteed by Worker::Constructor"),
506                    init.mem_profiler_chan.clone(),
507                    init.time_profiler_chan.clone(),
508                    init.script_to_constellation_chan.clone(),
509                    init.script_to_embedder_chan.clone(),
510                    init.resource_threads.clone(),
511                    init.storage_threads.clone(),
512                    #[cfg(feature = "webgpu")]
513                    gpu_id_hub.clone(),
514                    cx,
515                );
516                debugger_global.execute(cx);
517
518                let context_for_interrupt = runtime.thread_safe_js_context();
519                let _ = context_sender.send(context_for_interrupt);
520
521                let devtools_mpsc_port = from_devtools_receiver.route_preserving_errors();
522                let animation_frame_channel = init
523                    .animation_frame_provider_supported
524                    .then(|| generic_channel::channel().expect("Failed to create generic channel"));
525                let (animation_frame_tick_sender, animation_frame_tick_receiver) =
526                    match animation_frame_channel {
527                        Some((sender, receiver)) => {
528                            (Some(sender), Some(receiver.route_preserving_errors()))
529                        },
530                        None => (None, None),
531                    };
532
533                // Step 8 "Set up a worker environment settings object [...]"
534                //
535                // <https://html.spec.whatwg.org/multipage/#script-settings-for-workers>
536                //
537                // > The origin: Return a unique opaque origin if `worker global
538                // > scope`'s url's scheme is "data", and `inherited origin`
539                // > otherwise.
540                if worker_url.scheme() == "data" {
541                    // Workers created from a data: url are secure if they were created from secure contexts
542                    if is_secure_context {
543                        init.origin = ImmutableOrigin::new_opaque_data_url_worker();
544                    } else {
545                        init.origin = ImmutableOrigin::new_opaque();
546                    }
547                }
548
549                let worker_id = init.worker_id;
550                let devtools_enabled = init.to_devtools_sender.is_some();
551                let global = DedicatedWorkerGlobalScope::new(
552                    init,
553                    webview_id,
554                    worker_name.into(),
555                    worker_type,
556                    worker_url.url(),
557                    devtools_mpsc_port,
558                    runtime,
559                    parent_event_loop_sender,
560                    own_sender,
561                    receiver,
562                    closing,
563                    animation_frame_provider_supported,
564                    image_cache,
565                    browsing_context,
566                    animation_frame_tick_sender,
567                    animation_frame_tick_receiver,
568                    #[cfg(feature = "webgpu")]
569                    gpu_id_hub,
570                    control_receiver,
571                    insecure_requests_policy,
572                    font_context,
573                    &debugger_global,
574                    cx,
575                );
576
577                if devtools_enabled {
578                    debugger_global.fire_add_debuggee(
579                        cx,
580                        global.upcast(),
581                        pipeline_id,
582                        Some(worker_id),
583                    );
584                }
585                let scope = global.upcast::<WorkerGlobalScope>();
586                let global_scope = global.upcast::<GlobalScope>();
587
588                // Step 12. Obtain script by switching on options["type"]:
589                {
590                    let _ar = AutoWorkerReset::new(&global, worker.clone());
591                    match worker_type {
592                        WorkerType::Classic => {
593                            fetch_a_classic_worker_script(
594                                scope,
595                                worker_url,
596                                request_client,
597                                Destination::Worker,
598                                Some(webview_id),
599                                referrer,
600                            );
601                        },
602                        WorkerType::Module => {
603                            let worker_scope = DomRoot::from_ref(scope);
604                            fetch_a_module_script_graph(
605                                cx,
606                                global_scope,
607                                worker_url,
608                                request_client,
609                                Destination::Worker,
610                                referrer,
611                                credentials,
612                                Some(IntroductionType::WORKER),
613                                move |cx, module_tree| {
614                                    worker_scope.on_complete(cx, module_tree.map(Script::Module));
615                                },
616                            );
617                        },
618                    }
619
620                    let reporter_name = format!("dedicated-worker-reporter-{}", worker_id);
621                    scope
622                        .upcast::<GlobalScope>()
623                        .mem_profiler_chan()
624                        .run_with_memory_reporting(
625                            || {
626                                // Step 27, Run the responsible event loop specified
627                                // by inside settings until it is destroyed.
628                                // The worker processing model remains on this step
629                                // until the event loop is destroyed,
630                                // which happens after the closing flag is set to true.
631                                while !scope.is_closing() {
632                                    run_worker_event_loop(&*global, Some(&worker), cx);
633                                }
634                            },
635                            reporter_name,
636                            event_loop_sender,
637                            CommonScriptMsg::CollectReports,
638                        );
639                }
640
641                // Drop worker rAF state before destroying the JS runtime.
642                global.clear_animation_frame_callbacks_and_unregister();
643                scope.clear_js_runtime();
644            })
645            .expect("Thread spawning failed")
646    }
647
648    pub(crate) fn webview_id(&self) -> WebViewId {
649        self.webview_id
650    }
651
652    pub(crate) fn animation_frame_provider_supported(&self) -> bool {
653        self.animation_frame_provider_supported
654            .load(Ordering::SeqCst)
655    }
656
657    pub(crate) fn animation_frame_provider_supported_flag(&self) -> Arc<AtomicBool> {
658        self.animation_frame_provider_supported.clone()
659    }
660
661    fn register_animation_frame_provider(&self) {
662        if !self.animation_frame_provider_supported() {
663            return;
664        }
665
666        let Some(sender) = self.animation_frame_tick_sender.clone() else {
667            return;
668        };
669
670        if self.animation_frame_provider_registered.replace(true) {
671            return;
672        }
673
674        let worker_id = self.upcast::<WorkerGlobalScope>().worker_id();
675        log::debug!(
676            "Registering dedicated worker animation frame provider: worker={worker_id:?} ---->"
677        );
678        let _ = self
679            .upcast::<GlobalScope>()
680            .script_to_constellation_chan()
681            .send(
682                ScriptToConstellationMessage::RegisterWorkerAnimationFrameProvider(
683                    worker_id, sender,
684                ),
685            );
686    }
687
688    fn unregister_animation_frame_provider(&self) {
689        if !self.animation_frame_provider_registered.replace(false) {
690            return;
691        }
692
693        self.animation_frame_callbacks_active.set(false);
694        let worker_id = self.upcast::<WorkerGlobalScope>().worker_id();
695        log::debug!(
696            "Unregistering dedicated worker animation frame provider: worker={worker_id:?} ---->"
697        );
698        let _ = self
699            .upcast::<GlobalScope>()
700            .script_to_constellation_chan()
701            .send(ScriptToConstellationMessage::UnregisterWorkerAnimationFrameProvider(worker_id));
702    }
703
704    fn send_animation_frame_callbacks_state(&self, active: bool) {
705        if !self.animation_frame_provider_supported() {
706            return;
707        }
708
709        let worker_id = self.upcast::<WorkerGlobalScope>().worker_id();
710        log::debug!(
711            "Sending dedicated worker animation frame state: worker={worker_id:?}, active={active} ---->",
712        );
713        let _ = self
714            .upcast::<GlobalScope>()
715            .script_to_constellation_chan()
716            .send(
717                ScriptToConstellationMessage::ChangeWorkerAnimationFrameProviderState(
718                    worker_id, active,
719                ),
720            );
721    }
722
723    fn set_animation_frame_callbacks_active(&self, active: bool) {
724        if self.animation_frame_callbacks_active.replace(active) == active {
725            return;
726        }
727
728        self.send_animation_frame_callbacks_state(active);
729    }
730
731    fn remove_animation_frame_callback_from(
732        list: &DomRefCell<VecDeque<(u32, Rc<FrameRequestCallback>)>>,
733        ident: u32,
734    ) {
735        let mut list = list.borrow_mut();
736        if let Some(position) = list.iter().position(|(handle, _)| *handle == ident) {
737            list.remove(position);
738        }
739    }
740
741    fn has_animation_frame_callbacks(&self) -> bool {
742        !self.animation_frame_list.borrow().is_empty() ||
743            !self.current_animation_frame_list.borrow().is_empty()
744    }
745
746    /// <https://html.spec.whatwg.org/multipage/#dom-animationframeprovider-requestanimationframe>
747    pub(crate) fn request_animation_frame(
748        &self,
749        callback: Rc<FrameRequestCallback>,
750    ) -> Fallible<u32> {
751        // Step 1. If this is not supported, then throw a "NotSupportedError" DOMException.
752        if !self.animation_frame_provider_supported() {
753            return Err(Error::NotSupported(Some(
754                "Animation frame callbacks are not supported for this worker".to_owned(),
755            )));
756        }
757
758        self.register_animation_frame_provider();
759
760        // Step 2. Let target be this's target object.
761        // Step 3. Increment target's animation frame callback identifier by one,
762        // and let handle be the result.
763        let ident = self.animation_frame_ident.get() + 1;
764        self.animation_frame_ident.set(ident);
765
766        // Step 4. Let callbacks be target's map of animation frame callbacks.
767        // Step 5. Set callbacks[handle] to callback.
768        self.animation_frame_list
769            .borrow_mut()
770            .push_back((ident, callback));
771        log::debug!("Queued dedicated worker animation frame callback: handle={ident} ---->");
772        self.set_animation_frame_callbacks_active(true);
773
774        // Step 6. Return handle.
775        Ok(ident)
776    }
777
778    /// <https://html.spec.whatwg.org/multipage/#dom-animationframeprovider-cancelanimationframe>
779    pub(crate) fn cancel_animation_frame(&self, ident: u32) -> ErrorResult {
780        // Step 1. If this is not supported, then throw a "NotSupportedError" DOMException.
781        if !self.animation_frame_provider_supported() {
782            return Err(Error::NotSupported(Some(
783                "Animation frame callbacks are not supported for this worker".to_owned(),
784            )));
785        }
786
787        // Step 2. Let callbacks be this's target object's map of animation frame callbacks.
788        // Step 3. Remove callbacks[handle].
789        Self::remove_animation_frame_callback_from(&self.animation_frame_list, ident);
790
791        Self::remove_animation_frame_callback_from(&self.current_animation_frame_list, ident);
792        log::debug!("Cancelled dedicated worker animation frame callback: handle={ident} ---->");
793
794        if !self.running_animation_callbacks.get() && !self.has_animation_frame_callbacks() {
795            self.set_animation_frame_callbacks_active(false);
796        }
797
798        Ok(())
799    }
800
801    /// <https://html.spec.whatwg.org/multipage/#run-the-animation-frame-callbacks>
802    pub(crate) fn run_the_animation_frame_callbacks(&self, cx: &mut JSContext) {
803        if !self.animation_frame_provider_supported() ||
804            self.upcast::<WorkerGlobalScope>().is_closing()
805        {
806            return;
807        }
808
809        // Step 1. Let callbacks be target's map of animation frame callbacks.
810        // Step 2. Let callbackHandles be the result of getting the keys of callbacks.
811        let callback_count = self.animation_frame_list.borrow().len();
812        log::debug!("Running dedicated worker animation frame callbacks: count={callback_count}");
813        {
814            let mut pending = self.animation_frame_list.borrow_mut();
815            let mut current = self.current_animation_frame_list.borrow_mut();
816            for _ in 0..callback_count {
817                if let Some(callback) = pending.pop_front() {
818                    current.push_back(callback);
819                }
820            }
821        }
822
823        self.running_animation_callbacks.set(true);
824        let timing = self.upcast::<GlobalScope>().performance(cx).Now();
825
826        // Step 3. For each handle in callbackHandles, if handle exists in callbacks:
827        for _ in 0..callback_count {
828            // Step 3.1. Let callback be callbacks[handle].
829            // Step 3.2. Remove callbacks[handle].
830            let callback = self
831                .current_animation_frame_list
832                .borrow_mut()
833                .pop_front()
834                .map(|(_, callback)| callback);
835
836            if let Some(callback) = callback {
837                // Step 3.3. Invoke callback with « now » and "`report`".
838                let _ = callback.Call__(cx, Finite::wrap(*timing), ExceptionHandling::Report);
839            }
840        }
841
842        self.current_animation_frame_list.borrow_mut().clear();
843        self.running_animation_callbacks.set(false);
844
845        if !self.has_animation_frame_callbacks() {
846            self.set_animation_frame_callbacks_active(false);
847        } else {
848            // Acknowledge the consumed worker tick while staying active; Paint
849            // will drive the next refresh tick.
850            self.send_animation_frame_callbacks_state(true);
851        }
852    }
853
854    pub(crate) fn clear_animation_frame_callbacks_and_unregister(&self) {
855        // Worker shutdown drops pending rAF callbacks and unregisters the tick target.
856        let worker_id = self.upcast::<WorkerGlobalScope>().worker_id();
857        log::debug!(
858            "Clearing dedicated worker animation frame callbacks: worker={worker_id:?} ---->"
859        );
860        self.animation_frame_list.borrow_mut().clear();
861        self.current_animation_frame_list.borrow_mut().clear();
862        self.running_animation_callbacks.set(false);
863        self.animation_frame_callbacks_active.set(false);
864        self.unregister_animation_frame_provider();
865    }
866
867    /// The non-None value of the `worker` field can contain a rooted [`TrustedWorkerAddress`]
868    /// version of the main thread's worker object. This is set while handling messages and then
869    /// unset otherwise, ensuring that the main thread object can be garbage collected. See
870    /// [`AutoWorkerReset`].
871    fn replace_worker(
872        &self,
873        new_worker: Option<TrustedWorkerAddress>,
874    ) -> Option<TrustedWorkerAddress> {
875        let old_worker = std::mem::replace(&mut *self.worker.borrow_mut(), new_worker);
876
877        // The `TaskManager` maintains a handle to this `DedicatedWorkerGlobalScope`'s
878        // event_loop_sender, which might in turn have a `TrustedWorkerAddress` rooting of the main
879        // thread's worker, which prevents garbage collection. Resetting it here ensures that
880        // garbage collection of the main thread object can happen again (assuming the new `worker`
881        // is `None`).
882        self.upcast::<GlobalScope>()
883            .task_manager()
884            .set_sender(self.event_loop_sender());
885
886        old_worker
887    }
888
889    pub(crate) fn image_cache(&self) -> Arc<dyn ImageCache> {
890        self.image_cache.clone()
891    }
892
893    pub(crate) fn event_loop_sender(&self) -> Option<ScriptEventLoopSender> {
894        Some(ScriptEventLoopSender::DedicatedWorker {
895            sender: self.own_sender.clone(),
896            main_thread_worker: self.worker.borrow().clone()?,
897        })
898    }
899
900    pub(crate) fn new_script_pair(&self) -> (ScriptEventLoopSender, ScriptEventLoopReceiver) {
901        let (sender, receiver) = unbounded();
902        let main_thread_worker = self.worker.borrow().as_ref().unwrap().clone();
903        (
904            ScriptEventLoopSender::DedicatedWorker {
905                sender,
906                main_thread_worker,
907            },
908            ScriptEventLoopReceiver::DedicatedWorker(receiver),
909        )
910    }
911
912    pub(crate) fn fire_queued_messages(&self, cx: &mut JSContext) {
913        let queue: Vec<_> = self.queued_worker_tasks.borrow_mut().drain(..).collect();
914        for msg in queue {
915            if self.upcast::<WorkerGlobalScope>().is_closing() {
916                return;
917            }
918            self.dispatch_message_event(cx, msg);
919        }
920    }
921
922    fn dispatch_message_event(&self, cx: &mut JSContext, msg: MessageData) {
923        let scope = self.upcast::<WorkerGlobalScope>();
924        let target = self.upcast();
925        let mut realm = enter_auto_realm(cx, self);
926        let cx = &mut realm;
927        rooted!(&in(cx) let mut message = UndefinedValue());
928        if let Ok(ports) =
929            structuredclone::read(cx, scope.upcast(), *msg.data, message.handle_mut())
930        {
931            MessageEvent::dispatch_jsval(
932                cx,
933                target,
934                scope.upcast(),
935                message.handle(),
936                Some(&msg.origin.ascii_serialization()),
937                None,
938                ports,
939            );
940        } else {
941            MessageEvent::dispatch_error(cx, target, scope.upcast());
942        }
943    }
944
945    fn handle_script_event(&self, msg: WorkerScriptMsg, cx: &mut JSContext) {
946        match msg {
947            WorkerScriptMsg::DOMMessage(message_data) => {
948                if self.upcast::<WorkerGlobalScope>().is_execution_ready() {
949                    self.dispatch_message_event(cx, message_data);
950                } else {
951                    self.queued_worker_tasks.borrow_mut().push(message_data);
952                }
953            },
954            WorkerScriptMsg::Common(msg) => {
955                self.upcast::<WorkerGlobalScope>().process_event(msg, cx);
956            },
957        }
958    }
959
960    /// <https://html.spec.whatwg.org/multipage/#event-loop-processing-model>
961    fn handle_mixed_message(&self, msg: MixedMessage, cx: &mut JSContext) -> bool {
962        if self.upcast::<WorkerGlobalScope>().is_closing() {
963            return false;
964        }
965        // FIXME(#26324): `self.worker` is None in devtools messages.
966        match msg {
967            MixedMessage::Devtools(msg) => self
968                .upcast::<WorkerGlobalScope>()
969                .handle_devtools_message(msg, cx),
970            MixedMessage::Worker(DedicatedWorkerScriptMsg::CommonWorker(linked_worker, msg)) => {
971                let _ar = AutoWorkerReset::new(self, linked_worker);
972                self.handle_script_event(msg, cx);
973            },
974            MixedMessage::Worker(DedicatedWorkerScriptMsg::WakeUp) => {},
975            MixedMessage::Control(DedicatedWorkerControlMsg::Exit) => {
976                return false;
977            },
978            MixedMessage::Control(DedicatedWorkerControlMsg::AnimationFrameProviderUnsupported) => {
979                self.clear_animation_frame_callbacks_and_unregister();
980                self.upcast::<GlobalScope>()
981                    .disable_owned_worker_animation_frame_providers();
982            },
983            MixedMessage::AnimationFrameTick(_) => {
984                // Step 6.1.2. Run the animation frame callbacks for that
985                // DedicatedWorkerGlobalScope, passing in now as the timestamp.
986                self.run_the_animation_frame_callbacks(cx);
987            },
988            MixedMessage::Timer => {},
989        }
990        true
991    }
992
993    /// Step 7.2 of <https://html.spec.whatwg.org/multipage/#report-an-exception>
994    pub(crate) fn forward_error_to_worker_object(&self, error_info: ErrorInfo) {
995        // Step 7.2.1. Let workerObject be the Worker object associated with global.
996        let worker = self.worker.borrow().as_ref().unwrap().clone();
997        let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
998        let task = Box::new(task!(forward_error_to_worker_object: move |cx| {
999            let worker = worker.root();
1000            let global = worker.global();
1001
1002            // Step 7.2.2. Set notHandled to the result of firing an event named error at workerObject, using ErrorEvent,
1003            // with the cancelable attribute initialized to true, and additional attributes initialized according to errorInfo.
1004            let event = ErrorEvent::new(
1005                cx,
1006                &global,
1007                atom!("error"),
1008                EventBubbles::DoesNotBubble,
1009                EventCancelable::Cancelable,
1010                error_info.message.as_str().into(),
1011                error_info.filename.as_str().into(),
1012                error_info.lineno,
1013                error_info.column,
1014                HandleValue::null(),
1015            );
1016
1017            // Step 7.2.3. If notHandled is true, then report exception for workerObject's relevant global object with omitError set to true.
1018            if event.upcast::<Event>().fire(cx, worker.upcast::<EventTarget>()) {
1019                global.report_an_error(cx, error_info, HandleValue::null());
1020            }
1021        }));
1022        self.parent_event_loop_sender
1023            .send(CommonScriptMsg::Task(
1024                WorkerEvent,
1025                task,
1026                Some(pipeline_id),
1027                TaskSourceName::DOMManipulation,
1028            ))
1029            .unwrap();
1030    }
1031
1032    /// <https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage>
1033    fn post_message_impl(
1034        &self,
1035        cx: &mut JSContext,
1036        message: HandleValue,
1037        transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
1038    ) -> ErrorResult {
1039        let data = structuredclone::write(cx, message, Some(transfer))?;
1040        let worker = self.worker.borrow().as_ref().unwrap().clone();
1041        let global_scope = self.upcast::<GlobalScope>();
1042        let pipeline_id = global_scope.pipeline_id();
1043        let task = Box::new(task!(post_worker_message: move |cx| {
1044            Worker::handle_message(worker, data, cx);
1045        }));
1046        self.parent_event_loop_sender
1047            .send(CommonScriptMsg::Task(
1048                WorkerEvent,
1049                task,
1050                Some(pipeline_id),
1051                TaskSourceName::DOMManipulation,
1052            ))
1053            .expect("Sending to parent failed");
1054        Ok(())
1055    }
1056
1057    pub(crate) fn browsing_context(&self) -> Option<BrowsingContextId> {
1058        self.browsing_context
1059    }
1060
1061    pub(crate) fn report_csp_violations(&self, violations: Vec<Violation>) {
1062        let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
1063        self.parent_event_loop_sender
1064            .send(CommonScriptMsg::ReportCspViolations(
1065                pipeline_id,
1066                violations,
1067            ))
1068            .unwrap_or_else(|error| {
1069                log::warn!("Failed to send CSP violations to parent event loop: {error}");
1070            });
1071    }
1072
1073    pub(crate) fn forward_simple_error_at_worker(&self) {
1074        let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
1075        let worker = self.worker.borrow().clone().expect("worker must be set");
1076        self.parent_event_loop_sender
1077            .send(CommonScriptMsg::Task(
1078                WorkerEvent,
1079                Box::new(SimpleWorkerErrorHandler::new(worker)),
1080                Some(pipeline_id),
1081                TaskSourceName::DOMManipulation,
1082            ))
1083            .expect("Sending to parent failed");
1084    }
1085}
1086
1087/// <https://html.spec.whatwg.org/multipage/#fetch-a-classic-worker-script>
1088pub(crate) fn fetch_a_classic_worker_script(
1089    workerscope: &WorkerGlobalScope,
1090    url_with_blob_lock: UrlWithBlobClaim,
1091    fetch_client: RequestClient,
1092    destination: Destination,
1093    webview_id: Option<WebViewId>,
1094    referrer: Referrer,
1095) {
1096    let policy_container = fetch_client.policy_container.clone();
1097
1098    // Step 1. Let request be a new request whose URL is url,
1099    let request = RequestBuilder::new(webview_id, url_with_blob_lock.clone(), referrer)
1100        // client is fetchClient,
1101        .client(fetch_client)
1102        .pipeline_id(Some(workerscope.pipeline_id()))
1103        // destination is destination,
1104        .destination(destination)
1105        // TODO initiator type is "other",
1106        // mode is "same-origin",
1107        .mode(RequestMode::SameOrigin)
1108        // credentials mode is "same-origin",
1109        .credentials_mode(CredentialsMode::CredentialsSameOrigin)
1110        // parser metadata is "not parser-inserted",
1111        .parser_metadata(ParserMetadata::NotParserInserted)
1112        // and whose use-URL-credentials flag is set.
1113        .use_url_credentials(true);
1114
1115    let context = ScriptFetchContext::new(
1116        Trusted::new(workerscope),
1117        url_with_blob_lock.url(),
1118        policy_container,
1119    );
1120    let global = workerscope.upcast::<GlobalScope>();
1121    let task_source = global.task_manager().networking_task_source().to_sendable();
1122    global.fetch(request, context, task_source);
1123}
1124
1125impl DedicatedWorkerGlobalScopeMethods<crate::DomTypeHolder> for DedicatedWorkerGlobalScope {
1126    /// <https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-name>
1127    fn Name(&self) -> DOMString {
1128        self.workerglobalscope.worker_name()
1129    }
1130
1131    /// <https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage>
1132    fn PostMessage(
1133        &self,
1134        cx: &mut JSContext,
1135        message: HandleValue,
1136        transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
1137    ) -> ErrorResult {
1138        self.post_message_impl(cx, message, transfer)
1139    }
1140
1141    /// <https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage>
1142    fn PostMessage_(
1143        &self,
1144        cx: &mut JSContext,
1145        message: HandleValue,
1146        options: RootedTraceableBox<StructuredSerializeOptions>,
1147    ) -> ErrorResult {
1148        let mut rooted = CustomAutoRooter::new(
1149            options
1150                .transfer
1151                .iter()
1152                .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get())
1153                .collect(),
1154        );
1155        #[expect(unsafe_code)]
1156        let guard = unsafe { CustomAutoRooterGuard::new(cx.raw_cx(), &mut rooted) };
1157        self.post_message_impl(cx, message, guard)
1158    }
1159
1160    /// <https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-close>
1161    fn Close(&self) {
1162        // Step 2
1163        self.upcast::<WorkerGlobalScope>().close();
1164    }
1165
1166    /// <https://html.spec.whatwg.org/multipage/#dom-animationframeprovider-requestanimationframe>
1167    fn RequestAnimationFrame(&self, callback: Rc<FrameRequestCallback>) -> Fallible<u32> {
1168        self.request_animation_frame(callback)
1169    }
1170
1171    /// <https://html.spec.whatwg.org/multipage/#dom-animationframeprovider-cancelanimationframe>
1172    fn CancelAnimationFrame(&self, ident: u32) -> ErrorResult {
1173        self.cancel_animation_frame(ident)
1174    }
1175
1176    // https://html.spec.whatwg.org/multipage/#handler-dedicatedworkerglobalscope-onmessage
1177    event_handler!(message, GetOnmessage, SetOnmessage);
1178
1179    // https://html.spec.whatwg.org/multipage/#handler-dedicatedworkerglobalscope-onmessageerror
1180    event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror);
1181}
1182
1183impl HasOrigin for DedicatedWorkerGlobalScope {
1184    fn origin(&self) -> MutableOrigin {
1185        self.upcast::<WorkerGlobalScope>().origin()
1186    }
1187}