1use std::cell::Cell;
6use std::convert::TryInto;
7use std::ops::Deref;
8use std::sync::LazyLock;
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::{FetchMetadata, NetworkError, ResourceFetchTiming};
19use servo_config::pref;
20use servo_url::ServoUrl;
21
22use crate::body::Extractable;
23use crate::dom::bindings::cell::DomRefCell;
24use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods;
25use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
26use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
27use crate::dom::bindings::error::{Error, Fallible};
28use crate::dom::bindings::refcounted::Trusted;
29use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
30use crate::dom::bindings::root::{DomRoot, MutNullableDom};
31use crate::dom::bindings::str::{DOMString, USVString};
32use crate::dom::bindings::utils::to_frozen_array;
33#[cfg(feature = "bluetooth")]
34use crate::dom::bluetooth::Bluetooth;
35use crate::dom::clipboard::Clipboard;
36use crate::dom::credentialmanagement::credentialscontainer::CredentialsContainer;
37use crate::dom::csp::{GlobalCspReporting, Violation};
38use crate::dom::gamepad::Gamepad;
39use crate::dom::gamepad::gamepadevent::GamepadEventType;
40use crate::dom::geolocation::Geolocation;
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::performance::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::{FetchResponseListener, 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 Geolocation(&self) -> DomRoot<Geolocation> {
243 Geolocation::new(&self.global(), CanGc::note())
244 }
245
246 fn Language(&self) -> DOMString {
248 navigatorinfo::Language()
249 }
250
251 fn Languages(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
253 to_frozen_array(&[self.Language()], cx, retval, can_gc)
254 }
255
256 fn OnLine(&self) -> bool {
258 true
259 }
260
261 fn Plugins(&self) -> DomRoot<PluginArray> {
263 self.plugins
264 .or_init(|| PluginArray::new(&self.global(), CanGc::note()))
265 }
266
267 fn MimeTypes(&self) -> DomRoot<MimeTypeArray> {
269 self.mime_types
270 .or_init(|| MimeTypeArray::new(&self.global(), CanGc::note()))
271 }
272
273 fn JavaEnabled(&self) -> bool {
275 false
276 }
277
278 fn ServiceWorker(&self) -> DomRoot<ServiceWorkerContainer> {
280 self.service_worker
281 .or_init(|| ServiceWorkerContainer::new(&self.global(), CanGc::note()))
282 }
283
284 fn CookieEnabled(&self) -> bool {
286 true
287 }
288
289 fn GetGamepads(&self) -> Vec<Option<DomRoot<Gamepad>>> {
291 let global = self.global();
292 let window = global.as_window();
293 let doc = window.Document();
294
295 if !doc.is_fully_active() || !self.has_gamepad_gesture.get() {
297 return Vec::new();
298 }
299
300 self.gamepads.borrow().iter().map(|g| g.get()).collect()
301 }
302 fn Permissions(&self) -> DomRoot<Permissions> {
304 self.permissions
305 .or_init(|| Permissions::new(&self.global(), CanGc::note()))
306 }
307
308 #[cfg(feature = "webxr")]
310 fn Xr(&self) -> DomRoot<XRSystem> {
311 self.xr
312 .or_init(|| XRSystem::new(self.global().as_window(), CanGc::note()))
313 }
314
315 fn MediaDevices(&self) -> DomRoot<MediaDevices> {
317 self.mediadevices
318 .or_init(|| MediaDevices::new(&self.global(), CanGc::note()))
319 }
320
321 fn MediaSession(&self) -> DomRoot<MediaSession> {
323 self.mediasession.or_init(|| {
324 let global = self.global();
332 let window = global.as_window();
333 MediaSession::new(window, CanGc::note())
334 })
335 }
336
337 #[cfg(feature = "webgpu")]
339 fn Gpu(&self) -> DomRoot<GPU> {
340 self.gpu.or_init(|| GPU::new(&self.global(), CanGc::note()))
341 }
342
343 fn HardwareConcurrency(&self) -> u64 {
345 hardware_concurrency()
346 }
347
348 fn Clipboard(&self) -> DomRoot<Clipboard> {
350 self.clipboard
351 .or_init(|| Clipboard::new(&self.global(), CanGc::note()))
352 }
353
354 fn SendBeacon(&self, url: USVString, data: Option<BodyInit>, can_gc: CanGc) -> Fallible<bool> {
356 let global = self.global();
357 let base = global.api_base_url();
359 let origin = global.origin().immutable().clone();
361 let Ok(url) = ServoUrl::parse_with_base(Some(&base), &url) else {
365 return Err(Error::Type("Cannot parse URL".to_owned()));
366 };
367 if !matches!(url.scheme(), "http" | "https") {
368 return Err(Error::Type("URL is not http(s)".to_owned()));
369 }
370 let mut request_body = None;
371 let mut headers = HeaderMap::with_capacity(1);
373 let mut cors_mode = RequestMode::NoCors;
375 if let Some(data) = data {
377 let extracted_body = data.extract(&global, can_gc)?;
380 if let Some(total_bytes) = extracted_body.total_bytes {
384 if total_bytes > 64 * 1024 {
385 return Ok(false);
386 }
387 }
388 if let Some(content_type) = extracted_body.content_type.as_ref() {
390 cors_mode = RequestMode::CorsMode;
392 if is_cors_safelisted_request_content_type(content_type.as_bytes().deref()) {
395 cors_mode = RequestMode::NoCors;
396 }
397 headers.insert(
402 header::CONTENT_TYPE,
403 HeaderValue::from_str(&content_type.str()).unwrap(),
404 );
405 }
406 request_body = Some(extracted_body.into_net_request_body().0);
407 }
408 let request = RequestBuilder::new(None, url.clone(), global.get_referrer())
410 .mode(cors_mode)
411 .destination(Destination::None)
412 .policy_container(global.policy_container())
413 .insecure_requests_policy(global.insecure_requests_policy())
414 .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_or_current_origin())
415 .method(http::Method::POST)
416 .body(request_body)
417 .origin(origin)
418 .credentials_mode(CredentialsMode::Include)
420 .headers(headers);
421 global.fetch(
423 request,
424 BeaconFetchListener {
425 url,
426 global: Trusted::new(&global),
427 },
428 global.task_manager().networking_task_source().into(),
429 );
430 Ok(true)
433 }
434
435 fn Servo(&self) -> DomRoot<ServoInternals> {
437 self.servo_internals
438 .or_init(|| ServoInternals::new(&self.global(), CanGc::note()))
439 }
440}
441
442struct BeaconFetchListener {
443 url: ServoUrl,
445 global: Trusted<GlobalScope>,
447}
448
449impl FetchResponseListener for BeaconFetchListener {
450 fn process_request_body(&mut self, _: RequestId) {}
451
452 fn process_request_eof(&mut self, _: RequestId) {}
453
454 fn process_response(
455 &mut self,
456 _: RequestId,
457 fetch_metadata: Result<FetchMetadata, NetworkError>,
458 ) {
459 _ = fetch_metadata;
460 }
461
462 fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
463 _ = chunk;
464 }
465
466 fn process_response_eof(
467 self,
468 _: RequestId,
469 response: Result<ResourceFetchTiming, NetworkError>,
470 ) {
471 if let Ok(response) = response {
472 submit_timing(&self, &response, CanGc::note());
473 }
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}