Skip to main content

script/dom/
navigator.rs

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