Skip to main content

script/dom/navigator/
navigator.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
5#[cfg(feature = "gamepad")]
6use std::cell::Cell;
7use std::convert::TryInto;
8use std::ops::Deref;
9use std::sync::LazyLock;
10
11use bytes::Bytes;
12use dom_struct::dom_struct;
13use embedder_traits::{EmbedderMsg, ProtocolHandlerUpdateRegistration, RegisterOrUnregister};
14use headers::HeaderMap;
15use http::header::{self, HeaderValue};
16use js::context::JSContext;
17use js::rust::MutableHandleValue;
18use net_traits::blob_url_store::UrlWithBlobClaim;
19use net_traits::request::{
20    CredentialsMode, Destination, RequestBuilder, RequestId, RequestMode,
21    is_cors_safelisted_request_content_type,
22};
23use net_traits::{FetchMetadata, NetworkError, ResourceFetchTiming};
24use regex::Regex;
25#[cfg(feature = "gamepad")]
26use script_bindings::cell::DomRefCell;
27use script_bindings::reflector::{Reflector, reflect_dom_object};
28use servo_base::generic_channel;
29use servo_config::pref;
30use servo_url::ServoUrl;
31
32use crate::ScriptThread;
33use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods;
34#[cfg(feature = "gamepad")]
35use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionName;
36use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
37use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
38use crate::dom::bindings::error::{Error, Fallible};
39use crate::dom::bindings::reflector::DomGlobal;
40use crate::dom::bindings::root::{DomRoot, MutNullableDom};
41use crate::dom::bindings::str::{DOMString, USVString};
42use crate::dom::bindings::utils::to_frozen_array;
43#[cfg(feature = "bluetooth")]
44use crate::dom::bluetooth::Bluetooth;
45use crate::dom::clipboard::Clipboard;
46use crate::dom::credentialmanagement::credentialscontainer::CredentialsContainer;
47use crate::dom::csp::{GlobalCspReporting, Violation};
48#[cfg(feature = "gamepad")]
49use crate::dom::gamepad::Gamepad;
50use crate::dom::geolocation::Geolocation;
51use crate::dom::globalscope::GlobalScope;
52use crate::dom::mediadevices::MediaDevices;
53use crate::dom::mediasession::MediaSession;
54use crate::dom::mimetypearray::MimeTypeArray;
55use crate::dom::navigatorinfo;
56use crate::dom::performance::performanceresourcetiming::InitiatorType;
57use crate::dom::permissions::Permissions;
58use crate::dom::pluginarray::PluginArray;
59use crate::dom::serviceworkercontainer::ServiceWorkerContainer;
60use crate::dom::servointernals::ServoInternals;
61use crate::dom::storagemanager::StorageManager;
62use crate::dom::types::UserActivation;
63use crate::dom::wakelock::WakeLock;
64#[cfg(feature = "webgpu")]
65use crate::dom::webgpu::gpu::GPU;
66use crate::dom::window::Window;
67#[cfg(feature = "webxr")]
68use crate::dom::xrsystem::XRSystem;
69use crate::fetch::body::Extractable;
70use crate::fetch::fetch::RequestWithGlobalScope;
71use crate::fetch::network_listener::{
72    FetchResponseListener, ResourceTimingListener, submit_timing,
73};
74
75pub(crate) fn hardware_concurrency() -> u64 {
76    static CPUS: LazyLock<u64> = LazyLock::new(|| num_cpus::get().try_into().unwrap_or(1));
77
78    *CPUS
79}
80
81/// <https://html.spec.whatwg.org/multipage/#safelisted-scheme>
82static SAFELISTED_SCHEMES: [&str; 24] = [
83    "bitcoin",
84    "ftp",
85    "ftps",
86    "geo",
87    "im",
88    "irc",
89    "ircs",
90    "magnet",
91    "mailto",
92    "matrix",
93    "mms",
94    "news",
95    "nntp",
96    "openpgp4fpr",
97    "sftp",
98    "sip",
99    "sms",
100    "smsto",
101    "ssh",
102    "tel",
103    "urn",
104    "webcal",
105    "wtai",
106    "xmpp",
107];
108
109/// Used in <https://html.spec.whatwg.org/multipage/#normalize-protocol-handler-parameters>
110fn matches_web_plus_protocol(scheme: &str) -> bool {
111    static WEB_PLUS_SCHEME_GRAMMAR: LazyLock<Regex> =
112        LazyLock::new(|| Regex::new(r#"^web\+[a-z]+$"#).unwrap());
113
114    WEB_PLUS_SCHEME_GRAMMAR.is_match(scheme)
115}
116
117#[dom_struct]
118pub(crate) struct Navigator {
119    reflector_: Reflector,
120    #[cfg(feature = "bluetooth")]
121    bluetooth: MutNullableDom<Bluetooth>,
122    credentials: MutNullableDom<CredentialsContainer>,
123    plugins: MutNullableDom<PluginArray>,
124    mime_types: MutNullableDom<MimeTypeArray>,
125    service_worker: MutNullableDom<ServiceWorkerContainer>,
126    #[cfg(feature = "webxr")]
127    xr: MutNullableDom<XRSystem>,
128    mediadevices: MutNullableDom<MediaDevices>,
129    geolocation: MutNullableDom<Geolocation>,
130    /// <https://www.w3.org/TR/gamepad/#dfn-gamepads>
131    #[cfg(feature = "gamepad")]
132    gamepads: DomRefCell<Vec<MutNullableDom<Gamepad>>>,
133    permissions: MutNullableDom<Permissions>,
134    mediasession: MutNullableDom<MediaSession>,
135    clipboard: MutNullableDom<Clipboard>,
136    storage: MutNullableDom<StorageManager>,
137    #[cfg(feature = "webgpu")]
138    gpu: MutNullableDom<GPU>,
139    /// <https://www.w3.org/TR/gamepad/#dfn-hasgamepadgesture>
140    #[cfg(feature = "gamepad")]
141    has_gamepad_gesture: Cell<bool>,
142    servo_internals: MutNullableDom<ServoInternals>,
143    user_activation: MutNullableDom<UserActivation>,
144    wake_lock: MutNullableDom<WakeLock>,
145}
146
147impl Navigator {
148    fn new_inherited() -> Navigator {
149        Navigator {
150            reflector_: Reflector::new(),
151            #[cfg(feature = "bluetooth")]
152            bluetooth: Default::default(),
153            credentials: Default::default(),
154            plugins: Default::default(),
155            mime_types: Default::default(),
156            service_worker: Default::default(),
157            #[cfg(feature = "webxr")]
158            xr: Default::default(),
159            mediadevices: Default::default(),
160            geolocation: Default::default(),
161            #[cfg(feature = "gamepad")]
162            gamepads: Default::default(),
163            permissions: Default::default(),
164            mediasession: Default::default(),
165            clipboard: Default::default(),
166            storage: Default::default(),
167            #[cfg(feature = "webgpu")]
168            gpu: Default::default(),
169            #[cfg(feature = "gamepad")]
170            has_gamepad_gesture: Cell::new(false),
171            servo_internals: Default::default(),
172            user_activation: Default::default(),
173            wake_lock: Default::default(),
174        }
175    }
176
177    pub(crate) fn new(cx: &mut JSContext, window: &Window) -> DomRoot<Navigator> {
178        reflect_dom_object(cx, Box::new(Navigator::new_inherited()), window)
179    }
180
181    #[cfg(feature = "webxr")]
182    pub(crate) fn xr(&self) -> Option<DomRoot<XRSystem>> {
183        self.xr.get()
184    }
185
186    /// The `Geolocation` object, if `navigator.geolocation` was ever accessed.
187    pub(crate) fn maybe_geolocation(&self) -> Option<DomRoot<Geolocation>> {
188        self.geolocation.get()
189    }
190
191    #[cfg(feature = "gamepad")]
192    pub(crate) fn get_gamepad(&self, index: usize) -> Option<DomRoot<Gamepad>> {
193        self.gamepads.borrow().get(index).and_then(|g| g.get())
194    }
195
196    #[cfg(feature = "gamepad")]
197    pub(crate) fn set_gamepad(&self, index: usize, gamepad: Option<&Gamepad>) {
198        if let Some(gamepad_to_set) = self.gamepads.borrow().get(index) {
199            gamepad_to_set.set(gamepad);
200        }
201    }
202
203    /// <https://www.w3.org/TR/gamepad/#dfn-selecting-an-unused-gamepad-index>
204    #[cfg(feature = "gamepad")]
205    pub(crate) fn select_gamepad_index(&self) -> u32 {
206        let mut gamepad_list = self.gamepads.borrow_mut();
207        if let Some(index) = gamepad_list.iter().position(|g| g.get().is_none()) {
208            index as u32
209        } else {
210            let len = gamepad_list.len();
211            gamepad_list.resize_with(len + 1, Default::default);
212            len as u32
213        }
214    }
215
216    /// Step 2.6 of <https://www.w3.org/TR/gamepad/#dfn-gamepaddisconnected>
217    #[cfg(feature = "gamepad")]
218    pub(crate) fn shrink_gamepads_list(&self) {
219        let mut gamepad_list = self.gamepads.borrow_mut();
220        for i in (0..gamepad_list.len()).rev() {
221            if gamepad_list.get(i).is_none() {
222                gamepad_list.remove(i);
223            } else {
224                break;
225            }
226        }
227    }
228
229    #[cfg(feature = "gamepad")]
230    pub(crate) fn get_connected_gamepad(&self) -> Vec<DomRoot<Gamepad>> {
231        self.gamepads
232            .borrow()
233            .iter()
234            .filter_map(|gamepad| gamepad.get())
235            .filter(|gamepad| gamepad.connected())
236            .collect()
237    }
238
239    #[cfg(feature = "gamepad")]
240    pub(crate) fn has_gamepad_gesture(&self) -> bool {
241        self.has_gamepad_gesture.get()
242    }
243
244    #[cfg(feature = "gamepad")]
245    pub(crate) fn set_has_gamepad_gesture(&self, has_gamepad_gesture: bool) {
246        self.has_gamepad_gesture.set(has_gamepad_gesture);
247    }
248
249    /// <https://html.spec.whatwg.org/multipage/#normalize-protocol-handler-parameters>
250    fn normalize_protocol_handler_parameters(
251        &self,
252        scheme: DOMString,
253        url: USVString,
254    ) -> Fallible<(String, ServoUrl)> {
255        // Step 1. Set scheme to scheme, converted to ASCII lowercase.
256        let scheme = scheme.to_ascii_lowercase();
257        // Step 2. If scheme is neither a safelisted scheme nor
258        // a string starting with "web+" followed by one or more ASCII lower alphas, then throw a "SecurityError" DOMException.
259        if !SAFELISTED_SCHEMES.contains(&scheme.as_ref()) && !matches_web_plus_protocol(&scheme) {
260            return Err(Error::Security(None));
261        }
262        // Step 3. If url does not contain "%s", then throw a "SyntaxError" DOMException.
263        if !url.contains("%s") {
264            return Err(Error::Syntax(Some(
265                "Missing replacement string %s in URL".to_owned(),
266            )));
267        }
268        // Step 4. Let urlRecord be the result of encoding-parsing a URL given url, relative to environment.
269        let environment = self.global();
270        // Navigator is only exposed on Window, so this is safe to do
271        let window = environment.as_window();
272        let Ok(url) = window.Document().encoding_parse_a_url(&url) else {
273            // Step 5. If urlRecord is failure, then throw a "SyntaxError" DOMException.
274            return Err(Error::Syntax(Some("Cannot parse URL".to_owned())));
275        };
276        // Step 6. If urlRecord's scheme is not an HTTP(S) scheme or urlRecord's origin
277        // is not same origin with environment's origin, then throw a "SecurityError" DOMException.
278        if !matches!(url.scheme(), "http" | "https") {
279            return Err(Error::Security(None));
280        }
281        let environment_origin = environment.origin().immutable().clone();
282        if url.origin() != environment_origin {
283            return Err(Error::Security(None));
284        }
285        // Step 7. Assert: the result of Is url potentially trustworthy? given urlRecord is "Potentially Trustworthy".
286        assert!(url.is_potentially_trustworthy());
287        // Step 8. Return (scheme, urlRecord).
288        Ok((scheme, url))
289    }
290
291    fn send_protocol_update_registration_to_embedder(
292        &self,
293        registration: ProtocolHandlerUpdateRegistration,
294    ) {
295        let global = self.global();
296        let window = global.as_window();
297        let (sender, _) = generic_channel::channel().unwrap();
298        let _ = global
299            .script_to_embedder_chan()
300            .send(EmbedderMsg::AllowProtocolHandlerRequest(
301                window.webview_id(),
302                registration,
303                sender,
304            ));
305    }
306}
307
308impl NavigatorMethods<crate::DomTypeHolder> for Navigator {
309    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-product>
310    fn Product(&self) -> DOMString {
311        navigatorinfo::Product()
312    }
313
314    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-productsub>
315    fn ProductSub(&self) -> DOMString {
316        navigatorinfo::ProductSub()
317    }
318
319    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-vendor>
320    fn Vendor(&self) -> DOMString {
321        navigatorinfo::Vendor()
322    }
323
324    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-vendorsub>
325    fn VendorSub(&self) -> DOMString {
326        navigatorinfo::VendorSub()
327    }
328
329    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-taintenabled>
330    fn TaintEnabled(&self) -> bool {
331        navigatorinfo::TaintEnabled()
332    }
333
334    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-appname>
335    fn AppName(&self) -> DOMString {
336        navigatorinfo::AppName()
337    }
338
339    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-appcodename>
340    fn AppCodeName(&self) -> DOMString {
341        navigatorinfo::AppCodeName()
342    }
343
344    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-platform>
345    fn Platform(&self) -> DOMString {
346        navigatorinfo::Platform()
347    }
348
349    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-useragent>
350    fn UserAgent(&self) -> DOMString {
351        navigatorinfo::UserAgent(&pref!(user_agent))
352    }
353
354    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-appversion>
355    fn AppVersion(&self) -> DOMString {
356        navigatorinfo::AppVersion()
357    }
358
359    // https://webbluetoothcg.github.io/web-bluetooth/#dom-navigator-bluetooth
360    #[cfg(feature = "bluetooth")]
361    fn Bluetooth(&self, cx: &mut js::context::JSContext) -> DomRoot<Bluetooth> {
362        self.bluetooth
363            .or_init(|| Bluetooth::new(cx, &self.global()))
364    }
365
366    /// <https://www.w3.org/TR/credential-management-1/#framework-credential-management>
367    fn Credentials(&self, cx: &mut js::context::JSContext) -> DomRoot<CredentialsContainer> {
368        self.credentials
369            .or_init(|| CredentialsContainer::new(cx, &self.global()))
370    }
371
372    /// <https://www.w3.org/TR/geolocation/#navigator_interface>
373    fn Geolocation(&self, cx: &mut js::context::JSContext) -> DomRoot<Geolocation> {
374        self.geolocation
375            .or_init(|| Geolocation::new(cx, &self.global()))
376    }
377
378    /// <https://html.spec.whatwg.org/multipage/#navigatorlanguage>
379    fn Language(&self) -> DOMString {
380        navigatorinfo::Language()
381    }
382
383    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-languages>
384    fn Languages(&self, cx: &mut js::context::JSContext, retval: MutableHandleValue) {
385        to_frozen_array(cx, &[self.Language()], retval)
386    }
387
388    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-online>
389    fn OnLine(&self) -> bool {
390        ScriptThread::online()
391    }
392
393    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-plugins>
394    fn Plugins(&self, cx: &mut JSContext) -> DomRoot<PluginArray> {
395        self.plugins
396            .or_init(|| PluginArray::new(cx, &self.global()))
397    }
398
399    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-mimetypes>
400    fn MimeTypes(&self, cx: &mut JSContext) -> DomRoot<MimeTypeArray> {
401        self.mime_types
402            .or_init(|| MimeTypeArray::new(cx, &self.global()))
403    }
404
405    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-javaenabled>
406    fn JavaEnabled(&self) -> bool {
407        false
408    }
409
410    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-pdfviewerenabled>
411    fn PdfViewerEnabled(&self) -> bool {
412        false
413    }
414
415    /// <https://w3c.github.io/ServiceWorker/#navigator-service-worker-attribute>
416    fn ServiceWorker(&self, cx: &mut js::context::JSContext) -> DomRoot<ServiceWorkerContainer> {
417        self.service_worker
418            .or_init(|| ServiceWorkerContainer::new(cx, &self.global()))
419    }
420
421    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-cookieenabled>
422    fn CookieEnabled(&self) -> bool {
423        true
424    }
425
426    /// <https://www.w3.org/TR/gamepad/#dom-navigator-getgamepads>
427    #[cfg(feature = "gamepad")]
428    fn GetGamepads(&self, cx: &mut JSContext) -> Fallible<Vec<Option<DomRoot<Gamepad>>>> {
429        use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
430
431        // Step 1. Let doc be the current global object's associated Document.
432        let global = self.global();
433        let window = global.as_window();
434        let doc = window.Document();
435
436        // Step 2. If doc is null or doc is not fully active, then return an empty list.
437        if !doc.is_fully_active() {
438            return Ok(Vec::new());
439        }
440
441        // Step 3. If doc is not allowed to use the "gamepad" permission,
442        // then throw a "SecurityError" DOMException.
443        if !doc.allowed_to_use_feature(PermissionName::Gamepad) {
444            return Err(Error::Security(Some(
445                "Gamepad permission not allowed".into(),
446            )));
447        }
448
449        // Step 4. If this.[[hasGamepadGesture]] is false, then return an empty list.
450        if !self.has_gamepad_gesture.get() {
451            return Ok(Vec::new());
452        }
453
454        // Step 5. Let now be the current high resolution time given the current global object.
455        let now = *window.Performance(cx).Now();
456
457        // Step 6. Let gamepads be an empty list.
458        // Step 7. For each gamepad of this.[[gamepads]]:
459        Ok(self
460            .gamepads
461            .borrow()
462            .iter()
463            .map(|slot| {
464                slot.get().inspect(|gamepad| {
465                    // Step 7.1. If gamepad is not null and gamepad.[[exposed]] is false:
466                    if !gamepad.exposed() {
467                        // Step 7.1.1. Set gamepad.[[exposed]] to true.
468                        gamepad.set_exposed(true);
469                        // Step 7.1.2. Set gamepad.[[timestamp]] to now.
470                        gamepad.update_timestamp(now);
471                    }
472                })
473            })
474            .collect()) // Step 7.2. Append gamepad to gamepads.
475        // Step 8. Return gamepads.
476    }
477    /// <https://w3c.github.io/permissions/#navigator-and-workernavigator-extension>
478    fn Permissions(&self, cx: &mut JSContext) -> DomRoot<Permissions> {
479        self.permissions
480            .or_init(|| Permissions::new(cx, &self.global()))
481    }
482
483    /// <https://immersive-web.github.io/webxr/#dom-navigator-xr>
484    #[cfg(feature = "webxr")]
485    fn Xr(&self, cx: &mut JSContext) -> DomRoot<XRSystem> {
486        self.xr
487            .or_init(|| XRSystem::new(cx, self.global().as_window()))
488    }
489
490    /// <https://w3c.github.io/mediacapture-main/#dom-navigator-mediadevices>
491    fn MediaDevices(&self, cx: &mut JSContext) -> DomRoot<MediaDevices> {
492        self.mediadevices
493            .or_init(|| MediaDevices::new(cx, &self.global()))
494    }
495
496    /// <https://w3c.github.io/mediasession/#dom-navigator-mediasession>
497    fn MediaSession(&self, cx: &mut JSContext) -> DomRoot<MediaSession> {
498        self.mediasession.or_init(|| {
499            // There is a single MediaSession instance per Pipeline
500            // and only one active MediaSession globally.
501            //
502            // MediaSession creation can happen in two cases:
503            //
504            // - If content gets `navigator.mediaSession`
505            // - If a media instance (HTMLMediaElement so far) starts playing media.
506            let global = self.global();
507            let window = global.as_window();
508            MediaSession::new(cx, window)
509        })
510    }
511
512    // https://gpuweb.github.io/gpuweb/#dom-navigator-gpu
513    #[cfg(feature = "webgpu")]
514    fn Gpu(&self, cx: &mut JSContext) -> DomRoot<GPU> {
515        self.gpu.or_init(|| GPU::new(cx, &self.global()))
516    }
517
518    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-hardwareconcurrency>
519    fn HardwareConcurrency(&self) -> u64 {
520        hardware_concurrency()
521    }
522
523    /// <https://w3c.github.io/clipboard-apis/#h-navigator-clipboard>
524    fn Clipboard(&self, cx: &mut js::context::JSContext) -> DomRoot<Clipboard> {
525        self.clipboard
526            .or_init(|| Clipboard::new(cx, &self.global()))
527    }
528
529    /// <https://storage.spec.whatwg.org/#api>
530    fn Storage(&self, cx: &mut js::context::JSContext) -> DomRoot<StorageManager> {
531        self.storage
532            .or_init(|| StorageManager::new(cx, &self.global()))
533    }
534
535    /// <https://w3c.github.io/beacon/#sec-processing-model>
536    fn SendBeacon(
537        &self,
538        cx: &mut js::context::JSContext,
539        url: USVString,
540        data: Option<BodyInit>,
541    ) -> Fallible<bool> {
542        let global = self.global();
543        // Step 1. Set base to this's relevant settings object's API base URL.
544        let base = global.api_base_url();
545        // Step 2. Set origin to this's relevant settings object's origin.
546        //
547        // Handled in `crate::fetch::fetch::RequestWithGlobalScope::with_global_scope`
548
549        // Step 3. Set parsedUrl to the result of the URL parser steps with url and base.
550        // If the algorithm returns an error, or if parsedUrl's scheme is not "http" or "https",
551        // throw a "TypeError" exception and terminate these steps.
552        let Ok(url) = ServoUrl::parse_with_base(Some(&base), &url) else {
553            return Err(Error::Type(c"Cannot parse URL".to_owned()));
554        };
555        if !matches!(url.scheme(), "http" | "https") {
556            return Err(Error::Type(c"URL is not http(s)".to_owned()));
557        }
558        let mut request_body = None;
559        // Step 4. Let headerList be an empty list.
560        let mut headers = HeaderMap::with_capacity(1);
561        // Step 5. Let corsMode be "no-cors".
562        let mut cors_mode = RequestMode::NoCors;
563        // Step 6. If data is not null:
564        if let Some(data) = data {
565            // Step 6.1. Set transmittedData and contentType to the result of extracting data's byte stream
566            // with the keepalive flag set.
567            let extracted_body = data.extract(cx, &global, true)?;
568            // Step 6.2. If the amount of data that can be queued to be sent by keepalive enabled requests
569            // is exceeded by the size of transmittedData (as defined in HTTP-network-or-cache fetch),
570            // set the return value to false and terminate these steps.
571            if let Some(total_bytes) = extracted_body.total_bytes {
572                let in_flight_keep_alive_bytes =
573                    global.total_size_of_in_flight_keep_alive_records();
574                if total_bytes as u64 + in_flight_keep_alive_bytes > 64 * 1024 {
575                    return Ok(false);
576                }
577            }
578            // Step 6.3. If contentType is not null:
579            if let Some(content_type) = extracted_body.content_type.as_ref() {
580                // Set corsMode to "cors".
581                cors_mode = RequestMode::CorsMode;
582                // If contentType value is a CORS-safelisted request-header value for the Content-Type header,
583                // set corsMode to "no-cors".
584                if is_cors_safelisted_request_content_type(
585                    content_type.as_bytes(cx.no_gc()).deref(),
586                ) {
587                    cors_mode = RequestMode::NoCors;
588                }
589                // Append a Content-Type header with value contentType to headerList.
590                //
591                // We cannot use typed header insertion with `mime::Mime` parsing here,
592                // since it lowercases `charset=UTF-8`: https://github.com/hyperium/mime/issues/116
593                if let Ok(content_type_header_value) = HeaderValue::from_str(&content_type.str()) {
594                    headers.insert(header::CONTENT_TYPE, content_type_header_value);
595                }
596            }
597            request_body = Some(extracted_body.into_net_request_body(cx).0);
598        }
599        // Step 7.1. Let req be a new request, initialized as follows:
600        let request = RequestBuilder::new(
601            None,
602            UrlWithBlobClaim::from_url_without_having_claimed_blob(url.clone()),
603            global.get_referrer(),
604        )
605        .mode(cors_mode)
606        .destination(Destination::None)
607        .with_global_scope(&global)
608        .method(http::Method::POST)
609        .body(request_body)
610        .keep_alive(true)
611        .credentials_mode(CredentialsMode::Include)
612        .headers(headers);
613        // Step 7.2. Fetch req.
614        global.fetch(
615            request,
616            BeaconFetchListener { url },
617            global.task_manager().networking_task_source().into(),
618        );
619        // Step 7. Set the return value to true, return the sendBeacon() call,
620        // and continue to run the following steps in parallel:
621        Ok(true)
622    }
623
624    /// <https://servo.org/internal-no-spec>
625    fn Servo(&self, cx: &mut js::context::JSContext) -> DomRoot<ServoInternals> {
626        self.servo_internals
627            .or_init(|| ServoInternals::new(cx, &self.global()))
628    }
629
630    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-registerprotocolhandler>
631    fn RegisterProtocolHandler(&self, scheme: DOMString, url: USVString) -> Fallible<()> {
632        // Step 1. Let (normalizedScheme, normalizedURLString) be the result of
633        // running normalize protocol handler parameters with scheme, url, and this's relevant settings object.
634        let (scheme, url) = self.normalize_protocol_handler_parameters(scheme, url)?;
635        // Step 2. In parallel: register a protocol handler for normalizedScheme and normalizedURLString.
636        // User agents may, within the constraints described, do whatever they like. A user agent could,
637        // for instance, prompt the user and offer the user the opportunity to add the site to a shortlist of handlers,
638        // or make the handlers their default, or cancel the request. User agents could also silently collect the information,
639        // providing it only when relevant to the user.
640        // User agents should keep track of which sites have registered handlers (even if the user has declined such registrations)
641        // so that the user is not repeatedly prompted with the same request.
642        // If the registerProtocolHandler() automation mode of this's relevant global object's associated Document is not "none",
643        // the user agent should first verify that it is in an automation context (see WebDriver's security considerations).
644        // The user agent should then bypass the above communication of information and gathering of user consent,
645        // and instead do the following based on the value of the registerProtocolHandler() automation mode:
646        self.send_protocol_update_registration_to_embedder(ProtocolHandlerUpdateRegistration {
647            scheme,
648            url,
649            register_or_unregister: RegisterOrUnregister::Register,
650        });
651        Ok(())
652    }
653
654    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-unregisterprotocolhandler>
655    fn UnregisterProtocolHandler(&self, scheme: DOMString, url: USVString) -> Fallible<()> {
656        // Step 1. Let (normalizedScheme, normalizedURLString) be the result of
657        // running normalize protocol handler parameters with scheme, url, and this's relevant settings object.
658        let (scheme, url) = self.normalize_protocol_handler_parameters(scheme, url)?;
659        // Step 2. In parallel: unregister the handler described by normalizedScheme and normalizedURLString.
660        self.send_protocol_update_registration_to_embedder(ProtocolHandlerUpdateRegistration {
661            scheme,
662            url,
663            register_or_unregister: RegisterOrUnregister::Unregister,
664        });
665        Ok(())
666    }
667
668    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-useractivation>
669    fn UserActivation(&self, cx: &mut js::context::JSContext) -> DomRoot<UserActivation> {
670        self.user_activation
671            .or_init(|| UserActivation::new(cx, &self.global()))
672    }
673
674    /// <https://w3c.github.io/screen-wake-lock/#dom-navigator-wakelock>
675    fn WakeLock(&self, cx: &mut js::context::JSContext) -> DomRoot<WakeLock> {
676        self.wake_lock.or_init(|| WakeLock::new(cx, &self.global()))
677    }
678}
679
680struct BeaconFetchListener {
681    /// URL of this request.
682    url: ServoUrl,
683}
684
685impl FetchResponseListener for BeaconFetchListener {
686    fn process_request_body(&mut self, _: &GlobalScope, _: RequestId) {}
687
688    fn process_response(
689        &mut self,
690        _: &mut js::context::JSContext,
691        _: &GlobalScope,
692        _: RequestId,
693        fetch_metadata: Result<FetchMetadata, NetworkError>,
694    ) {
695        _ = fetch_metadata;
696    }
697
698    fn process_response_chunk(
699        &mut self,
700        _: &mut js::context::JSContext,
701        _: &GlobalScope,
702        _: RequestId,
703        chunk: Bytes,
704    ) {
705        _ = chunk;
706    }
707
708    fn process_response_eof(
709        self,
710        cx: &mut js::context::JSContext,
711        global: &GlobalScope,
712        _: RequestId,
713        response: Result<(), NetworkError>,
714        timing: ResourceFetchTiming,
715    ) {
716        submit_timing(cx, global, &self, &response, &timing);
717    }
718
719    fn process_csp_violations(
720        &mut self,
721        cx: &mut js::context::JSContext,
722        global: &GlobalScope,
723        _request_id: RequestId,
724        violations: Vec<Violation>,
725    ) {
726        global.report_csp_violations(cx, violations, None, None);
727    }
728}
729
730impl ResourceTimingListener for BeaconFetchListener {
731    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
732        (InitiatorType::Beacon, self.url.clone())
733    }
734}