Skip to main content

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