Skip to main content

servo/
site_data_manager.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, RefCell};
6
7use bitflags::bitflags;
8use cookie::Cookie;
9use log::warn;
10use net_traits::pub_domains::registered_domain_name;
11use net_traits::{CookieOperationId, ResourceThreads, SiteDescriptor};
12use rustc_hash::FxHashMap;
13use servo_url::ServoUrl;
14use storage_traits::StorageThreads;
15use storage_traits::webstorage_thread::{OriginDescriptor, WebStorageType};
16use url::Url;
17
18use crate::CookieSource;
19
20bitflags! {
21    /// Identifies categories of site data associated with a site.
22    ///
23    /// This type is used by `SiteDataManager` to query, describe, and manage
24    /// different kinds of data stored by the user agent for a given site.
25    ///
26    /// Additional storage categories (e.g. IndexedDB) may be added in the
27    /// future.
28    #[derive(Clone, Copy, Debug, PartialEq)]
29    pub struct StorageType: u8 {
30        /// Corresponds to the HTTP cookies:
31        /// <https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies>
32        const Cookies = 1 << 0;
33
34        /// Corresponds to the `localStorage` Web API:
35        /// <https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage>
36        const Local   = 1 << 1;
37
38        /// Corresponds to the `sessionStorage` Web API:
39        /// <https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage>
40        const Session = 1 << 2;
41    }
42}
43
44#[derive(Clone, Debug, PartialEq)]
45pub struct SiteData {
46    name: String,
47    storage_types: StorageType,
48}
49
50impl SiteData {
51    pub fn new(name: impl Into<String>, storage_types: StorageType) -> SiteData {
52        SiteData {
53            name: name.into(),
54            storage_types,
55        }
56    }
57
58    pub fn name(&self) -> String {
59        self.name.clone()
60    }
61
62    pub fn storage_types(&self) -> StorageType {
63        self.storage_types
64    }
65}
66
67/// The response data for a pending embedder cookie operation.
68pub(crate) enum CookieOperationResponse {
69    /// Cookies returned from a get operation.
70    Cookies(Vec<Cookie<'static>>),
71    /// Acknowledgement that a operation is completed.
72    Done,
73}
74
75/// A callback for a pending embedder cookie operation,
76/// paired with the [`CookieOperationResponse`].
77enum CookieOperationCallback {
78    Cookies(Box<dyn FnOnce(Vec<Cookie<'static>>)>),
79    Done(Box<dyn FnOnce()>),
80}
81
82/// Provides APIs for inspecting and managing site data.
83///
84/// `SiteDataManager` exposes information about data that is conceptually
85/// associated with a site (equivalent to an eTLD+1), such as web exposed
86/// storage mechanisms like `localStorage` and `sessionStorage`.
87///
88/// The manager can be used by embedders to list sites with stored data.
89/// Support for site scoped management operations (e.g. clearing data for a
90/// specific site) will be added in the future.
91///
92/// Note: Network layer state (such as the HTTP cache) is intentionally not
93/// handled by `SiteDataManager`. That functionality lives in `NetworkManager`.
94pub struct SiteDataManager {
95    public_resource_threads: ResourceThreads,
96    private_resource_threads: ResourceThreads,
97    public_storage_threads: StorageThreads,
98    private_storage_threads: StorageThreads,
99    next_cookie_op_id: Cell<u64>,
100    pending_cookie_callbacks: RefCell<FxHashMap<CookieOperationId, CookieOperationCallback>>,
101}
102
103impl SiteDataManager {
104    pub(crate) fn new(
105        public_resource_threads: ResourceThreads,
106        private_resource_threads: ResourceThreads,
107        public_storage_threads: StorageThreads,
108        private_storage_threads: StorageThreads,
109    ) -> Self {
110        Self {
111            public_resource_threads,
112            private_resource_threads,
113            public_storage_threads,
114            private_storage_threads,
115            next_cookie_op_id: Cell::new(0),
116            pending_cookie_callbacks: RefCell::new(FxHashMap::default()),
117        }
118    }
119
120    /// Return a list of sites that have associated site data.
121    ///
122    /// The returned list is filtered by the provided `storage_types` bitflags.
123    /// Each [`SiteData`] entry represents a site (equivalent to an eTLD+1)
124    /// and indicates which kinds of storage data are present for it (e.g.
125    /// localStorage, sessionStorage).
126    ///
127    /// The returned list is sorted by site name.
128    ///
129    /// Both public and private storage are included in the result.
130    pub fn site_data(&self, storage_types: StorageType) -> Vec<SiteData> {
131        let mut all_sites: FxHashMap<String, StorageType> = FxHashMap::default();
132
133        let mut add_sites = |sites: Vec<SiteDescriptor>, storage_type: StorageType| {
134            for site in sites {
135                all_sites
136                    .entry(site.name)
137                    .and_modify(|storage_types| *storage_types |= storage_type)
138                    .or_insert(storage_type);
139            }
140        };
141
142        if storage_types.contains(StorageType::Cookies) {
143            let public_cookies = self.public_resource_threads.cookies();
144            add_sites(public_cookies, StorageType::Cookies);
145
146            let private_cookies = self.private_resource_threads.cookies();
147            add_sites(private_cookies, StorageType::Cookies);
148        }
149
150        let mut add_origins = |origins: Vec<OriginDescriptor>, storage_type: StorageType| {
151            for origin in origins {
152                let url =
153                    ServoUrl::parse(&origin.name).expect("Should always be able to parse origins.");
154
155                let Some(domain) = registered_domain_name(&url) else {
156                    warn!("Failed to get a registered domain name for: {url}.");
157                    continue;
158                };
159                let domain = domain.to_string();
160
161                all_sites
162                    .entry(domain)
163                    .and_modify(|storage_types| *storage_types |= storage_type)
164                    .or_insert(storage_type);
165            }
166        };
167
168        if storage_types.contains(StorageType::Local) {
169            let public_origins = self
170                .public_storage_threads
171                .webstorage_origins(WebStorageType::Local);
172            add_origins(public_origins, StorageType::Local);
173
174            let private_origins = self
175                .private_storage_threads
176                .webstorage_origins(WebStorageType::Local);
177            add_origins(private_origins, StorageType::Local);
178        }
179
180        if storage_types.contains(StorageType::Session) {
181            let public_origins = self
182                .public_storage_threads
183                .webstorage_origins(WebStorageType::Session);
184            add_origins(public_origins, StorageType::Session);
185
186            let private_origins = self
187                .private_storage_threads
188                .webstorage_origins(WebStorageType::Session);
189            add_origins(private_origins, StorageType::Session);
190        }
191
192        let mut result: Vec<SiteData> = all_sites
193            .into_iter()
194            .map(|(name, storage_types)| SiteData::new(name, storage_types))
195            .collect();
196
197        result.sort_by_key(SiteData::name);
198
199        result
200    }
201
202    /// Clear site data for the given sites.
203    ///
204    /// The clearing is restricted to the provided `storage_types` bitflags.
205    /// Both public and private browsing data are affected.
206    pub fn clear_site_data(&self, sites: &[&str], storage_types: StorageType) {
207        if storage_types.contains(StorageType::Cookies) {
208            self.public_resource_threads.clear_cookies_for_sites(sites);
209            self.private_resource_threads.clear_cookies_for_sites(sites);
210        }
211
212        if storage_types.contains(StorageType::Local) {
213            self.public_storage_threads
214                .clear_webstorage_for_sites(WebStorageType::Local, sites);
215            self.private_storage_threads
216                .clear_webstorage_for_sites(WebStorageType::Local, sites);
217        }
218
219        if storage_types.contains(StorageType::Session) {
220            self.public_storage_threads
221                .clear_webstorage_for_sites(WebStorageType::Session, sites);
222            self.private_storage_threads
223                .clear_webstorage_for_sites(WebStorageType::Session, sites);
224        }
225    }
226
227    pub fn clear_cookies(&self) {
228        self.public_resource_threads.clear_cookies();
229        self.private_resource_threads.clear_cookies();
230    }
231
232    /// Delete all session cookies (cookies that have no expiry or max-age).
233    /// Session cookies from both the public and private browsing session cookies are removed.
234    pub fn clear_session_cookies(&self) {
235        self.public_resource_threads.clear_session_cookies();
236        self.private_resource_threads.clear_session_cookies();
237    }
238
239    /// Returns the cookies for the domain associated with the given [`Url`].
240    pub fn cookies_for_url(&self, url: Url, source: CookieSource) -> Vec<Cookie<'static>> {
241        self.public_resource_threads
242            .cookies_for_url(url.into(), source)
243    }
244
245    /// Sets a cookie for the domain associated with the given [`Url`].
246    ///
247    /// Returns `true` if the request to set the cookie is successfully sent.
248    /// This call will block, such that any operations triggered after the
249    /// call will use the provided cookie.
250    pub fn set_cookie_for_url(&self, url: Url, cookie: Cookie<'static>) {
251        self.public_resource_threads.set_cookie_for_url_sync(
252            url.into(),
253            cookie,
254            CookieSource::HTTP,
255        );
256    }
257
258    /// Asynchronously returns the cookies for the domain associated with the given [`Url`].
259    pub fn cookies_for_url_async(
260        &self,
261        url: Url,
262        source: CookieSource,
263        callback: impl FnOnce(Vec<Cookie<'static>>) + 'static,
264    ) {
265        let id = self.next_operation_id();
266        self.pending_cookie_callbacks
267            .borrow_mut()
268            .insert(id, CookieOperationCallback::Cookies(Box::new(callback)));
269        self.public_resource_threads
270            .cookies_for_url_async(id, url.into(), source);
271    }
272
273    /// Asynchronously sets a cookie for the domain associated with the given [`Url`].
274    pub fn set_cookie_for_url_async(
275        &self,
276        url: Url,
277        cookie: Cookie<'static>,
278        callback: impl FnOnce() + 'static,
279    ) {
280        let id = self.next_operation_id();
281        self.pending_cookie_callbacks
282            .borrow_mut()
283            .insert(id, CookieOperationCallback::Done(Box::new(callback)));
284        self.public_resource_threads.set_cookie_for_url_async(
285            id,
286            url.into(),
287            cookie,
288            CookieSource::HTTP,
289        );
290    }
291
292    /// Handle a cookie operation response from the resource thread.
293    ///
294    /// This is called by the event loop when an embedder cookie response is received.
295    pub(crate) fn handle_cookie_response(
296        &self,
297        id: CookieOperationId,
298        response: CookieOperationResponse,
299    ) {
300        let Some(callback) = self.pending_cookie_callbacks.borrow_mut().remove(&id) else {
301            warn!("Received cookie response for unknown operation {id:?}");
302            return;
303        };
304        match (callback, response) {
305            (CookieOperationCallback::Cookies(cb), CookieOperationResponse::Cookies(cookies)) => {
306                cb(cookies);
307            },
308            (CookieOperationCallback::Done(cb), CookieOperationResponse::Done) => {
309                cb();
310            },
311            _ => {
312                warn!("Cookie response type mismatch for operation {id:?}");
313            },
314        }
315    }
316
317    fn next_operation_id(&self) -> CookieOperationId {
318        let id = CookieOperationId(self.next_cookie_op_id.get());
319        self.next_cookie_op_id.set(id.0 + 1);
320        id
321    }
322}