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