Skip to main content

servo/
geolocation_delegate.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 embedder_traits::{
6    GeolocationError, GeolocationPositionData, GeolocationRequestOptions, GeolocationResult,
7    GeolocationWatchId,
8};
9use servo_base::generic_channel::GenericCallback;
10use servo_config::pref;
11
12use crate::WebView;
13
14/// A request for a single geolocation fix, corresponding to one call to
15/// navigator.geolocation.getCurrentPosition()
16pub struct PositionRequest {
17    result_sender: GenericCallback<GeolocationResult>,
18    options: GeolocationRequestOptions,
19    response_sent: bool,
20}
21
22impl PositionRequest {
23    pub(crate) fn new(
24        result_sender: GenericCallback<GeolocationResult>,
25        options: GeolocationRequestOptions,
26    ) -> Self {
27        Self {
28            result_sender,
29            options,
30            response_sent: false,
31        }
32    }
33
34    /// Hints about how the position should be acquired.
35    pub fn options(&self) -> &GeolocationRequestOptions {
36        &self.options
37    }
38
39    /// Report the acquired position.
40    pub fn success(mut self, position: GeolocationPositionData) {
41        let _ = self.result_sender.send(Ok(position));
42        self.response_sent = true;
43    }
44
45    /// Report that the position could not be acquired.
46    pub fn failure(mut self, error: GeolocationError) {
47        let _ = self.result_sender.send(Err(error));
48        self.response_sent = true;
49    }
50}
51
52impl Drop for PositionRequest {
53    fn drop(&mut self) {
54        if !self.response_sent {
55            let _ = self
56                .result_sender
57                .send(Err(GeolocationError::position_unavailable(
58                    "No response sent for request.",
59                )));
60        }
61    }
62}
63
64/// An ongoing subscription to position updates, corresponding to one call to
65/// navigator.geolocation.watchPosition()
66pub struct PositionWatch {
67    id: GeolocationWatchId,
68    result_sender: GenericCallback<GeolocationResult>,
69    options: GeolocationRequestOptions,
70}
71
72impl PositionWatch {
73    pub(crate) fn new(
74        id: GeolocationWatchId,
75        result_sender: GenericCallback<GeolocationResult>,
76        options: GeolocationRequestOptions,
77    ) -> Self {
78        Self {
79            id,
80            result_sender,
81            options,
82        }
83    }
84
85    /// The identifier used to stop this watch.
86    pub fn id(&self) -> GeolocationWatchId {
87        self.id.clone()
88    }
89
90    /// Hints about how positions should be acquired.
91    pub fn options(&self) -> &GeolocationRequestOptions {
92        &self.options
93    }
94
95    /// Deliver a new position to script. May be called any number of times.
96    pub fn update(&self, position: GeolocationPositionData) {
97        let _ = self.result_sender.send(Ok(position));
98    }
99
100    /// Report that a position could not be acquired. This does not end the watch: script keeps
101    /// listening, and the embedder may still call PositionWatch::update() later.
102    pub fn error(&self, error: GeolocationError) {
103        let _ = self.result_sender.send(Err(error));
104    }
105}
106
107/// A delegate responsible for acquiring the device's geographic position.
108pub trait GeolocationDelegate {
109    /// A request for a single position fix.
110    fn request_position(&self, _webview: WebView, request: PositionRequest) {
111        match default_position_response() {
112            Ok(position) => request.success(position),
113            Err(error) => request.failure(error),
114        }
115    }
116
117    /// A request to start delivering continuous position updates.
118    fn start_watch(&self, _webview: WebView, watch: PositionWatch) {
119        match default_position_response() {
120            Ok(position) => watch.update(position),
121            Err(error) => watch.error(error),
122        }
123    }
124
125    /// A request to stop delivering updates for a watch previously passed to
126    /// GeolocationDelegate::start_watch(), and to drop the corresponding PositionWatch.
127    fn stop_watch(&self, _webview: WebView, _id: &GeolocationWatchId) {}
128}
129
130/// The position described by the `dom_geolocation_test_position_*` preferences, or an error if
131/// they are not set.
132fn default_position_response() -> GeolocationResult {
133    if !pref!(dom_geolocation_test_position_enabled) {
134        return Err(GeolocationError::position_unavailable(
135            "No geolocation backend is available.",
136        ));
137    }
138
139    Ok(GeolocationPositionData {
140        accuracy: pref!(dom_geolocation_test_accuracy),
141        latitude: pref!(dom_geolocation_test_latitude),
142        longitude: pref!(dom_geolocation_test_longitude),
143        altitude: None,
144        altitude_accuracy: None,
145        heading: Some(pref!(dom_geolocation_test_heading)),
146        speed: Some(pref!(dom_geolocation_test_speed)),
147    })
148}
149
150pub(crate) struct DefaultGeolocationDelegate;
151
152impl GeolocationDelegate for DefaultGeolocationDelegate {}