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