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