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