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