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::csp::{GlobalCspReporting, Violation};
38use crate::dom::gamepad::Gamepad;
39use crate::dom::gamepad::gamepadevent::GamepadEventType;
40use crate::dom::globalscope::GlobalScope;
41use crate::dom::mediadevices::MediaDevices;
42use crate::dom::mediasession::MediaSession;
43use crate::dom::mimetypearray::MimeTypeArray;
44use crate::dom::navigatorinfo;
45use crate::dom::performanceresourcetiming::InitiatorType;
46use crate::dom::permissions::Permissions;
47use crate::dom::pluginarray::PluginArray;
48use crate::dom::serviceworkercontainer::ServiceWorkerContainer;
49use crate::dom::servointernals::ServoInternals;
50#[cfg(feature = "webgpu")]
51use crate::dom::webgpu::gpu::GPU;
52use crate::dom::window::Window;
53#[cfg(feature = "webxr")]
54use crate::dom::xrsystem::XRSystem;
55use crate::network_listener::{PreInvoke, ResourceTimingListener, submit_timing};
56use crate::script_runtime::{CanGc, JSContext};
57
58pub(super) fn hardware_concurrency() -> u64 {
59 static CPUS: LazyLock<u64> = LazyLock::new(|| num_cpus::get().try_into().unwrap_or(1));
60
61 *CPUS
62}
63
64#[dom_struct]
65pub(crate) struct Navigator {
66 reflector_: Reflector,
67 #[cfg(feature = "bluetooth")]
68 bluetooth: MutNullableDom<Bluetooth>,
69 plugins: MutNullableDom<PluginArray>,
70 mime_types: MutNullableDom<MimeTypeArray>,
71 service_worker: MutNullableDom<ServiceWorkerContainer>,
72 #[cfg(feature = "webxr")]
73 xr: MutNullableDom<XRSystem>,
74 mediadevices: MutNullableDom<MediaDevices>,
75 gamepads: DomRefCell<Vec<MutNullableDom<Gamepad>>>,
77 permissions: MutNullableDom<Permissions>,
78 mediasession: MutNullableDom<MediaSession>,
79 clipboard: MutNullableDom<Clipboard>,
80 #[cfg(feature = "webgpu")]
81 gpu: MutNullableDom<GPU>,
82 has_gamepad_gesture: Cell<bool>,
84 servo_internals: MutNullableDom<ServoInternals>,
85}
86
87impl Navigator {
88 fn new_inherited() -> Navigator {
89 Navigator {
90 reflector_: Reflector::new(),
91 #[cfg(feature = "bluetooth")]
92 bluetooth: Default::default(),
93 plugins: Default::default(),
94 mime_types: Default::default(),
95 service_worker: Default::default(),
96 #[cfg(feature = "webxr")]
97 xr: Default::default(),
98 mediadevices: Default::default(),
99 gamepads: Default::default(),
100 permissions: Default::default(),
101 mediasession: Default::default(),
102 clipboard: Default::default(),
103 #[cfg(feature = "webgpu")]
104 gpu: Default::default(),
105 has_gamepad_gesture: Cell::new(false),
106 servo_internals: Default::default(),
107 }
108 }
109
110 pub(crate) fn new(window: &Window, can_gc: CanGc) -> DomRoot<Navigator> {
111 reflect_dom_object(Box::new(Navigator::new_inherited()), window, can_gc)
112 }
113
114 #[cfg(feature = "webxr")]
115 pub(crate) fn xr(&self) -> Option<DomRoot<XRSystem>> {
116 self.xr.get()
117 }
118
119 pub(crate) fn get_gamepad(&self, index: usize) -> Option<DomRoot<Gamepad>> {
120 self.gamepads.borrow().get(index).and_then(|g| g.get())
121 }
122
123 pub(crate) fn set_gamepad(&self, index: usize, gamepad: &Gamepad, can_gc: CanGc) {
124 if let Some(gamepad_to_set) = self.gamepads.borrow().get(index) {
125 gamepad_to_set.set(Some(gamepad));
126 }
127 if self.has_gamepad_gesture.get() {
128 gamepad.set_exposed(true);
129 if self.global().as_window().Document().is_fully_active() {
130 gamepad.notify_event(GamepadEventType::Connected, can_gc);
131 }
132 }
133 }
134
135 pub(crate) fn remove_gamepad(&self, index: usize) {
136 if let Some(gamepad_to_remove) = self.gamepads.borrow_mut().get(index) {
137 gamepad_to_remove.set(None);
138 }
139 self.shrink_gamepads_list();
140 }
141
142 pub(crate) fn select_gamepad_index(&self) -> u32 {
144 let mut gamepad_list = self.gamepads.borrow_mut();
145 if let Some(index) = gamepad_list.iter().position(|g| g.get().is_none()) {
146 index as u32
147 } else {
148 let len = gamepad_list.len();
149 gamepad_list.resize_with(len + 1, Default::default);
150 len as u32
151 }
152 }
153
154 fn shrink_gamepads_list(&self) {
155 let mut gamepad_list = self.gamepads.borrow_mut();
156 for i in (0..gamepad_list.len()).rev() {
157 if gamepad_list.get(i).is_none() {
158 gamepad_list.remove(i);
159 } else {
160 break;
161 }
162 }
163 }
164
165 pub(crate) fn has_gamepad_gesture(&self) -> bool {
166 self.has_gamepad_gesture.get()
167 }
168
169 pub(crate) fn set_has_gamepad_gesture(&self, has_gamepad_gesture: bool) {
170 self.has_gamepad_gesture.set(has_gamepad_gesture);
171 }
172}
173
174impl NavigatorMethods<crate::DomTypeHolder> for Navigator {
175 fn Product(&self) -> DOMString {
177 navigatorinfo::Product()
178 }
179
180 fn ProductSub(&self) -> DOMString {
182 navigatorinfo::ProductSub()
183 }
184
185 fn Vendor(&self) -> DOMString {
187 navigatorinfo::Vendor()
188 }
189
190 fn VendorSub(&self) -> DOMString {
192 navigatorinfo::VendorSub()
193 }
194
195 fn TaintEnabled(&self) -> bool {
197 navigatorinfo::TaintEnabled()
198 }
199
200 fn AppName(&self) -> DOMString {
202 navigatorinfo::AppName()
203 }
204
205 fn AppCodeName(&self) -> DOMString {
207 navigatorinfo::AppCodeName()
208 }
209
210 fn Platform(&self) -> DOMString {
212 navigatorinfo::Platform()
213 }
214
215 fn UserAgent(&self) -> DOMString {
217 navigatorinfo::UserAgent(&pref!(user_agent))
218 }
219
220 fn AppVersion(&self) -> DOMString {
222 navigatorinfo::AppVersion()
223 }
224
225 #[cfg(feature = "bluetooth")]
227 fn Bluetooth(&self) -> DomRoot<Bluetooth> {
228 self.bluetooth
229 .or_init(|| Bluetooth::new(&self.global(), CanGc::note()))
230 }
231
232 fn Language(&self) -> DOMString {
234 navigatorinfo::Language()
235 }
236
237 #[allow(unsafe_code)]
239 fn Languages(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
240 to_frozen_array(&[self.Language()], cx, retval, can_gc)
241 }
242
243 fn OnLine(&self) -> bool {
245 true
246 }
247
248 fn Plugins(&self) -> DomRoot<PluginArray> {
250 self.plugins
251 .or_init(|| PluginArray::new(&self.global(), CanGc::note()))
252 }
253
254 fn MimeTypes(&self) -> DomRoot<MimeTypeArray> {
256 self.mime_types
257 .or_init(|| MimeTypeArray::new(&self.global(), CanGc::note()))
258 }
259
260 fn JavaEnabled(&self) -> bool {
262 false
263 }
264
265 fn ServiceWorker(&self) -> DomRoot<ServiceWorkerContainer> {
267 self.service_worker
268 .or_init(|| ServiceWorkerContainer::new(&self.global(), CanGc::note()))
269 }
270
271 fn CookieEnabled(&self) -> bool {
273 true
274 }
275
276 fn GetGamepads(&self) -> Vec<Option<DomRoot<Gamepad>>> {
278 let global = self.global();
279 let window = global.as_window();
280 let doc = window.Document();
281
282 if !doc.is_fully_active() || !self.has_gamepad_gesture.get() {
284 return Vec::new();
285 }
286
287 self.gamepads.borrow().iter().map(|g| g.get()).collect()
288 }
289 fn Permissions(&self) -> DomRoot<Permissions> {
291 self.permissions
292 .or_init(|| Permissions::new(&self.global(), CanGc::note()))
293 }
294
295 #[cfg(feature = "webxr")]
297 fn Xr(&self) -> DomRoot<XRSystem> {
298 self.xr
299 .or_init(|| XRSystem::new(self.global().as_window(), CanGc::note()))
300 }
301
302 fn MediaDevices(&self) -> DomRoot<MediaDevices> {
304 self.mediadevices
305 .or_init(|| MediaDevices::new(&self.global(), CanGc::note()))
306 }
307
308 fn MediaSession(&self) -> DomRoot<MediaSession> {
310 self.mediasession.or_init(|| {
311 let global = self.global();
319 let window = global.as_window();
320 MediaSession::new(window, CanGc::note())
321 })
322 }
323
324 #[cfg(feature = "webgpu")]
326 fn Gpu(&self) -> DomRoot<GPU> {
327 self.gpu.or_init(|| GPU::new(&self.global(), CanGc::note()))
328 }
329
330 fn HardwareConcurrency(&self) -> u64 {
332 hardware_concurrency()
333 }
334
335 fn Clipboard(&self) -> DomRoot<Clipboard> {
337 self.clipboard
338 .or_init(|| Clipboard::new(&self.global(), CanGc::note()))
339 }
340
341 fn SendBeacon(&self, url: USVString, data: Option<BodyInit>, can_gc: CanGc) -> Fallible<bool> {
343 let global = self.global();
344 let base = global.api_base_url();
346 let origin = global.origin().immutable().clone();
348 let Ok(url) = ServoUrl::parse_with_base(Some(&base), &url) else {
352 return Err(Error::Type("Cannot parse URL".to_owned()));
353 };
354 if !matches!(url.scheme(), "http" | "https") {
355 return Err(Error::Type("URL is not http(s)".to_owned()));
356 }
357 let mut request_body = None;
358 let mut headers = HeaderMap::with_capacity(1);
360 let mut cors_mode = RequestMode::NoCors;
362 if let Some(data) = data {
364 let extracted_body = data.extract(&global, can_gc)?;
367 if let Some(total_bytes) = extracted_body.total_bytes {
371 if total_bytes > 64 * 1024 {
372 return Ok(false);
373 }
374 }
375 if let Some(content_type) = extracted_body.content_type.as_ref() {
377 cors_mode = RequestMode::CorsMode;
379 if is_cors_safelisted_request_content_type(content_type.as_bytes()) {
382 cors_mode = RequestMode::NoCors;
383 }
384 headers.insert(
389 header::CONTENT_TYPE,
390 HeaderValue::from_str(content_type).unwrap(),
391 );
392 }
393 request_body = Some(extracted_body.into_net_request_body().0);
394 }
395 let request = RequestBuilder::new(None, url.clone(), global.get_referrer())
397 .mode(cors_mode)
398 .destination(Destination::None)
399 .policy_container(global.policy_container())
400 .insecure_requests_policy(global.insecure_requests_policy())
401 .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_or_current_origin())
402 .method(http::Method::POST)
403 .body(request_body)
404 .origin(origin)
405 .credentials_mode(CredentialsMode::Include)
407 .headers(headers);
408 global.fetch(
410 request,
411 Arc::new(Mutex::new(BeaconFetchListener {
412 url,
413 global: Trusted::new(&global),
414 resource_timing: ResourceFetchTiming::new(ResourceTimingType::None),
415 })),
416 global.task_manager().networking_task_source().into(),
417 );
418 Ok(true)
421 }
422
423 fn Servo(&self) -> DomRoot<ServoInternals> {
425 self.servo_internals
426 .or_init(|| ServoInternals::new(&self.global(), CanGc::note()))
427 }
428}
429
430struct BeaconFetchListener {
431 url: ServoUrl,
433 resource_timing: ResourceFetchTiming,
435 global: Trusted<GlobalScope>,
437}
438
439impl FetchResponseListener for BeaconFetchListener {
440 fn process_request_body(&mut self, _: RequestId) {}
441
442 fn process_request_eof(&mut self, _: RequestId) {}
443
444 fn process_response(
445 &mut self,
446 _: RequestId,
447 fetch_metadata: Result<FetchMetadata, NetworkError>,
448 ) {
449 _ = fetch_metadata;
450 }
451
452 fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
453 _ = chunk;
454 }
455
456 fn process_response_eof(
457 &mut self,
458 _: RequestId,
459 response: Result<ResourceFetchTiming, NetworkError>,
460 ) {
461 _ = response;
462 }
463
464 fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
465 &mut self.resource_timing
466 }
467
468 fn resource_timing(&self) -> &ResourceFetchTiming {
469 &self.resource_timing
470 }
471
472 fn submit_resource_timing(&mut self) {
473 submit_timing(self, CanGc::note())
474 }
475
476 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
477 let global = self.resource_timing_global();
478 global.report_csp_violations(violations, None, None);
479 }
480}
481
482impl ResourceTimingListener for BeaconFetchListener {
483 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
484 (InitiatorType::Beacon, self.url.clone())
485 }
486
487 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
488 self.global.root()
489 }
490}
491
492impl PreInvoke for BeaconFetchListener {
493 fn should_invoke(&self) -> bool {
494 true
495 }
496}