Skip to main content

script/dom/workers/
workerglobalscope.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
5#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
6
7use std::cell::{OnceCell, RefCell, RefMut};
8use std::collections::HashSet;
9use std::default::Default;
10use std::rc::Rc;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::time::Duration;
14
15use bytes::{Bytes, BytesMut};
16use content_security_policy::CspList;
17use devtools_traits::{DevtoolScriptControlMsg, WorkerId};
18use dom_struct::dom_struct;
19use encoding_rs::UTF_8;
20use fonts::FontContext;
21use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
22use js::context::JSContext;
23use js::conversions::ToJSValConvertible;
24use js::jsapi::{Heap, JSContext as RawJSContext, Value};
25use js::realm::CurrentRealm;
26use js::rust::{HandleValue, MutableHandleValue, ParentRuntime};
27use mime::Mime;
28use net_traits::blob_url_store::UrlWithBlobClaim;
29use net_traits::policy_container::PolicyContainer;
30use net_traits::request::{
31    CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata, RequestBuilder, RequestId,
32};
33use net_traits::{FetchMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming};
34use profile_traits::mem::{ProcessReports, perform_memory_report};
35use script_bindings::cell::{DomRefCell, Ref};
36use script_bindings::conversions::root_from_handlevalue;
37use script_bindings::reflector::DomObject;
38use script_bindings::root::rooted_heap_handle;
39use script_bindings::trace::CustomTraceable;
40use servo_base::cross_process_instant::CrossProcessInstant;
41use servo_base::generic_channel::{GenericSend, GenericSender, RoutedReceiver};
42use servo_base::id::{PipelineId, PipelineNamespace};
43#[cfg(feature = "webgl")]
44use servo_canvas_traits::webgl::WebGLChan;
45use servo_constellation_traits::WorkerGlobalScopeInit;
46use servo_url::{MutableOrigin, ServoUrl};
47use timers::TimerScheduler;
48use uuid::Uuid;
49
50use crate::dom::Window;
51use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{
52    ImageBitmapOptions, ImageBitmapSource,
53};
54use crate::dom::bindings::codegen::Bindings::MessagePortBinding::StructuredSerializeOptions;
55use crate::dom::bindings::codegen::Bindings::ReportingObserverBinding::Report;
56use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestInit;
57use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction;
58use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType;
59use crate::dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods;
60use crate::dom::bindings::codegen::UnionTypes::{
61    RequestOrUSVString, TrustedScriptOrString, TrustedScriptOrStringOrFunction,
62    TrustedScriptURLOrUSVString,
63};
64use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
65use crate::dom::bindings::inheritance::Castable;
66use crate::dom::bindings::refcounted::Trusted;
67use crate::dom::bindings::reflector::DomGlobal;
68use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
69use crate::dom::bindings::str::{DOMString, USVString};
70use crate::dom::bindings::trace::{HashMapTracedValues, RootedTraceableBox};
71use crate::dom::bindings::utils::define_all_exposed_interfaces;
72#[cfg(feature = "webcrypto")]
73use crate::dom::crypto::Crypto;
74use crate::dom::csp::{GlobalCspReporting, Violation, parse_csp_list_from_metadata};
75use crate::dom::debugger::debuggerglobalscope::DebuggerGlobalScope;
76use crate::dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope;
77use crate::dom::globalscope::GlobalScope;
78use crate::dom::globalscope::script_execution::RethrowErrors;
79use crate::dom::htmlscriptelement::{SCRIPT_JS_MIMES, Script};
80use crate::dom::idbfactory::IDBFactory;
81use crate::dom::performance::performance::Performance;
82use crate::dom::performance::performanceresourcetiming::InitiatorType;
83use crate::dom::promise::RootedPromise;
84use crate::dom::reporting::reportingendpoint::{ReportingEndpoint, SendReportsToEndpoints};
85use crate::dom::reporting::reportingobserver::ReportingObserver;
86use crate::dom::script_execution::ScriptOptions;
87use crate::dom::serviceworker::cachestorage::CacheStorage;
88use crate::dom::sharedworkerglobalscope::SharedWorkerGlobalScope;
89use crate::dom::trustedtypes::trustedscripturl::TrustedScriptURL;
90use crate::dom::trustedtypes::trustedtypepolicyfactory::TrustedTypePolicyFactory;
91use crate::dom::types::ImageBitmap;
92#[cfg(feature = "webgpu")]
93use crate::dom::webgpu::identityhub::IdentityHub;
94use crate::dom::window::{base64_atob, base64_btoa};
95use crate::dom::workerlocation::WorkerLocation;
96use crate::dom::workernavigator::WorkerNavigator;
97use crate::event_loop::timers::{IsInterval, OneshotTimers, TimerCallback};
98use crate::fetch::fetch::{
99    CspViolationsProcessor, Fetch, RequestWithGlobalScope, load_whole_resource,
100};
101use crate::fetch::network_listener::{
102    FetchResponseListener, ResourceTimingListener, submit_timing,
103};
104use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender};
105use crate::modules::script_module::{ModuleRequest, ModuleStatus, ScriptFetchOptions};
106use crate::realms::enter_auto_realm;
107use crate::runtime::job_queue::{MicrotaskRunnable, UserMicrotask, job_queue_microtask_checkpoint};
108use crate::runtime::script_runtime::{IntroductionType, Runtime, get_reports};
109use crate::tasks::task::TaskCanceller;
110use crate::tasks::task_manager::TaskManager;
111
112/// <https://html.spec.whatwg.org/multipage/#animation-frames>
113pub(crate) fn prepare_workerscope_init(
114    global: &GlobalScope,
115    devtools_sender: Option<GenericSender<DevtoolScriptControlMsg>>,
116    worker_id: Option<WorkerId>,
117    #[cfg(feature = "webgl")] webgl_chan: Option<WebGLChan>,
118) -> WorkerGlobalScopeInit {
119    // An AnimationFrameProvider provider is considered supported if any of the following are true:
120    // - provider is a Window.
121    // - provider's owner set contains a Document object.
122    // - Any of the DedicatedWorkerGlobalScope objects in provider's owner set are supported.
123    let animation_frame_provider_supported = if global.downcast::<Window>().is_some() {
124        true
125    } else if let Some(dedicated) = global.downcast::<DedicatedWorkerGlobalScope>() {
126        dedicated.animation_frame_provider_supported()
127    } else {
128        false
129    };
130
131    WorkerGlobalScopeInit {
132        resource_threads: global.resource_threads().clone(),
133        storage_threads: global.storage_threads().clone(),
134        mem_profiler_chan: global.mem_profiler_chan().clone(),
135        to_devtools_sender: global.devtools_chan().cloned(),
136        time_profiler_chan: global.time_profiler_chan().clone(),
137        from_devtools_sender: devtools_sender,
138        script_to_constellation_chan: global.script_to_constellation_chan().sender,
139        script_to_embedder_chan: global.script_to_embedder_chan().clone(),
140        worker_id: worker_id.unwrap_or_else(|| WorkerId(Uuid::new_v4())),
141        animation_frame_provider_supported,
142        pipeline_id: global.pipeline_id(),
143        origin: global.origin().immutable().clone(),
144        inherited_secure_context: Some(global.is_secure_context()),
145        unminify_js: global.unminify_js(),
146        #[cfg(feature = "webgl")]
147        webgl_chan,
148    }
149}
150
151pub(crate) struct ScriptFetchContext {
152    scope: Trusted<WorkerGlobalScope>,
153    response: Option<Metadata>,
154    body_bytes: BytesMut,
155    url: ServoUrl,
156    policy_container: PolicyContainer,
157}
158
159impl ScriptFetchContext {
160    pub(crate) fn new(
161        scope: Trusted<WorkerGlobalScope>,
162        url: ServoUrl,
163        policy_container: PolicyContainer,
164    ) -> ScriptFetchContext {
165        ScriptFetchContext {
166            scope,
167            response: None,
168            body_bytes: BytesMut::new(),
169            url,
170            policy_container,
171        }
172    }
173}
174
175impl FetchResponseListener for ScriptFetchContext {
176    fn process_request_body(&mut self, _request_id: RequestId) {}
177
178    fn process_response(
179        &mut self,
180        _: &mut JSContext,
181        _request_id: RequestId,
182        metadata: Result<FetchMetadata, NetworkError>,
183    ) {
184        self.response = metadata.ok().map(|m| match m {
185            FetchMetadata::Unfiltered(m) => m,
186            FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
187        });
188    }
189
190    fn process_response_chunk(&mut self, _: &mut JSContext, _: RequestId, chunk: Bytes) {
191        self.body_bytes.extend_from_slice(&chunk);
192    }
193
194    fn process_response_eof(
195        mut self,
196        cx: &mut JSContext,
197        _request_id: RequestId,
198        response: Result<(), NetworkError>,
199        timing: ResourceFetchTiming,
200    ) {
201        let scope = self.scope.root();
202
203        if response
204            .as_ref()
205            .inspect_err(|e| error!("error loading script {} ({:?})", self.url, e))
206            .is_err() ||
207            self.response.is_none()
208        {
209            scope.on_complete(cx, None);
210            return;
211        }
212        let metadata = self.response.take().unwrap();
213
214        // The processResponseConsumeBody steps defined inside
215        // [run a worker](https://html.spec.whatwg.org/multipage/#run-a-worker)
216        scope.process_response_for_workerscope(&metadata, &self.policy_container);
217
218        // The processResponseConsumeBody steps defined inside
219        // [fetch a classic worker script](https://html.spec.whatwg.org/multipage/#fetch-a-classic-worker-script)
220
221        // Step 1 Set response to response's unsafe response. Done in process_response
222
223        // Step 2 If any of the following are true: bodyBytes is null or failure; or response's status is not an ok status,
224        if !metadata.status.is_success() {
225            // then run onComplete given null, and abort these steps.
226            scope.on_complete(cx, None);
227            return;
228        }
229
230        // Step 3 If all of the following are true:
231        // response's URL's scheme is an HTTP(S) scheme;
232        let is_http_scheme = matches!(metadata.final_url.scheme(), "http" | "https");
233        // and the result of extracting a MIME type from response's header list is not a JavaScript MIME type,
234        let not_a_javascript_mime_type = !metadata.content_type.is_some_and(|ct| {
235            let mime: Mime = ct.into_inner().into();
236            SCRIPT_JS_MIMES.contains(&mime.essence_str())
237        });
238
239        if is_http_scheme && not_a_javascript_mime_type {
240            // then run onComplete given null, and abort these steps.
241            scope.on_complete(cx, None);
242            return;
243        }
244
245        // Step 4 Let sourceText be the result of UTF-8 decoding bodyBytes.
246        let (source, _) = UTF_8.decode_with_bom_removal(&self.body_bytes);
247
248        let global_scope = scope.upcast::<GlobalScope>();
249
250        // Step 5 Let script be the result of creating a classic script using
251        // sourceText, settingsObject, response's URL, and the default script fetch options.
252        let script = global_scope.create_a_classic_script(
253            cx,
254            source,
255            scope.worker_url.borrow().clone(),
256            ScriptOptions::External,
257            ScriptFetchOptions::default_classic_script(),
258            Some(IntroductionType::WORKER),
259            1,
260        );
261
262        // Step 6 Run onComplete given script.
263        scope.on_complete(cx, Some(Script::Classic(script)));
264
265        submit_timing(cx, &self, &response, &timing);
266    }
267
268    fn process_csp_violations(
269        &mut self,
270        _cx: &mut JSContext,
271        _request_id: RequestId,
272        violations: Vec<content_security_policy::Violation>,
273    ) {
274        let scope = self.scope.root();
275
276        if let Some(worker_scope) = scope.downcast::<DedicatedWorkerGlobalScope>() {
277            worker_scope.report_csp_violations(violations);
278        } else if let Some(worker_scope) = scope.downcast::<SharedWorkerGlobalScope>() {
279            worker_scope.report_csp_violations(violations);
280        }
281    }
282
283    fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
284        self.body_bytes
285            .reserve(size.saturating_sub(self.body_bytes.len()));
286    }
287}
288
289impl ResourceTimingListener for ScriptFetchContext {
290    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
291        (InitiatorType::Other, self.url.clone())
292    }
293
294    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
295        self.scope.root().global()
296    }
297}
298
299// https://html.spec.whatwg.org/multipage/#the-workerglobalscope-common-interface
300#[dom_struct]
301pub(crate) struct WorkerGlobalScope {
302    globalscope: GlobalScope,
303
304    worker_name: DOMString,
305    worker_type: WorkerType,
306
307    #[no_trace]
308    worker_id: WorkerId,
309    #[no_trace]
310    worker_url: DomRefCell<ServoUrl>,
311    #[conditional_malloc_size_of]
312    closing: Arc<AtomicBool>,
313    execution_ready: AtomicBool,
314    #[ignore_malloc_size_of = "Defined in js"]
315    runtime: DomRefCell<Option<Runtime>>,
316    location: MutNullableDom<WorkerLocation>,
317    navigator: MutNullableDom<WorkerNavigator>,
318    #[cfg(feature = "webcrypto")]
319    crypto: MutNullableDom<Crypto>,
320    #[no_trace]
321    /// <https://html.spec.whatwg.org/multipage/#the-workerglobalscope-common-interface:policy-container>
322    policy_container: DomRefCell<PolicyContainer>,
323
324    #[ignore_malloc_size_of = "Defined in base"]
325    #[no_trace]
326    /// A `Sender` for sending messages to devtools. This is unused but is stored here to
327    /// keep the channel alive.
328    _devtools_sender: Option<GenericSender<DevtoolScriptControlMsg>>,
329
330    #[ignore_malloc_size_of = "Defined in base"]
331    #[no_trace]
332    /// A `Receiver` for receiving messages from devtools.
333    devtools_receiver: Option<RoutedReceiver<DevtoolScriptControlMsg>>,
334
335    #[no_trace]
336    navigation_start: CrossProcessInstant,
337    performance: MutNullableDom<Performance>,
338    trusted_types: MutNullableDom<TrustedTypePolicyFactory>,
339
340    /// A [`TimerScheduler`] used to schedule timers for this [`WorkerGlobalScope`].
341    /// Timers are handled in the service worker event loop.
342    #[no_trace]
343    timer_scheduler: RefCell<TimerScheduler>,
344
345    /// The mechanism by which time-outs and intervals are scheduled.
346    /// <https://html.spec.whatwg.org/multipage/#timers>
347    timers: OnceCell<OneshotTimers>,
348
349    #[no_trace]
350    insecure_requests_policy: InsecureRequestsPolicy,
351
352    /// <https://w3c.github.io/reporting/#windoworworkerglobalscope-registered-reporting-observer-list>
353    reporting_observer_list: DomRefCell<Vec<Dom<ReportingObserver>>>,
354
355    /// <https://w3c.github.io/reporting/#windoworworkerglobalscope-reports>
356    report_list: DomRefCell<Vec<Report>>,
357
358    /// <https://w3c.github.io/reporting/#windoworworkerglobalscope-endpoints>
359    #[no_trace]
360    endpoints_list: DomRefCell<Vec<ReportingEndpoint>>,
361
362    /// The debugger global object associated with this worker global.
363    /// All traced members of DOM objects must be same-compartment with the
364    /// realm being traced, so this is the debugger global object wrapped into
365    /// this global's compartment.
366    #[ignore_malloc_size_of = "Measured by the JS engine"]
367    debugger_global: Heap<Value>,
368
369    #[no_trace]
370    pipeline_id: PipelineId,
371
372    /// <https://w3c.github.io/ServiceWorker/#global-caches-attribute>
373    caches: MutNullableDom<CacheStorage>,
374
375    /// The [`TaskManager`] for this [`WorkerGlobalScope`].
376    #[conditional_malloc_size_of]
377    task_manager: Rc<TaskManager>,
378
379    #[no_trace]
380    origin: MutableOrigin,
381
382    /// The [`FontContext`] for this worker, used by any offscreen canvas.
383    #[conditional_malloc_size_of]
384    #[no_trace]
385    font_context: Arc<FontContext>,
386
387    /// module map is used when importing JavaScript modules
388    /// <https://html.spec.whatwg.org/multipage/#concept-settings-object-module-map>
389    #[ignore_malloc_size_of = "mozjs"]
390    module_map: DomRefCell<HashMapTracedValues<ModuleRequest, ModuleStatus>>,
391}
392
393impl WorkerGlobalScope {
394    #[allow(clippy::too_many_arguments)]
395    pub(crate) fn new_inherited(
396        init: WorkerGlobalScopeInit,
397        worker_name: DOMString,
398        worker_type: WorkerType,
399        worker_url: ServoUrl,
400        runtime: Runtime,
401        devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
402        closing: Arc<AtomicBool>,
403        #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
404        insecure_requests_policy: InsecureRequestsPolicy,
405        font_context: Arc<FontContext>,
406        event_loop_sender: Option<ScriptEventLoopSender>,
407    ) -> Self {
408        // Install a pipeline-namespace in the current thread.
409        PipelineNamespace::auto_install();
410
411        let devtools_receiver = match init.from_devtools_sender {
412            Some(..) => Some(devtools_receiver),
413            None => None,
414        };
415
416        Self {
417            globalscope: GlobalScope::new_inherited(
418                init.to_devtools_sender,
419                init.mem_profiler_chan,
420                init.time_profiler_chan,
421                init.script_to_constellation_chan,
422                init.script_to_embedder_chan,
423                init.resource_threads,
424                init.storage_threads,
425                worker_url.clone(),
426                None,
427                #[cfg(feature = "webgpu")]
428                gpu_id_hub,
429                init.inherited_secure_context,
430                init.unminify_js,
431            ),
432            caches: Default::default(),
433            worker_id: init.worker_id,
434            worker_name,
435            worker_type,
436            worker_url: DomRefCell::new(worker_url),
437            closing: closing.clone(),
438            execution_ready: AtomicBool::new(false),
439            runtime: DomRefCell::new(Some(runtime)),
440            location: Default::default(),
441            navigator: Default::default(),
442            #[cfg(feature = "webcrypto")]
443            crypto: Default::default(),
444            policy_container: Default::default(),
445            devtools_receiver,
446            _devtools_sender: init.from_devtools_sender,
447            navigation_start: CrossProcessInstant::now(),
448            performance: Default::default(),
449            timer_scheduler: RefCell::default(),
450            timers: Default::default(),
451            insecure_requests_policy,
452            trusted_types: Default::default(),
453            reporting_observer_list: Default::default(),
454            report_list: Default::default(),
455            endpoints_list: Default::default(),
456            debugger_global: Default::default(),
457            pipeline_id: init.pipeline_id,
458            task_manager: Rc::new(TaskManager::new(
459                event_loop_sender,
460                init.pipeline_id,
461                Some(TaskCanceller { cancelled: closing }),
462            )),
463            origin: MutableOrigin::new(init.origin),
464            font_context,
465            module_map: Default::default(),
466        }
467    }
468
469    pub(crate) fn module_map(
470        &self,
471    ) -> &DomRefCell<HashMapTracedValues<ModuleRequest, ModuleStatus>> {
472        &self.module_map
473    }
474
475    pub(crate) fn font_context(&self) -> Arc<FontContext> {
476        self.font_context.clone()
477    }
478
479    pub(crate) fn timers(&self) -> &OneshotTimers {
480        self.timers
481            .get_or_init(|| OneshotTimers::new(self.upcast()))
482    }
483
484    pub(crate) fn enqueue_microtask(&self, cx: &JSContext, job: Box<dyn MicrotaskRunnable>) {
485        crate::runtime::job_queue::enqueue(cx, job);
486    }
487
488    /// Perform a microtask checkpoint.
489    pub(crate) fn perform_a_microtask_checkpoint(&self, cx: &mut JSContext) {
490        // Only perform the checkpoint if we're not shutting down.
491        if !self.is_closing() {
492            job_queue_microtask_checkpoint(cx, vec![DomRoot::from_ref(&self.globalscope)]);
493        }
494    }
495
496    /// Returns a policy value that should be used by fetches initiated by this worker.
497    pub(crate) fn insecure_requests_policy(&self) -> InsecureRequestsPolicy {
498        self.insecure_requests_policy
499    }
500
501    /// Clear various items when the worker event-loop shuts-down.
502    pub(crate) fn clear_js_runtime(&self) {
503        self.upcast::<GlobalScope>()
504            .remove_web_messaging_and_dedicated_workers_infra();
505
506        // Drop the runtime.
507        let runtime = self.runtime.borrow_mut().take();
508        drop(runtime);
509    }
510
511    pub(crate) fn runtime_handle(&self) -> ParentRuntime {
512        self.runtime
513            .borrow()
514            .as_ref()
515            .unwrap()
516            .prepare_for_new_child()
517    }
518
519    pub(crate) fn devtools_receiver(&self) -> Option<&RoutedReceiver<DevtoolScriptControlMsg>> {
520        self.devtools_receiver.as_ref()
521    }
522
523    pub(crate) fn is_closing(&self) -> bool {
524        self.closing.load(Ordering::SeqCst)
525    }
526
527    pub(crate) fn is_execution_ready(&self) -> bool {
528        self.execution_ready.load(Ordering::Relaxed)
529    }
530
531    pub(crate) fn get_url(&self) -> Ref<'_, ServoUrl> {
532        self.worker_url.borrow()
533    }
534
535    pub(crate) fn set_url(&self, url: ServoUrl) {
536        *self.worker_url.borrow_mut() = url;
537    }
538
539    pub(crate) fn worker_name(&self) -> DOMString {
540        self.worker_name.clone()
541    }
542
543    pub(crate) fn worker_id(&self) -> WorkerId {
544        self.worker_id
545    }
546
547    pub(crate) fn pipeline_id(&self) -> PipelineId {
548        self.pipeline_id
549    }
550
551    pub(crate) fn task_manager(&self) -> Rc<TaskManager> {
552        self.task_manager.clone()
553    }
554
555    pub(crate) fn policy_container(&self) -> Ref<'_, PolicyContainer> {
556        self.policy_container.borrow()
557    }
558
559    pub(crate) fn set_csp_list(&self, csp_list: Option<CspList>) {
560        self.policy_container.borrow_mut().set_csp_list(csp_list);
561    }
562
563    pub(crate) fn set_referrer_policy(&self, referrer_policy: ReferrerPolicy) {
564        self.policy_container
565            .borrow_mut()
566            .set_referrer_policy(referrer_policy);
567    }
568
569    pub(crate) fn append_reporting_observer(&self, reporting_observer: &ReportingObserver) {
570        self.reporting_observer_list
571            .borrow_mut()
572            .push(Dom::from_ref(reporting_observer));
573    }
574
575    pub(crate) fn remove_reporting_observer(&self, reporting_observer: &ReportingObserver) {
576        if let Some(index) = self
577            .reporting_observer_list
578            .borrow()
579            .iter()
580            .position(|observer| &**observer == reporting_observer)
581        {
582            self.reporting_observer_list.borrow_mut().remove(index);
583        }
584    }
585
586    pub(crate) fn registered_reporting_observers(&self) -> Vec<DomRoot<ReportingObserver>> {
587        self.reporting_observer_list
588            .borrow()
589            .iter()
590            .map(|observer| DomRoot::from_ref(&**observer))
591            .collect()
592    }
593
594    pub(crate) fn append_report(&self, report: Report) {
595        self.report_list.borrow_mut().push(report);
596        let trusted_worker = Trusted::new(self);
597        self.upcast::<GlobalScope>()
598            .task_manager()
599            .dom_manipulation_task_source()
600            .queue(task!(send_to_reporting_endpoints: move || {
601                let worker = trusted_worker.root();
602                let reports = std::mem::take(&mut *worker.report_list.borrow_mut());
603                worker.upcast::<GlobalScope>().send_reports_to_endpoints(
604                    reports,
605                    worker.endpoints_list.borrow().clone(),
606                );
607            }));
608    }
609
610    pub(crate) fn buffered_reports(&self) -> Vec<Report> {
611        self.report_list.borrow().clone()
612    }
613
614    pub(crate) fn set_endpoints_list(&self, endpoints: Option<Vec<ReportingEndpoint>>) {
615        if let Some(endpoints) = endpoints {
616            *self.endpoints_list.borrow_mut() = endpoints;
617        }
618    }
619
620    /// Get a mutable reference to the [`TimerScheduler`] for this [`ServiceWorkerGlobalScope`].
621    pub(crate) fn timer_scheduler(&self) -> RefMut<'_, TimerScheduler> {
622        self.timer_scheduler.borrow_mut()
623    }
624
625    /// <https://html.spec.whatwg.org/multipage/#initialize-worker-policy-container> and
626    /// <https://html.spec.whatwg.org/multipage/#creating-a-policy-container-from-a-fetch-response>
627    fn initialize_policy_container_for_worker_global_scope(
628        &self,
629        metadata: &Metadata,
630        parent_policy_container: &PolicyContainer,
631    ) {
632        // Step 1. If workerGlobalScope's url is local but its scheme is not "blob":
633        //
634        // Note that we also allow for blob here, as the parent_policy_container is in both cases
635        // the container that we need to clone.
636        if metadata.final_url.is_local_scheme() {
637            // Step 1.2. Set workerGlobalScope's policy container to a clone of workerGlobalScope's
638            // owner set[0]'s relevant settings object's policy container.
639            //
640            // Step 1. If response's URL's scheme is "blob", then return a clone of response's URL's
641            // blob URL entry's environment's policy container.
642            self.set_csp_list(parent_policy_container.csp_list.clone());
643            self.set_referrer_policy(parent_policy_container.get_referrer_policy());
644            return;
645        }
646        // Step 3. Set result's CSP list to the result of parsing a response's Content Security Policies given response.
647        self.set_csp_list(parse_csp_list_from_metadata(&metadata.headers));
648        // Step 5. Set result's referrer policy to the result of parsing the `Referrer-Policy`
649        // header given response. [REFERRERPOLICY]
650        let referrer_policy = metadata
651            .headers
652            .as_ref()
653            .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
654            .into();
655        self.set_referrer_policy(referrer_policy);
656    }
657
658    /// onComplete algorithm defined inside <https://html.spec.whatwg.org/multipage/#run-a-worker>
659    #[expect(unsafe_code)]
660    pub(crate) fn on_complete(&self, cx: &mut JSContext, script: Option<Script>) {
661        // Step 1. If script is null or if script's error to rethrow is non-null, then:
662        let script = match script {
663            Some(Script::Classic(script)) if script.record.is_ok() => Script::Classic(script),
664            Some(Script::Module(module_tree))
665                if module_tree.get_rethrow_error().borrow().is_none() =>
666            {
667                Script::Module(module_tree)
668            },
669            _ => {
670                // Step 1.1. Queue a global task on the DOM manipulation task source given
671                // worker's relevant global object to fire an event named error at worker.
672                if let Some(dedicated) = self.downcast::<DedicatedWorkerGlobalScope>() {
673                    dedicated.forward_simple_error_at_worker();
674                } else if let Some(shared) = self.downcast::<SharedWorkerGlobalScope>() {
675                    shared.forward_simple_error_at_worker();
676                }
677
678                // TODO Step 1.2. Run the environment discarding steps for inside settings.
679                // Step 1.3 Abort these steps.
680                return;
681            },
682        };
683
684        unsafe {
685            // Handle interrupt requests
686            js::rust::wrappers2::JS_AddInterruptCallback(cx, Some(interrupt_callback));
687        }
688
689        if self.is_closing() {
690            return;
691        }
692
693        {
694            let mut realm = enter_auto_realm(cx, self);
695            let cx = &mut realm.current_realm();
696            define_all_exposed_interfaces(cx, self.upcast());
697            // Step 9. Set inside settings's execution ready flag.
698            self.execution_ready.store(true, Ordering::Relaxed);
699            match script {
700                Script::Classic(script) => {
701                    _ = self.globalscope.run_a_classic_script(
702                        cx,
703                        script,
704                        RethrowErrors::No,
705                        None, // return_value
706                    );
707                },
708                Script::Module(module_tree) => {
709                    self.globalscope.run_a_module_script(cx, module_tree, false);
710                },
711                _ => unreachable!(),
712            }
713            if let Some(dedicated) = self.downcast::<DedicatedWorkerGlobalScope>() {
714                dedicated.fire_queued_messages(cx);
715            } else if let Some(shared) = self.downcast::<SharedWorkerGlobalScope>() {
716                // Step 11. Enable outside port's port message queue.
717                shared.enable_outside_port_message_queue();
718                // Step 13. If the initial Connect arrived before execution-ready,
719                // queue the global task to fire its connect event now.
720                shared.fire_pending_connect(cx);
721            }
722        }
723    }
724
725    // The processResponseConsumeBody steps defined inside
726    // [run a worker](https://html.spec.whatwg.org/multipage/#run-a-worker)
727    pub(crate) fn process_response_for_workerscope(
728        &self,
729        metadata: &Metadata,
730        policy_container: &PolicyContainer,
731    ) {
732        // Step 1. Set worker global scope's url to response's url.
733        self.set_url(metadata.final_url.clone());
734
735        // Step 2. Set inside settings's creation URL to response's url.
736        self.globalscope
737            .set_creation_url(metadata.final_url.clone());
738
739        // Step 3. Initialize worker global scope's policy container given worker global scope, response, and inside settings.
740        self.initialize_policy_container_for_worker_global_scope(metadata, policy_container);
741        self.set_endpoints_list(ReportingEndpoint::parse_reporting_endpoints_header(
742            &metadata.final_url.clone(),
743            &metadata.headers,
744        ));
745    }
746
747    pub(crate) fn origin(&self) -> MutableOrigin {
748        self.origin.clone()
749    }
750}
751
752impl WorkerGlobalScopeMethods<crate::DomTypeHolder> for WorkerGlobalScope {
753    /// <https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-self>
754    fn Self_(&self) -> DomRoot<WorkerGlobalScope> {
755        DomRoot::from_ref(self)
756    }
757
758    /// <https://w3c.github.io/IndexedDB/#factory-interface>
759    fn IndexedDB(&self, cx: &mut JSContext) -> DomRoot<IDBFactory> {
760        self.upcast::<GlobalScope>().ensure_indexeddb_factory(cx)
761    }
762
763    /// <https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-location>
764    fn Location(&self, cx: &mut JSContext) -> DomRoot<WorkerLocation> {
765        self.location
766            .or_init(|| WorkerLocation::new(cx, self, self.worker_url.borrow().clone()))
767    }
768
769    /// <https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-importscripts>
770    fn ImportScripts(
771        &self,
772        cx: &mut JSContext,
773        url_strings: Vec<TrustedScriptURLOrUSVString>,
774    ) -> ErrorResult {
775        // https://html.spec.whatwg.org/multipage/#import-scripts-into-worker-global-scope
776        // Step 1: If worker global scope's type is "module", throw a TypeError exception.
777        if self.worker_type == WorkerType::Module {
778            return Err(Error::Type(
779                c"importScripts() is not allowed in module workers".to_owned(),
780            ));
781        }
782
783        // Step 4: Let urlStrings be « ».
784        let mut urls = Vec::with_capacity(url_strings.len());
785        // Step 5: For each url of urls:
786        for url in url_strings {
787            // Step 3: Append the result of invoking the Get Trusted Type compliant string algorithm
788            // with TrustedScriptURL, this's relevant global object, url, "WorkerGlobalScope importScripts",
789            // and "script" to urlStrings.
790            let url = TrustedScriptURL::get_trusted_type_compliant_string(
791                cx,
792                self.upcast::<GlobalScope>(),
793                url,
794                "WorkerGlobalScope importScripts",
795            )?;
796            let url = self.worker_url.borrow().join(&url.str());
797            match url {
798                Ok(url) => urls.push(url),
799                Err(_) => return Err(Error::Syntax(None)),
800            };
801        }
802
803        for url in urls {
804            let global_scope = self.upcast::<GlobalScope>();
805            let request = RequestBuilder::new(
806                global_scope.webview_id(),
807                UrlWithBlobClaim::from_url_without_having_claimed_blob(url.clone()),
808                global_scope.get_referrer(),
809            )
810            .destination(Destination::Script)
811            .credentials_mode(CredentialsMode::Include)
812            .parser_metadata(ParserMetadata::NotParserInserted)
813            .use_url_credentials(true)
814            .with_global_scope(global_scope);
815
816            // https://html.spec.whatwg.org/multipage/#fetch-a-classic-worker-imported-script
817            let (url, bytes, muted_errors) = match load_whole_resource(
818                request,
819                &global_scope.resource_threads().sender(),
820                global_scope,
821                &WorkerCspProcessor {
822                    global_scope: DomRoot::from_ref(global_scope),
823                },
824                cx,
825            ) {
826                Err(_) => return Err(Error::Network(None)),
827                Ok((metadata, bytes, muted_errors)) => {
828                    // Step 7: Check if response status is not an ok status
829                    if !metadata.status.is_success() {
830                        return Err(Error::Network(None));
831                    }
832
833                    // Step 7: Check if the MIME type is not a JavaScript MIME type
834                    let not_a_javascript_mime_type =
835                        !metadata.content_type.clone().is_some_and(|ct| {
836                            let mime: Mime = ct.into_inner().into();
837                            SCRIPT_JS_MIMES.contains(&mime.essence_str())
838                        });
839                    if not_a_javascript_mime_type {
840                        return Err(Error::Network(None));
841                    }
842
843                    (metadata.final_url, bytes, muted_errors)
844                },
845            };
846
847            // Step 8. Let sourceText be the result of UTF-8 decoding bodyBytes.
848            let (source, _) = UTF_8.decode_with_bom_removal(&bytes);
849
850            // Step 9. Let mutedErrors be true if response was CORS-cross-origin, and false otherwise.
851            // Note: done inside load_whole_resource
852
853            // Step 10. Let script be the result of creating a classic script
854            // given sourceText, settingsObject, response's URL, the default script fetch options, and mutedErrors.
855            let mut script_options = ScriptOptions::External;
856            script_options.set(ScriptOptions::MutedErrors, muted_errors);
857            let script = self.globalscope.create_a_classic_script(
858                cx,
859                source,
860                url,
861                script_options,
862                ScriptFetchOptions::default_classic_script(),
863                Some(IntroductionType::WORKER),
864                1,
865            );
866
867            // Run the classic script script, with rethrow errors set to true.
868            let result = self.globalscope.run_a_classic_script(
869                cx,
870                script,
871                RethrowErrors::Yes,
872                None, // return_value
873            );
874
875            if let Err(error) = result {
876                if self.is_closing() {
877                    // Don't return JSFailed as we might not have
878                    // any pending exceptions.
879                    error!("evaluate_script failed (terminated)");
880                } else {
881                    error!("evaluate_script failed");
882                    return Err(error);
883                }
884            }
885        }
886
887        Ok(())
888    }
889
890    // https://html.spec.whatwg.org/multipage/#handler-workerglobalscope-onerror
891    error_event_handler!(error, GetOnerror, SetOnerror);
892
893    // https://html.spec.whatwg.org/multipage/#handler-workerglobalscope-onlanguagechange
894    event_handler!(languagechange, GetOnlanguagechange, SetOnlanguagechange);
895
896    // https://html.spec.whatwg.org/multipage/#handler-workerglobalscope-onoffline
897    event_handler!(offline, GetOnoffline, SetOnoffline);
898
899    // https://html.spec.whatwg.org/multipage/#handler-workerglobalscope-ononline
900    event_handler!(online, GetOnonline, SetOnonline);
901
902    // https://html.spec.whatwg.org/multipage/#handler-workerglobalscope-onrejectionhandled
903    event_handler!(
904        rejectionhandled,
905        GetOnrejectionhandled,
906        SetOnrejectionhandled
907    );
908
909    // https://html.spec.whatwg.org/multipage/#handler-workerglobalscope-onunhandledrejection
910    event_handler!(
911        unhandledrejection,
912        GetOnunhandledrejection,
913        SetOnunhandledrejection
914    );
915
916    /// <https://html.spec.whatwg.org/multipage/#dom-worker-navigator>
917    fn Navigator(&self, cx: &mut JSContext) -> DomRoot<WorkerNavigator> {
918        self.navigator.or_init(|| WorkerNavigator::new(cx, self))
919    }
920
921    /// <https://html.spec.whatwg.org/multipage/#dfn-Crypto>
922    #[cfg(feature = "webcrypto")]
923    fn Crypto(&self, cx: &mut JSContext) -> DomRoot<Crypto> {
924        self.crypto
925            .or_init(|| Crypto::new(cx, self.upcast::<GlobalScope>()))
926    }
927
928    /// <https://html.spec.whatwg.org/multipage/#dom-reporterror>
929    fn ReportError(&self, cx: &mut JSContext, error: HandleValue) {
930        self.upcast::<GlobalScope>().report_an_exception(cx, error);
931    }
932
933    /// <https://html.spec.whatwg.org/multipage/#dom-windowbase64-btoa>
934    fn Btoa(&self, btoa: DOMString) -> Fallible<DOMString> {
935        base64_btoa(btoa)
936    }
937
938    /// <https://html.spec.whatwg.org/multipage/#dom-windowbase64-atob>
939    fn Atob(&self, atob: DOMString) -> Fallible<DOMString> {
940        base64_atob(atob)
941    }
942
943    /// <https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout>
944    fn SetTimeout(
945        &self,
946        cx: &mut JSContext,
947        callback: TrustedScriptOrStringOrFunction,
948        timeout: i32,
949        args: Vec<HandleValue>,
950    ) -> Fallible<i32> {
951        let callback = match callback {
952            TrustedScriptOrStringOrFunction::String(i) => {
953                TimerCallback::StringTimerCallback(TrustedScriptOrString::String(i))
954            },
955            TrustedScriptOrStringOrFunction::TrustedScript(i) => {
956                TimerCallback::StringTimerCallback(TrustedScriptOrString::TrustedScript(i))
957            },
958            TrustedScriptOrStringOrFunction::Function(i) => TimerCallback::FunctionTimerCallback(i),
959        };
960        self.upcast::<GlobalScope>().set_timeout_or_interval(
961            cx,
962            callback,
963            args,
964            Duration::from_millis(timeout.max(0) as u64),
965            IsInterval::NonInterval,
966        )
967    }
968
969    /// <https://html.spec.whatwg.org/multipage/#dom-windowtimers-cleartimeout>
970    fn ClearTimeout(&self, handle: i32) {
971        self.upcast::<GlobalScope>()
972            .clear_timeout_or_interval(handle);
973    }
974
975    /// <https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval>
976    fn SetInterval(
977        &self,
978        cx: &mut JSContext,
979        callback: TrustedScriptOrStringOrFunction,
980        timeout: i32,
981        args: Vec<HandleValue>,
982    ) -> Fallible<i32> {
983        let callback = match callback {
984            TrustedScriptOrStringOrFunction::String(i) => {
985                TimerCallback::StringTimerCallback(TrustedScriptOrString::String(i))
986            },
987            TrustedScriptOrStringOrFunction::TrustedScript(i) => {
988                TimerCallback::StringTimerCallback(TrustedScriptOrString::TrustedScript(i))
989            },
990            TrustedScriptOrStringOrFunction::Function(i) => TimerCallback::FunctionTimerCallback(i),
991        };
992        self.upcast::<GlobalScope>().set_timeout_or_interval(
993            cx,
994            callback,
995            args,
996            Duration::from_millis(timeout.max(0) as u64),
997            IsInterval::Interval,
998        )
999    }
1000
1001    /// <https://w3c.github.io/ServiceWorker/#global-caches-attribute>
1002    fn Caches(&self, cx: &mut JSContext) -> DomRoot<CacheStorage> {
1003        self.caches
1004            .or_init(|| CacheStorage::new(cx, self.upcast::<GlobalScope>()))
1005    }
1006
1007    /// <https://html.spec.whatwg.org/multipage/#dom-windowtimers-clearinterval>
1008    fn ClearInterval(&self, handle: i32) {
1009        self.ClearTimeout(handle);
1010    }
1011
1012    /// <https://html.spec.whatwg.org/multipage/#dom-queuemicrotask>
1013    fn QueueMicrotask(&self, cx: &mut JSContext, callback: Rc<VoidFunction>) {
1014        self.enqueue_microtask(
1015            cx,
1016            Box::new(UserMicrotask {
1017                callback,
1018                global: Dom::from_ref(&self.globalscope),
1019            }),
1020        );
1021    }
1022
1023    /// <https://html.spec.whatwg.org/multipage/#dom-createimagebitmap>
1024    fn CreateImageBitmap(
1025        &self,
1026        realm: &mut CurrentRealm,
1027        image: ImageBitmapSource,
1028        options: &ImageBitmapOptions,
1029    ) -> RootedPromise {
1030        ImageBitmap::create_image_bitmap(self.upcast(), image, 0, 0, None, None, options, realm)
1031    }
1032
1033    /// <https://html.spec.whatwg.org/multipage/#dom-createimagebitmap>
1034    fn CreateImageBitmap_(
1035        &self,
1036        realm: &mut CurrentRealm,
1037        image: ImageBitmapSource,
1038        sx: i32,
1039        sy: i32,
1040        sw: i32,
1041        sh: i32,
1042        options: &ImageBitmapOptions,
1043    ) -> RootedPromise {
1044        ImageBitmap::create_image_bitmap(
1045            self.upcast(),
1046            image,
1047            sx,
1048            sy,
1049            Some(sw),
1050            Some(sh),
1051            options,
1052            realm,
1053        )
1054    }
1055
1056    /// <https://fetch.spec.whatwg.org/#dom-global-fetch>
1057    fn Fetch(
1058        &self,
1059        realm: &mut CurrentRealm,
1060        input: RequestOrUSVString,
1061        init: RootedTraceableBox<RequestInit>,
1062    ) -> RootedPromise {
1063        Fetch(self.upcast(), input, init, realm)
1064    }
1065
1066    /// <https://w3c.github.io/hr-time/#the-performance-attribute>
1067    fn Performance(&self, cx: &mut JSContext) -> DomRoot<Performance> {
1068        self.performance.or_init(|| {
1069            let global_scope = self.upcast::<GlobalScope>();
1070            Performance::new(cx, global_scope, self.navigation_start)
1071        })
1072    }
1073
1074    /// <https://html.spec.whatwg.org/multipage/#dom-origin>
1075    fn Origin(&self) -> USVString {
1076        USVString(
1077            self.upcast::<GlobalScope>()
1078                .origin()
1079                .immutable()
1080                .ascii_serialization()
1081                .into_owned(),
1082        )
1083    }
1084
1085    /// <https://w3c.github.io/webappsec-secure-contexts/#dom-windoworworkerglobalscope-issecurecontext>
1086    fn IsSecureContext(&self) -> bool {
1087        self.upcast::<GlobalScope>().is_secure_context()
1088    }
1089
1090    /// <https://html.spec.whatwg.org/multipage/#dom-structuredclone>
1091    fn StructuredClone(
1092        &self,
1093        cx: &mut JSContext,
1094        value: HandleValue,
1095        options: RootedTraceableBox<StructuredSerializeOptions>,
1096        retval: MutableHandleValue,
1097    ) -> Fallible<()> {
1098        self.upcast::<GlobalScope>()
1099            .structured_clone(cx, value, options, retval)
1100    }
1101
1102    /// <https://www.w3.org/TR/trusted-types/#dom-windoworworkerglobalscope-trustedtypes>
1103    fn TrustedTypes(&self, cx: &mut JSContext) -> DomRoot<TrustedTypePolicyFactory> {
1104        self.trusted_types.or_init(|| {
1105            let global_scope = self.upcast::<GlobalScope>();
1106            TrustedTypePolicyFactory::new(cx, global_scope)
1107        })
1108    }
1109}
1110
1111impl WorkerGlobalScope {
1112    pub(crate) fn new_script_pair(&self) -> (ScriptEventLoopSender, ScriptEventLoopReceiver) {
1113        let dedicated = self.downcast::<DedicatedWorkerGlobalScope>();
1114        if let Some(dedicated) = dedicated {
1115            dedicated.new_script_pair()
1116        } else if let Some(shared) = self.downcast::<SharedWorkerGlobalScope>() {
1117            shared.new_script_pair()
1118        } else {
1119            panic!("need to implement a sender for ServiceWorker")
1120        }
1121    }
1122
1123    /// Process a single event as if it were the next event
1124    /// in the queue for this worker event-loop.
1125    /// Returns a boolean indicating whether further events should be processed.
1126    pub(crate) fn process_event(&self, msg: CommonScriptMsg, cx: &mut JSContext) -> bool {
1127        if self.is_closing() {
1128            return false;
1129        }
1130        match msg {
1131            CommonScriptMsg::Task(_, task, _, _) => task.run_box(cx),
1132            CommonScriptMsg::CollectReports(reports_chan) => {
1133                perform_memory_report(|ops| {
1134                    let reports =
1135                        get_reports(cx, format!("url({})", self.get_url()), ops, HashSet::new());
1136                    reports_chan.send(ProcessReports::new(reports));
1137                });
1138            },
1139            CommonScriptMsg::ReportCspViolations(_, violations) => {
1140                self.upcast::<GlobalScope>()
1141                    .report_csp_violations(cx, violations, None, None);
1142            },
1143        }
1144        true
1145    }
1146
1147    /// <https://html.spec.whatwg.org/multipage/#close-a-worker>
1148    pub(crate) fn close(&self) {
1149        // Step 1. Discard any tasks that have been added to workerGlobal's relevant
1150        // agent's event loop's task queues.
1151        //
1152        // Worker rAF callbacks are stored outside the task queues.
1153        if let Some(dedicated) = self.downcast::<DedicatedWorkerGlobalScope>() {
1154            dedicated.clear_animation_frame_callbacks_and_unregister();
1155        }
1156
1157        // Step 2. Set workerGlobal's closing flag to true. (This prevents any
1158        // further tasks from being queued.)
1159        self.closing.store(true, Ordering::SeqCst);
1160        self.upcast::<GlobalScope>()
1161            .task_manager()
1162            .cancel_all_tasks_and_ignore_future_tasks();
1163
1164        // From <https://w3c.github.io/IndexedDB/#database-connection>
1165        // > The connection can be closed through several means. If the execution context where
1166        // > the connection was created is destroyed (for example due to the user navigating away
1167        // > from that page), the connection is closed.
1168        if let Some(factory) = self.upcast::<GlobalScope>().indexeddb_factory() {
1169            factory.abort_pending_upgrades_and_close_databases();
1170        }
1171    }
1172
1173    pub(crate) fn init_debugger_global(
1174        &self,
1175        debugger_global: &DebuggerGlobalScope,
1176        cx: &mut JSContext,
1177    ) {
1178        let mut realm = enter_auto_realm(cx, self);
1179        let cx = &mut realm.current_realm();
1180
1181        // Convert the debugger global’s reflector to a Value, wrapping it from its originating realm (debugger realm)
1182        // into the active realm (debuggee realm) so that it can be passed across compartments.
1183        rooted!(&in(cx) let mut wrapped_global: Value);
1184        debugger_global
1185            .reflector()
1186            .to_jsval(cx, wrapped_global.handle_mut());
1187        self.debugger_global.set(*wrapped_global);
1188    }
1189
1190    pub(crate) fn handle_devtools_message(&self, msg: DevtoolScriptControlMsg, cx: &mut JSContext) {
1191        match msg {
1192            DevtoolScriptControlMsg::WantsLiveNotifications(_pipe_id, _wants_updates) => {},
1193            DevtoolScriptControlMsg::Eval(code, id, frame_actor_id, eager, reply) => {
1194                let debugger_global_handle = rooted_heap_handle(self, |this| &this.debugger_global);
1195                let debugger_global =
1196                    root_from_handlevalue::<DebuggerGlobalScope>(cx, debugger_global_handle)
1197                        .expect("must be a debugger global scope");
1198
1199                debugger_global.fire_eval(
1200                    cx,
1201                    code.into(),
1202                    id,
1203                    Some(self.worker_id()),
1204                    frame_actor_id,
1205                    eager,
1206                    reply,
1207                );
1208            },
1209            _ => debug!("got an unusable devtools control message inside the worker!"),
1210        }
1211    }
1212}
1213
1214#[expect(unsafe_code)]
1215unsafe extern "C" fn interrupt_callback(cx: *mut RawJSContext) -> bool {
1216    // SAFETY: it is safe to construct a JSContext from engine hook.
1217    let mut cx = unsafe { JSContext::from_ptr(std::ptr::NonNull::new(cx).unwrap()) };
1218    let mut realm = CurrentRealm::assert(&mut cx);
1219
1220    let global = GlobalScope::from_current_realm(&mut realm);
1221
1222    // If we are running the debugger script, just exit immediately.
1223    let Some(worker) = global.downcast::<WorkerGlobalScope>() else {
1224        return false;
1225    };
1226
1227    // A false response causes the script to terminate.
1228    !worker.is_closing()
1229}
1230
1231struct WorkerCspProcessor {
1232    global_scope: DomRoot<GlobalScope>,
1233}
1234
1235impl CspViolationsProcessor for WorkerCspProcessor {
1236    fn process_csp_violations(&self, cx: &mut JSContext, violations: Vec<Violation>) {
1237        self.global_scope
1238            .report_csp_violations(cx, violations, None, None);
1239    }
1240}