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