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