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