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