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