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