1use std::cell::Cell;
6use std::convert::TryInto;
7use std::ops::Deref;
8use std::sync::{Arc, LazyLock, Mutex};
9
10use dom_struct::dom_struct;
11use headers::HeaderMap;
12use http::header::{self, HeaderValue};
13use js::rust::MutableHandleValue;
14use net_traits::request::{
15 CredentialsMode, Destination, RequestBuilder, RequestId, RequestMode,
16 is_cors_safelisted_request_content_type,
17};
18use net_traits::{
19 FetchMetadata, FetchResponseListener, NetworkError, ResourceFetchTiming, ResourceTimingType,
20};
21use servo_config::pref;
22use servo_url::ServoUrl;
23
24use crate::body::Extractable;
25use crate::dom::bindings::cell::DomRefCell;
26use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods;
27use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
28use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
29use crate::dom::bindings::error::{Error, Fallible};
30use crate::dom::bindings::refcounted::Trusted;
31use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
32use crate::dom::bindings::root::{DomRoot, MutNullableDom};
33use crate::dom::bindings::str::{DOMString, USVString};
34use crate::dom::bindings::utils::to_frozen_array;
35#[cfg(feature = "bluetooth")]
36use crate::dom::bluetooth::Bluetooth;
37use crate::dom::clipboard::Clipboard;
38use crate::dom::credentialmanagement::credentialscontainer::CredentialsContainer;
39use crate::dom::csp::{GlobalCspReporting, Violation};
40use crate::dom::gamepad::Gamepad;
41use crate::dom::gamepad::gamepadevent::GamepadEventType;
42use crate::dom::geolocation::Geolocation;
43use crate::dom::globalscope::GlobalScope;
44use crate::dom::mediadevices::MediaDevices;
45use crate::dom::mediasession::MediaSession;
46use crate::dom::mimetypearray::MimeTypeArray;
47use crate::dom::navigatorinfo;
48use crate::dom::performanceresourcetiming::InitiatorType;
49use crate::dom::permissions::Permissions;
50use crate::dom::pluginarray::PluginArray;
51use crate::dom::serviceworkercontainer::ServiceWorkerContainer;
52use crate::dom::servointernals::ServoInternals;
53#[cfg(feature = "webgpu")]
54use crate::dom::webgpu::gpu::GPU;
55use crate::dom::window::Window;
56#[cfg(feature = "webxr")]
57use crate::dom::xrsystem::XRSystem;
58use crate::network_listener::{PreInvoke, ResourceTimingListener, submit_timing};
59use crate::script_runtime::{CanGc, JSContext};
60
61pub(super) fn hardware_concurrency() -> u64 {
62 static CPUS: LazyLock<u64> = LazyLock::new(|| num_cpus::get().try_into().unwrap_or(1));
63
64 *CPUS
65}
66
67#[dom_struct]
68pub(crate) struct Navigator {
69 reflector_: Reflector,
70 #[cfg(feature = "bluetooth")]
71 bluetooth: MutNullableDom<Bluetooth>,
72 credentials: MutNullableDom<CredentialsContainer>,
73 plugins: MutNullableDom<PluginArray>,
74 mime_types: MutNullableDom<MimeTypeArray>,
75 service_worker: MutNullableDom<ServiceWorkerContainer>,
76 #[cfg(feature = "webxr")]
77 xr: MutNullableDom<XRSystem>,
78 mediadevices: MutNullableDom<MediaDevices>,
79 gamepads: DomRefCell<Vec<MutNullableDom<Gamepad>>>,
81 permissions: MutNullableDom<Permissions>,
82 mediasession: MutNullableDom<MediaSession>,
83 clipboard: MutNullableDom<Clipboard>,
84 #[cfg(feature = "webgpu")]
85 gpu: MutNullableDom<GPU>,
86 has_gamepad_gesture: Cell<bool>,
88 servo_internals: MutNullableDom<ServoInternals>,
89}
90
91impl Navigator {
92 fn new_inherited() -> Navigator {
93 Navigator {
94 reflector_: Reflector::new(),
95 #[cfg(feature = "bluetooth")]
96 bluetooth: Default::default(),
97 credentials: Default::default(),
98 plugins: Default::default(),
99 mime_types: Default::default(),
100 service_worker: Default::default(),
101 #[cfg(feature = "webxr")]
102 xr: Default::default(),
103 mediadevices: Default::default(),
104 gamepads: Default::default(),
105 permissions: Default::default(),
106 mediasession: Default::default(),
107 clipboard: Default::default(),
108 #[cfg(feature = "webgpu")]
109 gpu: Default::default(),
110 has_gamepad_gesture: Cell::new(false),
111 servo_internals: Default::default(),
112 }
113 }
114
115 pub(crate) fn new(window: &Window, can_gc: CanGc) -> DomRoot<Navigator> {
116 reflect_dom_object(Box::new(Navigator::new_inherited()), window, can_gc)
117 }
118
119 #[cfg(feature = "webxr")]
120 pub(crate) fn xr(&self) -> Option<DomRoot<XRSystem>> {
121 self.xr.get()
122 }
123
124 pub(crate) fn get_gamepad(&self, index: usize) -> Option<DomRoot<Gamepad>> {
125 self.gamepads.borrow().get(index).and_then(|g| g.get())
126 }
127
128 pub(crate) fn set_gamepad(&self, index: usize, gamepad: &Gamepad, can_gc: CanGc) {
129 if let Some(gamepad_to_set) = self.gamepads.borrow().get(index) {
130 gamepad_to_set.set(Some(gamepad));
131 }
132 if self.has_gamepad_gesture.get() {
133 gamepad.set_exposed(true);
134 if self.global().as_window().Document().is_fully_active() {
135 gamepad.notify_event(GamepadEventType::Connected, can_gc);
136 }
137 }
138 }
139
140 pub(crate) fn remove_gamepad(&self, index: usize) {
141 if let Some(gamepad_to_remove) = self.gamepads.borrow_mut().get(index) {
142 gamepad_to_remove.set(None);
143 }
144 self.shrink_gamepads_list();
145 }
146
147 pub(crate) fn select_gamepad_index(&self) -> u32 {
149 let mut gamepad_list = self.gamepads.borrow_mut();
150 if let Some(index) = gamepad_list.iter().position(|g| g.get().is_none()) {
151 index as u32
152 } else {
153 let len = gamepad_list.len();
154 gamepad_list.resize_with(len + 1, Default::default);
155 len as u32
156 }
157 }
158
159 fn shrink_gamepads_list(&self) {
160 let mut gamepad_list = self.gamepads.borrow_mut();
161 for i in (0..gamepad_list.len()).rev() {
162 if gamepad_list.get(i).is_none() {
163 gamepad_list.remove(i);
164 } else {
165 break;
166 }
167 }
168 }
169
170 pub(crate) fn has_gamepad_gesture(&self) -> bool {
171 self.has_gamepad_gesture.get()
172 }
173
174 pub(crate) fn set_has_gamepad_gesture(&self, has_gamepad_gesture: bool) {
175 self.has_gamepad_gesture.set(has_gamepad_gesture);
176 }
177}
178
179impl NavigatorMethods<crate::DomTypeHolder> for Navigator {
180 fn Product(&self) -> DOMString {
182 navigatorinfo::Product()
183 }
184
185 fn ProductSub(&self) -> DOMString {
187 navigatorinfo::ProductSub()
188 }
189
190 fn Vendor(&self) -> DOMString {
192 navigatorinfo::Vendor()
193 }
194
195 fn VendorSub(&self) -> DOMString {
197 navigatorinfo::VendorSub()
198 }
199
200 fn TaintEnabled(&self) -> bool {
202 navigatorinfo::TaintEnabled()
203 }
204
205 fn AppName(&self) -> DOMString {
207 navigatorinfo::AppName()
208 }
209
210 fn AppCodeName(&self) -> DOMString {
212 navigatorinfo::AppCodeName()
213 }
214
215 fn Platform(&self) -> DOMString {
217 navigatorinfo::Platform()
218 }
219
220 fn UserAgent(&self) -> DOMString {
222 navigatorinfo::UserAgent(&pref!(user_agent))
223 }
224
225 fn AppVersion(&self) -> DOMString {
227 navigatorinfo::AppVersion()
228 }
229
230 #[cfg(feature = "bluetooth")]
232 fn Bluetooth(&self) -> DomRoot<Bluetooth> {
233 self.bluetooth
234 .or_init(|| Bluetooth::new(&self.global(), CanGc::note()))
235 }
236
237 fn Credentials(&self) -> DomRoot<CredentialsContainer> {
239 self.credentials
240 .or_init(|| CredentialsContainer::new(&self.global(), CanGc::note()))
241 }
242
243 fn Geolocation(&self) -> DomRoot<Geolocation> {
245 Geolocation::new(&self.global(), CanGc::note())
246 }
247
248 fn Language(&self) -> DOMString {
250 navigatorinfo::Language()
251 }
252
253 #[allow(unsafe_code)]
255 fn Languages(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
256 to_frozen_array(&[self.Language()], cx, retval, can_gc)
257 }
258
259 fn OnLine(&self) -> bool {
261 true
262 }
263
264 fn Plugins(&self) -> DomRoot<PluginArray> {
266 self.plugins
267 .or_init(|| PluginArray::new(&self.global(), CanGc::note()))
268 }
269
270 fn MimeTypes(&self) -> DomRoot<MimeTypeArray> {
272 self.mime_types
273 .or_init(|| MimeTypeArray::new(&self.global(), CanGc::note()))
274 }
275
276 fn JavaEnabled(&self) -> bool {
278 false
279 }
280
281 fn ServiceWorker(&self) -> DomRoot<ServiceWorkerContainer> {
283 self.service_worker
284 .or_init(|| ServiceWorkerContainer::new(&self.global(), CanGc::note()))
285 }
286
287 fn CookieEnabled(&self) -> bool {
289 true
290 }
291
292 fn GetGamepads(&self) -> Vec<Option<DomRoot<Gamepad>>> {
294 let global = self.global();
295 let window = global.as_window();
296 let doc = window.Document();
297
298 if !doc.is_fully_active() || !self.has_gamepad_gesture.get() {
300 return Vec::new();
301 }
302
303 self.gamepads.borrow().iter().map(|g| g.get()).collect()
304 }
305 fn Permissions(&self) -> DomRoot<Permissions> {
307 self.permissions
308 .or_init(|| Permissions::new(&self.global(), CanGc::note()))
309 }
310
311 #[cfg(feature = "webxr")]
313 fn Xr(&self) -> DomRoot<XRSystem> {
314 self.xr
315 .or_init(|| XRSystem::new(self.global().as_window(), CanGc::note()))
316 }
317
318 fn MediaDevices(&self) -> DomRoot<MediaDevices> {
320 self.mediadevices
321 .or_init(|| MediaDevices::new(&self.global(), CanGc::note()))
322 }
323
324 fn MediaSession(&self) -> DomRoot<MediaSession> {
326 self.mediasession.or_init(|| {
327 let global = self.global();
335 let window = global.as_window();
336 MediaSession::new(window, CanGc::note())
337 })
338 }
339
340 #[cfg(feature = "webgpu")]
342 fn Gpu(&self) -> DomRoot<GPU> {
343 self.gpu.or_init(|| GPU::new(&self.global(), CanGc::note()))
344 }
345
346 fn HardwareConcurrency(&self) -> u64 {
348 hardware_concurrency()
349 }
350
351 fn Clipboard(&self) -> DomRoot<Clipboard> {
353 self.clipboard
354 .or_init(|| Clipboard::new(&self.global(), CanGc::note()))
355 }
356
357 fn SendBeacon(&self, url: USVString, data: Option<BodyInit>, can_gc: CanGc) -> Fallible<bool> {
359 let global = self.global();
360 let base = global.api_base_url();
362 let origin = global.origin().immutable().clone();
364 let Ok(url) = ServoUrl::parse_with_base(Some(&base), &url) else {
368 return Err(Error::Type("Cannot parse URL".to_owned()));
369 };
370 if !matches!(url.scheme(), "http" | "https") {
371 return Err(Error::Type("URL is not http(s)".to_owned()));
372 }
373 let mut request_body = None;
374 let mut headers = HeaderMap::with_capacity(1);
376 let mut cors_mode = RequestMode::NoCors;
378 if let Some(data) = data {
380 let extracted_body = data.extract(&global, can_gc)?;
383 if let Some(total_bytes) = extracted_body.total_bytes {
387 if total_bytes > 64 * 1024 {
388 return Ok(false);
389 }
390 }
391 if let Some(content_type) = extracted_body.content_type.as_ref() {
393 cors_mode = RequestMode::CorsMode;
395 if is_cors_safelisted_request_content_type(content_type.as_bytes().deref()) {
398 cors_mode = RequestMode::NoCors;
399 }
400 headers.insert(
405 header::CONTENT_TYPE,
406 HeaderValue::from_str(&content_type.str()).unwrap(),
407 );
408 }
409 request_body = Some(extracted_body.into_net_request_body().0);
410 }
411 let request = RequestBuilder::new(None, url.clone(), global.get_referrer())
413 .mode(cors_mode)
414 .destination(Destination::None)
415 .policy_container(global.policy_container())
416 .insecure_requests_policy(global.insecure_requests_policy())
417 .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_or_current_origin())
418 .method(http::Method::POST)
419 .body(request_body)
420 .origin(origin)
421 .credentials_mode(CredentialsMode::Include)
423 .headers(headers);
424 global.fetch(
426 request,
427 Arc::new(Mutex::new(BeaconFetchListener {
428 url,
429 global: Trusted::new(&global),
430 resource_timing: ResourceFetchTiming::new(ResourceTimingType::None),
431 })),
432 global.task_manager().networking_task_source().into(),
433 );
434 Ok(true)
437 }
438
439 fn Servo(&self) -> DomRoot<ServoInternals> {
441 self.servo_internals
442 .or_init(|| ServoInternals::new(&self.global(), CanGc::note()))
443 }
444}
445
446struct BeaconFetchListener {
447 url: ServoUrl,
449 resource_timing: ResourceFetchTiming,
451 global: Trusted<GlobalScope>,
453}
454
455impl FetchResponseListener for BeaconFetchListener {
456 fn process_request_body(&mut self, _: RequestId) {}
457
458 fn process_request_eof(&mut self, _: RequestId) {}
459
460 fn process_response(
461 &mut self,
462 _: RequestId,
463 fetch_metadata: Result<FetchMetadata, NetworkError>,
464 ) {
465 _ = fetch_metadata;
466 }
467
468 fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
469 _ = chunk;
470 }
471
472 fn process_response_eof(
473 &mut self,
474 _: RequestId,
475 response: Result<ResourceFetchTiming, NetworkError>,
476 ) {
477 _ = response;
478 }
479
480 fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
481 &mut self.resource_timing
482 }
483
484 fn resource_timing(&self) -> &ResourceFetchTiming {
485 &self.resource_timing
486 }
487
488 fn submit_resource_timing(&mut self) {
489 submit_timing(self, CanGc::note())
490 }
491
492 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
493 let global = self.resource_timing_global();
494 global.report_csp_violations(violations, None, None);
495 }
496}
497
498impl ResourceTimingListener for BeaconFetchListener {
499 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
500 (InitiatorType::Beacon, self.url.clone())
501 }
502
503 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
504 self.global.root()
505 }
506}
507
508impl PreInvoke for BeaconFetchListener {
509 fn should_invoke(&self) -> bool {
510 true
511 }
512}